Une visite
Chaque écran de Textchum, dans l'ordre où vous les rencontreriez, sur les deux interfaces : l'app macOS à gauche et celle en GTK à droite. Les captures viennent d'un petit projet fictif — Harbor, un courtier de ports qui n'existe que pour donner à ces images quelque chose d'honnête à montrer — et chacune suit votre réglage clair ou sombre.
Les deux interfaces sont le même éditeur sur le même cœur : les images diffèrent surtout par ce qu'apporte la plateforme — l'habillage de la fenêtre, l'endroit où un panneau se dessine, et la police que le système fournit.
La fenêtre
Un document par fenêtre, des onglets par défaut, et un tiroir de navigation en deux moitiés : en haut les tampons ouverts groupés par projet, en bas l'arbre de fichiers de ce projet.
La barre de titre porte les faits du document — encodage, taille, langage, et le nombre de problèmes dès qu'un serveur de langage a un avis. L'arbre suit : changer d'onglet déplie le chemin du fichier courant et le met en évidence, et un fichier enregistré, créé ou supprimé dans un dossier qu'il montre — par l'éditeur, une refactorisation ou une commande dans un terminal — apparaît ou disparaît de lui-même.
Un clic droit dans le texte ouvre le menu de l'éditeur : aller à la définition, chercher les références, renommer le symbole, le diagnostic de la ligne, le blame de la ligne, formater le document et les propriétés du fichier, à côté de couper, copier, coller et des suggestions orthographiques. Ces commandes agissent sur le caractère cliqué et non sur le curseur, que le clic laisse où il était. Ce dont le document n'a pas l'usage n'y est pas : sans serveur pas de recherche des références, sans signalements pas de lignes de diagnostics.
Repliement
Fold (⌘[, Ctrl+[ sous Linux) referme le bloc qui s'ouvre sur la ligne du curseur ; Fold All (⌥⌘[, Ctrl+Alt+[) referme tous ceux qui ne sont pas déjà dans un bloc fermé, et Unfold All (⌘], Ctrl+]) les rouvre. Un bloc fermé montre sa ligne d'ouverture suivie de points de suspension.
Les blocs viennent de l'arbre qui sert à la coloration, et les replis appartiennent au document : refermer une fonction dans une vue la referme dans toutes les vues de ce fichier.
Colonnes
Une fenêtre est une rangée de colonnes. Une colonne montre un fichier à la fois et tient une ou plusieurs vues de celui-ci, empilées.
New Column (⌘\, Ctrl+\ sous Linux) place une colonne à côté de
celle-ci, montrant le même fichier jusqu'à ce qu'on lui en donne un
autre ; Close Column (⇧⌘\, Ctrl+Shift+\) la retire. Second
View (⌥⌘\, Ctrl+Alt+\) empile une autre vue du fichier de la
colonne sous la première, et Close View (⇧⌥⌘\,
Ctrl+Alt+Shift+\) l'enlève. Next Pane (⌥⌘, Ctrl+Alt+) déplace le
clavier de l'une à l'autre.
Chaque vue défile de son côté, et c'est bien le but : lire le haut d'un fichier en modifiant le bas. La colonne possède le fichier qu'elle montre : changer son onglet emmène toutes ses vues vers le nouveau fichier.
Les deux côtés sont un seul document. Il y a une histoire et un enregistrement : une modification d'un côté est la même modification, et aucune vue ne peut être une copie périmée de l'autre. Les deux toolkits sont faits pour cela — un tampon de texte que plusieurs vues partagent — et ce qui n'est pas gratuit est la coloration, qui sous macOS vit dans la mise en page et non dans le texte ; chaque vue est peinte.
Serveurs de langage
Les diagnostics arrivent en marques teintées dans le texte et en compteur dans la barre de titre. Rien dans l'éditeur n'attend le serveur : il s'attache quand il peut, et le dit quand il ne peut pas.
La marge dit la même chose : une ligne avec un diagnostic porte son numéro dans la couleur de la sévérité — rouge, orange, bleu — avec un disque à côté pour une erreur ou un avertissement et un anneau pour les informations et les indices. Le code dans la documentation du serveur — une signature, une définition — est coloré comme l'éditeur colore ce langage, et le menu contextuel de l'éditeur montre les mêmes touches que le menu principal pour chaque commande.
Laisser le pointeur sur un symbole affiche la documentation du serveur, avec le Markdown qu'il envoie déjà rendu — blocs de code en chasse fixe, emphase stylée. ⌃⌘H la demande pour le symbole sous le curseur, ce qui marche même souris désactivée.
La même documentation peut vivre dans un panneau plutôt que dans une bulle. Affichage ▸ Panneau d'information (⌥⌘I) en ancre un sous l'éditeur, comme une seconde vue s'empile dans la colonne ; tant qu'il est là la bulle ne paraît pas, et le panneau montre la documentation du symbole sous le pointeur, et celle du symbole au curseur à mesure qu'il se déplace. Sa seconde face liste les diagnostics du document — ligne, type, message — et un clic mène à la ligne. Le panneau suit le fichier qui a le focus d'un onglet et d'une colonne à l'autre.
Aller ▸ Historique des sauts… liste le chemin que Retour suivrait, le plus récent d'abord, avec le chemin avant en dessous quand il y en a un, dans la même liste que Rechercher les références ; choisir un endroit y mène, comme un saut de plus. Les deux listes montrent chaque ligne dans les couleurs de son langage.
Un dossier s'ouvre comme projet : Fichier ▸ Ouvrir accepte un
répertoire, et chum chemin/vers/dossier aussi. Un chemin qui n'existe
pas encore — chum notes/nouveau.go — ouvre un nouveau document de ce
nom, traité comme le langage que son nom indique, et Enregistrer
l'écrit là sans demander. Quand un onglet de ce
projet est ouvert quelque part, cet onglet passe devant ; sinon une
nouvelle fenêtre s'ouvre avec le projet dans son arbre et sans fichier
encore, et elle reste ouverte quand son dernier onglet se ferme. La
barre d'état dit la branche git du projet, lue quand le fichier est
montré et de nouveau quand git réécrit HEAD — l'éditeur surveille ce
fichier plutôt que d'interroger git à intervalle. Ensuite viennent les
miettes de pain : les constructions nommées où se trouve le
curseur, la plus extérieure d'abord — l'impl et la fonction, la
classe et la méthode —, lues dans l'arbre syntaxique, sans besoin de
serveur de langage. Chacune est un menu de ce qui est à côté, celle
qu'elle nomme marquée ; en choisir une y va, comme un saut dont Go Back
revient. Un texte qu'aucune grammaire ne lit n'en a pas. Son extrémité
droite
est la zone de notifications : la dernière chose que l'éditeur a eu
à dire — un serveur de langage introuvable, et ce qui l'installerait ;
une grammaire qu'il n'a pas pu charger ; un enregistrement qui s'est
fait sans ses préprocesseurs — autant qu'il en tient, et en entier
comme infobulle. Rien de cela n'interrompt : ce qui était un dialogue
à fermer est une ligne ici. Un clic liste les notifications de la
session, la plus récente d'abord avec l'heure de chacune,
sélectionnables, puisque ce qui est dit est souvent une commande à
lancer. Pas encore là : la liste ne survit pas à la session, et rien ne
la vide. Aller ▸ Arbres de
travail… liste les arbres de travail du dépôt : choisissez-en un pour
y basculer cette fenêtre — ses fichiers se ferment et ceux que l'autre
arbre a aussi s'y rouvrent, et les serveurs de langage démarrent dans
la nouvelle racine — ou pour l'ouvrir comme projet à part. Avec des
fichiers modifiés dans l'arbre courant, ou non enregistrés dans la
fenêtre, seule l'ouverture est proposée ; un arbre déjà ouvert passe
simplement devant. Un fichier que git confie à l'éditeur — un message
de commit, la liste d'un rebase — commence toujours en haut.
Rechercher et remplacer (⌥⌘F) ancre une barre au-dessus du texte
avec le motif, le remplacement et trois interrupteurs : expression
régulière, respecter la casse, mot entier. Les expressions sont celles
de Vim — groupes \( \), \|, \+, \?, \{n,m}, \< \>, \v
et \V, \c — et le remplacement accepte \1…\9, & ou \0 pour
la correspondance, \n, et \u \l \U \L \E pour la casse. ⏎
dans le motif va à la correspondance suivante, ⇧⏎ à la précédente, ⏎
dans le remplacement remplace la courante et continue ; Tout remplace
toutes en une seule étape d'annulation. ⎋, dans la barre ou dans le
texte, range la barre et ses marques et laisse le clavier au texte.
Aller à la ligne (⌘L, Ctrl+L sous Linux) accepte un numéro, ou le
src/main.rs:412:8 entier collé depuis un journal de compilation — le
nom du fichier et le bruit qui suit sont ignorés, la ligne arrive au
tiers de la hauteur de la vue, et Retour revient là où la lecture s'est interrompue.
Une barre de changements descend à gauche de la gouttière et dit quelles lignes diffèrent du fichier tel qu'il est dans git : une bande verte pour une ligne nouvelle, bleue pour une qui dit autre chose, et un coin rouge à la limite où des lignes ont été supprimées — une ligne supprimée n'occupe aucune hauteur, une bande n'aurait rien à couvrir. Elle suit le tampon et non le fichier sur disque, donc elle est juste avant l'enregistrement, et se recalcule quand la frappe s'arrête. Un fichier sans version validée, ou hors d'un dépôt, ne porte aucune marque plutôt que de voir toutes ses lignes déclarées nouvelles.
La barre compare avec le dernier commit, sauf indication contraire. Au
fond d'une branche cette réponse se tait — tout ce qui est validé sur
la branche compte comme inchangé — alors editor.git_marks: "branch"
compare avec le commit d'où la branche est née, et la barre montre tout
ce que la branche touche. Quand git ne nomme pas de branche par défaut,
editor.merge_base_branches liste les noms à essayer, le plus probable
d'abord ; les deux réglages peuvent être remplacés par projet.
Aller → Modifié dans la branche (⌃⌘T, Ctrl+Alt+G sous Linux) liste
les fichiers de la branche — ceux de la pull request, lus depuis git
seul — derrière le même filtre flou qu'Ouvrir rapidement.
Attribuer la ligne (⌃⌘B, Ctrl+Alt+B sous Linux) demande à git qui a touché en dernier la ligne sous le curseur : le commit, l'auteur et la date où il l'a écrite, le sujet et le corps du message — où se trouve d'ordinaire la raison — et le nom qu'avait le fichier à l'époque s'il a été renommé depuis. Le commit est à un bouton du presse-papiers, ce à quoi sert l'essentiel de la réponse. Une ligne tapée depuis le dernier commit le dit, au lieu d'emprunter l'auteur d'un autre.
Elle interroge avec le texte du tampon et non le fichier sur disque : une modification non enregistrée au-dessus du curseur ne peut donc pas déplacer discrètement la réponse sur la ligne voisine.
Dans l'espace initial d'une ligne, deux touches font un peu plus que d'ordinaire. Retour arrière efface jusqu'au taquet précédent plutôt qu'espace par espace, et Tab aligne la ligne sur la première ligne non vide au-dessus — une seconde pression, déjà alignée, descend d'un niveau. Ailleurs dans la ligne, les deux touches sont elles-mêmes : c'est la position qui décide, pas un mode, et c'est ce qui les empêche de surprendre. Une ligne indentée avec des tabulations est laissée à son propre caractère, déjà d'une frappe par niveau.
Avec du texte sélectionné, taper un délimiteur ouvrant — (, [, {,
', ", ` — entoure la sélection de la paire au lieu de la
remplacer. Ce qui vient d'être entouré reste sélectionné, si bien qu'en
taper un autre l'entoure à son tour : [, ( puis { sur hello
donnent [({hello})]. Taper autre chose remplace la sélection comme
avant.
Sans rien de sélectionné, Close brackets and quotes as they are
typed (Réglages ▸ Général, désactivé par défaut) amène la moitié
fermante — ( donne () avec le curseur au milieu — et le ) tapé
ensuite passe par-dessus celui qui est déjà là au lieu d'en ajouter un
second ; Retour arrière entre une paire vide emporte les deux. Les
crochets s'apparient partout ; les guillemets suivent le langage : '
est une apostrophe en prose et une durée de vie en Rust, où on le
laisse donc tranquille, et l'accent grave ne s'apparie que là où il
signifie du code. Aucune paire ne s'ouvre dans un mot, et aucun
guillemet juste après un. Un langage que la règle ne connaît pas, ou
pour lequel elle se trompe sur un guillemet, apporte sa propre table
dans le fichier : editor.pairs dans
la configuration.
La barre fine sous l'éditeur répond à ce qu'un regard sur le texte ne peut pas : où est le curseur, si le fichier indente avec des tabulations ou des espaces et de combien, quel langage lui est appliqué, et son encodage. L'indentation et le langage sont cliquables et ouvrent les Propriétés du fichier, où ces choix se font.
Défilé au fond d'un long corps, la première ligne de chaque
construction englobante reste épinglée en haut de la vue — la ligne
class et la ligne def pendant qu'une méthode Python défile — et
cliquer sur une épingle y mène. Les épingles coûtent des lignes ;
editor.context_lines: false (ou l'interrupteur des Réglages) les
retire.
La ligne du curseur porte une teinte légère, pour retrouver le curseur
d'un coup d'œil dans un long fichier. Un clic droit sur le texte — ou
Fichier → Copier le chemin — propose la ligne du curseur sous les
formes qui se collent : Chemin et ligne (chemin:ligne, qu'un
terminal ouvre directement) et URL de la forge pour la ligne, l'URL
du fichier sur la forge avec le fragment de ligne écrit comme cette
forge l'écrit.
Le déplacement par mots (⌥→/⌥←, Ctrl+flèches sous Linux, et leurs
formes de sélection et de suppression) s'arrête à chaque changement de
classe de caractère — identifiant, symbole, blanc — et à chaque saut de
ligne : une suite comme )} est un arrêt, et le curseur ne saute
jamais de ligne. Un guillemet appartient au mot qu'il borde, si bien
que "hello" est un seul arrêt, et ., , et ; sont franchis
comme des blancs — ils séparent les choses au lieu d'en être — si bien
que a.b fait deux arrêts, le point sauté. Une parenthèse ou accolade fermante tapée en tête de
ligne prend l'indentation de la ligne qui l'a ouverte.
La complétion apparaît à la frappe après les caractères d'identifiant
et . ; ↑/↓ choisissent, ⏎ ou ⇥ acceptent, ⎋ ferme. Un snippet arrive
avec son premier marqueur sélectionné, donc taper le remplace ; ⇥ passe
au marqueur suivant et ⇧⇥ au précédent, et un marqueur écrit deux fois
se recopie à la frappe. Le dernier ⇥ laisse le curseur là où le snippet
l'a demandé et rend les touches.
⇧⌘O liste les symboles du fichier, filtrables au clavier.
Présentation ▸ État des serveurs répond à « mon serveur est-il vivant ? » — ce qui tourne et où, plus les transitions récentes de la session, rafraîchi en direct.
Trouver des choses
⌘T ouvre les fichiers par nom flou dans le projet. La portée est parcourue une fois puis filtrée en mémoire, donc la frappe reste instantanée ; la ligne d'état dit combien de fichiers sur combien correspondent et ce que font les touches — ⏎ ouvre la ligne sélectionnée dès que les lignes répondent à la requête, et cherche tant qu'une recherche reste due ; ⌘⏎ ouvre dans tous les cas — pour qu'affiner une requête n'ouvre jamais un fichier par accident.
⇧⌘F cherche dans le contenu le texte tel qu'il est tapé — ou une
expression régulière, avec la bascule .* à côté de la requête — avec
des filtres empilés qui affinent par texte de ligne ou par chemin. La
ligne d'état dit toujours ce que la recherche a fait.
⇧⌘P est la palette de commandes : chaque action de menu, cherchable en flou, avec son raccourci à côté.
Lancer des commandes
Run → Lancer une commande… (⌃⌥R, Maj+F10 sous Linux) lance une
ligne de commande par le shell dans le projet du fichier au premier
plan — cargo test, go build ./..., pytest -x — et montre sa
sortie au fur et à mesure dans un panneau sous l'éditeur. La ligne
proposée est la dernière lancée dans ce projet ; Relancer (⌘R,
Ctrl+F5 sous Linux) relance la commande du panneau, et Arrêter
(⌘F2, Ctrl+F2 sous Linux) l'arrête avec tout ce qu'elle a lancé. La
sortie garde ses couleurs — on les demande à l'outil qui écrit dans un
panneau, comme on les lui demanderait dans un terminal — et une ligne
de progression redessinée sur place reste une seule ligne.
Les endroits que la sortie nomme — src/main.rs:12:5, le File
"app.py", line 3 d'une trace Python, ui.ts(4,7) — sont soulignés et
s'ouvrent d'un clic ; Endroit suivant de la sortie (F4) et
Endroit précédent (⇧F4) les parcourent comme les problèmes signalés.
Une commande qui ne dit rien depuis un moment tout en tournant attend
peut-être une entrée : le panneau le dit, et une ligne tapée dans son
champ part vers l'entrée de la commande.
Ce qui peut être lancé dans le fichier est marqué dans la marge d'un
triangle vert : en Rust, un #[test], un #[bench] et le main d'un
binaire ou d'un exemple ; en Go, un TestX, un BenchmarkX et le
main d'un paquet main ; en Python, une fonction ou méthode de test
pytest et le if __name__ == "__main__": d'un module. Un clic sur la
marque lance celui-là seul — cargo test -- tests::adds --exact, go
test -v -run '^TestAdds$' ., python3 -m pytest -v tests/test_app.py::test_adds
— dans le panneau, depuis le dossier du paquet. Run → Lancer au
curseur (⌃⇧R, Ctrl+Maj+F10 sous Linux) lance le plus intérieur autour
du curseur. Là où l'ampoule d'une correction est sur la même ligne,
c'est l'ampoule qui est dessinée et le clic propose la correction.
Quand la sortie rend compte de tests — le test x ... ok de cargo, go
test -v, pytest — le panneau les liste à côté par suite, chacun marqué
réussi, échoué, ignoré ou encore en cours, avec les totaux dans son
en-tête. Un test choisi dans la liste montre la ligne de la sortie qui
l'a signalé, suivie de son échec ; Entrée ou un double clic lance ce
test seul, et le champ au-dessus de la liste la filtre par nom pendant
la frappe. Relancer les échecs dans le
panneau, ou Run → Relancer les tests échoués, relance seulement ceux
qui ont échoué, depuis le même dossier : la commande est celle qui les
a lancés, ses propres filtres remplacés par les noms échoués.
Relancer lance aussi dans le dossier où le lancement a commencé, si
bien qu'un test lancé depuis sa marque se relance dans son paquet.
Pas encore là : le panneau n'est pas un terminal. Un programme qui
dessine tout l'écran — un éditeur, top, un REPL qui édite sa propre
ligne — n'y a pas sa place. Les marques ne sont trouvées que pour Rust,
Go et Python, et un test écrit selon les conventions d'un autre lanceur
n'est pas marqué. Les tests ne sont lus que dans la sortie de cargo, de
go test et de pytest ; sans -v, go test et pytest ne nomment que
ce qui a échoué, et seul cela est listé. Une ligne de commande avec un
tube ou && n'est pas restreinte pour Relancer les échecs, et un test
de la liste n'ouvre pas encore son code — l'endroit de son échec dans
la sortie, si.
Débogage
Un clic dans la colonne des marques de la marge, sur une ligne sans
autre marque, y pose un point d'arrêt — un disque rouge — et un second
clic le retire ; Run → Basculer le point d'arrêt (⌘F8, Ctrl+F8 sous
Linux) fait de même sur la ligne du curseur. Les points d'arrêt suivent
le texte modifié au-dessus d'eux. Run → Déboguer au curseur (⌃⇧D,
Maj+F9 sous Linux), ou un ⌥-clic (Alt-clic sous Linux) sur une marque
de lancement, lance le test ou le main sous un débogueur : debugpy
pour Python, Delve pour Go, lldb-dap pour Rust — une fois que cargo a
compilé le test ou le programme, ses messages dans la console de
débogage.
Le panneau de débogage s'ouvre sous l'éditeur. Quand le programme
s'arrête sur un point d'arrêt, sa ligne est teintée et le fichier
s'ouvre à cet endroit ; le panneau liste la pile du fil qui s'est
arrêté, les variables du cadre choisi dans celle-ci — une valeur
structurée s'ouvre pour montrer ce qu'elle contient — et la sortie du
programme, avec un champ qui évalue une expression dans ce cadre.
Continuer (F9, F5 sous Linux), Pas à pas principal (F8, F6
sous Linux), Pas à pas détaillé (F7), Pas à pas sortant (⇧F8,
Maj+F7 sous Linux) et Arrêter le débogage (⇧⌘F2, Ctrl+Maj+F2 sous
Linux) sont dans le menu Run et sur le panneau. Un adaptateur absent
est nommé, avec la commande qui l'installe ; un programme qui ne
démarre pas dit ce qui le retient d'habitude — sous macOS, la
permission dont un débogueur a besoin pour prendre le contrôle d'un
programme, qu'accorde le mode développeur (DevToolsSecurity -enable).
Pas encore là : le débogage ne part que d'un test ou d'un main que
trouvent les marques de lancement, en Rust, Go et Python ; l'adaptateur
d'un langage ne peut pas être choisi ; on ne peut pas s'attacher à un
programme en cours, ni poser de point d'arrêt conditionnel, ni suivre
des expressions ; et les points d'arrêt ne sont pas gardés d'un
redémarrage à l'autre.
Markdown et prose
Les documents Markdown s'ouvrent avec un aperçu vivant à côté du texte, et le correcteur orthographique de la prose — inactif jusqu'à ce que vous choisissiez un dictionnaire — marque les fautes en violet, distinct des diagnostics. Dans le code il ne regarde que les commentaires ; les identifiants ne sont jamais signalés.
Réglages
Les réglages sont un fichier JSON que la fenêtre édite ; le fichier est la trappe de secours, et il est surveillé, donc une édition ailleurs s'applique aussitôt.
Projets décide comment les racines sont trouvées, ce que l'arbre cache, et quels réglages d'éditeur une racine remplace.
Les noms cachés sont des motifs glob, édités un par ligne, avec un menu qui ajoute un préréglage nommé en un clic.
Presets édite ces ensembles nommés de la même façon. Ils commencent intégrés ; éditez-en un et votre liste prend la main, donc celui que vous supprimez le reste jusqu'à restauration. Cet écran et le suivant n'ont pas d'image de l'interface GTK parce qu'ils n'y sont pas des écrans : les presets vivent dans Projects, et les préprocesseurs dans Language Servers.

Serveurs de langage remplace la commande qui sert un langage, pour tous les projets ou pour une racine.
Préprocesseurs lance des formateurs avant chaque sauvegarde : une commande par ligne, chacune lisant le document sur l'entrée standard et le réécrivant sur la sortie standard.

Petites choses
⇧⌘N démarre un document dans le langage choisi, filtré au clavier, pour que la coloration marche avant la première sauvegarde.
Et le panneau À propos dit quelle version vous utilisez — une vraie version, même pour une compilation locale.



































































