Configuration
Les réglages de Textchum suivent un principe : l'interface graphique est le moyen confortable de les changer, et un simple fichier JSON est la sortie de secours toujours disponible. Il y a exactement un seul dépôt — le fichier — et la fenêtre Réglages le lit et l'écrit ; rien ne vit uniquement à l'intérieur de l'application.
La fenêtre Réglages
Textchum → Settings… (⌘,) édite les réglages reconnus :
- Apparence — suivre le système (en changeant en direct quand macOS le fait) ou forcer le clair ou le sombre.
- Thème — la palette de syntaxe ; voir Thèmes plus bas.
- Icônes de fichier — un pack d'icônes VS Code pour l'arborescence ; voir Icônes de fichier.
- Ouvrir les fichiers dans — des onglets de la fenêtre courante (le défaut) ou des fenêtres séparées. Avec des fenêtres séparées, le navigateur de chaque fenêtre ne liste que les documents de son propre groupe d'onglets.
- Police — toute famille à chasse fixe installée sur le système, ou la police à chasse fixe de la plateforme.
- Taille de police — de 6 à 72 points.
- Largeur de tabulation — de 1 à 16 colonnes.
- Close brackets and quotes as they are typed — désactivé par
défaut ;
editor.auto_close_pairsdans le fichier. Voir la visite. - Afficher les numéros de ligne — la marge, aussi basculables par session avec View → Toggle Line Numbers (⇧⌘L).
Chaque changement s'applique immédiatement aux fenêtres d'édition ouvertes et s'écrit sur disque au même instant. Il n'y a aucun bouton Appliquer ou Enregistrer à oublier.
Le fichier
Les réglages vivent dans :
~/Library/Application Support/Textchum/config.json
Un fichier édité à la main pourrait ressembler à :
{
"appearance": "dark",
"editor": {
"font_family": "JetBrains Mono",
"font_size": 13,
"tab_width": 4,
"hover": false,
"hover_modifier": "option"
}
}
appearance accepte "system", "light" ou "dark" ; en son absence
(le défaut), le système est suivi. editor.hover désactive la bulle de
documentation au survol (true, le défaut, la laisse active) ;
editor.hover_modifier — "shift", "control", "option" ou
"command" — la fait attendre cette touche en plus, et l'omettre
signifie la souris seule.
editor.new_files_in place les documents neufs dans un "tab" du
groupe de la fenêtre frontale (le défaut) ou dans une "window" à
eux. editor.mark_occurrences (true par défaut) marque les autres
endroits où apparaît le mot sélectionné ;
editor.occurrences_case_sensitive et editor.occurrences_whole_word
décident ce qui compte comme tel, tous deux true par défaut.
editor.rainbow_brackets (false par défaut) colore les paires de
crochets selon la profondeur ; voir coloration.
editor.inlay_hints (true par défaut) affiche les types qu'un
serveur de langage a inférés, estompés, après la ligne à laquelle ils
appartiennent ; voir serveurs de langage.
editor.context_lines (true par défaut) épingle la première ligne de
chaque construction englobante en haut de la vue pendant qu'un long
corps défile ; false retire les épingles.
editor.git_marks choisit avec quoi la barre de changements compare :
"head" (le défaut) avec le dernier commit, "branch" avec le commit
d'où la branche est née. editor.merge_base_branches est la liste des
noms de branche essayés, dans l'ordre, quand git ne nomme pas de
branche par défaut — ["main", "master", "trunk", "develop"] sinon.
Les deux clés fonctionnent aussi par projet, comme surcharges editor
d'une entrée du workspace.
editor.pairs donne à un langage sa propre table de paires, pour
Close brackets and quotes as they are typed et pour envelopper une
sélection : pour un langage que la règle intégrée ne connaît pas, ou
pour lequel elle se trompe sur un guillemet. Les clés sont des noms de
langage tels que la barre d'état les affiche ; chaque entrée est la
moitié ouvrante puis la fermante :
{
"editor": {
"auto_close_pairs": true,
"pairs": {
"nix": ["()", "[]", "{}", "\"\""],
"rust": ["()", "[]", "{}", "\"\"", "<>"]
}
}
}
Une table est la réponse entière pour son langage — rien de la règle intégrée ne s'y ajoute, si bien qu'un guillemet omis reste une frappe ordinaire. Une paire dont les deux moitiés sont le même caractère est un guillemet, jamais ouvert juste après un mot ni devant un autre lui-même, et aucune paire ne s'ouvre dans un mot, table ou pas. Pas encore là : la fenêtre des Réglages n'édite pas la table, et le texte brut ne peut pas en avoir.
Tout est optionnel — fichier, section ou clé manquants signifient simplement la valeur par défaut. Les écritures sont atomiques (fichier temporaire puis renommage), comme toute écriture de Textchum.
Deux garanties rendent l'édition à la main sûre :
- Les clés inconnues survivent. La fenêtre de réglages ne réécrit que les clés qui lui appartiennent. Tout le reste du fichier — vos annotations, des clés d'une version plus récente — est préservé tel quel à chaque enregistrement.
- Les fichiers cassés ne sont jamais écrasés. Si le fichier ne peut pas
être analysé, Textchum démarre avec les réglages par défaut, le signale
une fois au lancement et laisse le fichier exactement tel que vous
l'aviez écrit, pour le réparer dans n'importe quel éditeur — y compris
Textchum lui-même. Si vous changez un réglage depuis l'interface pendant
que le fichier est cassé, l'original inanalysable est d'abord copié vers
config.json.bak, puis remplacé.
Les valeurs hors limites ou mal typées ne comptent pas comme une casse : un
font_size de 4000 est ramené dans la plage valide, un font_family de
42 est ignoré, et le reste du fichier fonctionne normalement.
Ailleurs, le temps d'une exécution
--data-dir <chemin> garde tout ce que Textchum possède sous un seul
répertoire pour cette exécution — la configuration, les thèmes, les
packs d'icônes, la session et le journal du serveur de langage — au
lieu des emplacements habituels :
Textchum --data-dir ~/profil-d-essai
C'est un profil entier fabriqué pour l'occasion et jeté ensuite, le
vrai n'étant jamais ouvert ; make playground s'en sert, et tout ce
qui ne doit pas toucher à vos réglages aussi. Sous Linux, une exécution
avec son propre profil est son propre processus : remettre les fichiers
à une instance déjà lancée les ouvrirait dans le profil de celle-ci.
--config <chemin> en est la version étroite : il désigne un fichier
de configuration, et la session le suit.
Thèmes
Le sélecteur Theme de l'onglet General choisit la palette de syntaxe. Sept sont fournis d'origine : Textchum (par défaut), Textchum High Contrast, Graphite (un thème feutré presque monochrome) et les classiques — Molokai, Solarized, Dracula et Gruvbox. Chaque thème porte une palette claire et une palette sombre dans un même fichier, si bien qu'un thème sert aux deux modes d'apparence (les classiques nés sombres associent leur palette canonique à une claire au contraste ajusté ; Solarized et Gruvbox utilisent leurs vraies palettes claires).
Les thèmes personnels sont des fichiers JSON dans :
~/Library/Application Support/Textchum/themes/
sélectionnés par nom de fichier (sans .json) ; un fichier portant le
nom d'un thème intégré le remplace. Textchum → Open Themes Folder
ouvre (et crée) ce dossier. Le plus rapide pour en commencer un
est de générer un point de départ complet — chaque nom de capture
stylée, rempli avec la palette par défaut — et de ne changer que les
couleurs :
Textchum --emit-theme ~/Library/Application\ Support/Textchum/themes/Mien.json
Les entrées associent des noms de capture tree-sitter à des styles :
{
"name": "Mien",
"styles": {
"keyword": {"light": "#AD3DA4", "dark": "#FC5FA3", "bold": true},
"comment": {"light": "#707F8C", "dark": "#7F8C98", "italic": true}
}
}
Les couleurs s'écrivent #RRGGBB ou #RRGGBBAA. Tout ce qui est omis
— une couleur, un drapeau, une capture entière — garde la valeur de la
palette par défaut : un thème n'a besoin de dire que ce qu'il change.
Les règles de secours sont celles de la configuration : un thème qui ne
se laisse pas analyser retombe sur le thème par défaut avec un seul
avertissement et n'est jamais écrasé, et les clés inconnues survivent.
Les fichiers de thème sont lus au lancement et au changement de
sélection.
En importer un depuis un autre éditeur
Textchum → Importer un thème récupère les couleurs de VS Code ou de
TextMate. Choisissez un fichier de thème, ou un dossier qui en contient
plusieurs — un répertoire d'extension VS Code (son package.json dit
ce qu'il apporte) ou un bundle TextMate (ses thèmes sont dans
Themes/). Tout ce qui s'y trouve est importé, et le premier est mis.
Ce qui a été importé, et ce qui n'est pas passé, est dit dans la zone de
notifications à droite de la barre d'état ; un clic y montre le tout.
Les deux éditeurs décrivent la couleur par portée TextMate ;
importer revient donc à traduire les portées vers les noms de capture
de Textchum : entity.name.function devient function,
keyword.control.loop devient repeat. Les portées s'arrêtent là où
les captures continuent — aucun thème ne colore if autrement que
while — alors une capture que la source n'a jamais nommée prend la
couleur de celle dont elle est un cas particulier, dans les deux sens :
un thème qui dit keyword colore toutes les sortes de mot-clé, et un
qui ne dit que constant.numeric colore toute la famille des
constantes.
Deux choses à savoir avant de trouver les couleurs fausses :
- Un thème remplit une apparence. Les deux éditeurs écrivent un thème pour un fond clair ou pour un fond sombre ; ceux de Textchum portent les deux. L'import remplit le côté que la source déclare et laisse l'autre à la palette par défaut, en disant lequel il a rempli. Importer un thème sombre alors que l'éditeur est en apparence claire ne change rien de visible.
- Les portées sans destination sont nommées. Tout ce que la source a coloré et à quoi aucune capture ne répond est listé à la fin. Ces couleurs restent inutilisées.
Le résultat est un fichier de thème ordinaire dans le dossier des thèmes, modifiable comme un autre.
Icônes de fichier
L'arborescence dessine une icône par ligne. Sans pack, c'est celle que
le bureau propose pour le type du fichier, qui distingue Python de
Markdown et ne va guère plus loin — et n'a jamais entendu parler d'un
fichier nommé Dockerfile.
Réglages → Général → Icônes de fichier accepte un pack d'icônes VS Code. Les packs déjà vus sont sur la liste, séparés entre ceux importés ici et ceux ouverts là où ils sont ; System icons est le retour en arrière.
Import… copie le pack dans le dossier propre à Textchum —
~/Library/Application Support/Textchum/icons/ sur macOS,
~/.local/share/textchum/icons/ sur Linux — déplacer ou supprimer
l'original n'emporte donc pas les icônes. Open… pointe vers un pack
là où il est et s'en souvient, ce qui convient à un pack que vous
maintenez vous-même. Les deux acceptent le fichier JSON du thème
d'icônes ou le dossier d'extension qui le contient (son package.json
dit lequel). Un pack déposé à la main dans le dossier de Textchum est
listé aussi — un dossier d'extension, ou le fichier JSON du thème seul
— et l'icône du fichier dans la barre de titre suit le pack, comme
l'arbre. Delete supprime un pack importé ; un pack ouvert
ailleurs appartient à qui l'a mis là, il ne peut donc que sortir de la
liste.
Le choix est un chemin dans config.json, et les packs ouverts
ailleurs sont retenus à côté :
{
"icon_pack": "~/packs/material-icon-theme/dist/material-icons.json",
"icon_packs": ["~/packs/material-icon-theme/dist/material-icons.json"]
}
Un pack dont le dossier a disparu quitte la liste au lieu d'y rester pour échouer une fois choisi. Un pack illisible est signalé une fois et l'arborescence garde les icônes du système.
La recherche suit celle de VS Code, du plus précis au moins précis :
- Le nom entier du fichier (
Dockerfile,cargo.toml), en minuscules. - L'extension la plus longue qui corresponde :
component.test.tsessaietest.tsavantts. - Le langage que Textchum a retenu pour le fichier — c'est aussi par là qu'un langage fixé à la main dans Propriétés du fichier atteint l'icône.
- La valeur par défaut du pack lui-même.
La section light d'un pack remplace n'importe laquelle de ces
réponses sur fond clair, une recherche à la fois : un pack qui n'en
redessine qu'une poignée garde le reste.
Deux choses restent dehors. Les icônes de dossier : l'arborescence
dessine les siennes. Les icônes tracées avec une police — les
définitions fontCharacter qu'utilisent Seti et ses descendants —
demandent la police installée et un texte là où va une image ; un pack
qui n'a que cela est refusé avec cette raison, plutôt que chargé pour
ne rien dessiner.
Projets
L'onglet Projects décide où un projet commence et finit — la frontière selon laquelle le navigateur regroupe et sur laquelle le pool de serveurs de langage indexe ses instances. Chaque interrupteur existe en deux exemplaires : comme valeur par défaut pour tous les projets, et par racine de projet. Une ligne ajoutée avec le champ de chemin (qui complète les noms de répertoire pendant la frappe et porte un bouton Browse…) remplace les valeurs par défaut pour cette racine seulement.
- Manifest projects — normalement le dépôt le plus externe
l'emporte : ouvrir un fichier n'importe où dans un dépôt fait du dépôt
le projet, quel que soit le nombre de
Cargo.tomloupyproject.tomlentre les deux. L'activer redécoupe une racine aux manifestes de langage, si bien que les modules imbriqués redeviennent des projets à part entière. - Recursive config — fait que les entrées de serveur de langage d'une racine s'appliquent aux projets imbriqués qu'elle contient, l'ancêtre le plus proche d'abord, et que le serveur de la racine s'en occupe : un projet imbriqué sans entrée propre est servi par l'instance qui tourne à la racine, laquelle voit tout l'arbre. Utile pour les monorepos : une configuration, et un serveur, en haut ; beaucoup de projets en dessous. L'onglet Language Servers montre le même interrupteur à côté de chaque entrée de projet : Nested projects too.
- Separate nested servers — sous recursive config, donne à chaque
projet imbriqué un serveur à lui : la ligne de commande de la racine,
lancée dans le projet imbriqué,
{project}le désignant lui. Pour des membres qui portent chacun leur environnement — un.venvpar paquet — là où un serveur en haut n'en verrait aucun. A server each, dans l'onglet Language Servers. - Ctags fallback — répond à Aller à la Définition depuis un index Universal Ctags quand aucun serveur de langage n'est disponible ; voir serveurs de langage.
Dans le fichier, tout cela vit dans une section workspace :
{
"workspace": {
"manifest_projects": false,
"recursive_config": false,
"separate_nested_servers": false,
"ctags_fallback": false,
"projects": {
"/Users/you/code/monorepo": {
"manifest_projects": true,
"recursive_config": true
}
}
}
}
Préprocesseurs de sauvegarde
Les formateurs et correcteurs peuvent s'exécuter automatiquement avant
chaque sauvegarde, par langage — pour tous les projets ou pour une
racine précise, exactement comme les serveurs de langage. Chaque entrée
est une chaîne : une commande par ligne, dans l'ordre, où chaque
commande lit le document sur l'entrée standard et réécrit le document
entier sur la sortie standard (la convention - que suivent presque
tous les formateurs). Si un maillon échoue — code de sortie non nul,
sortie vide, ou plus de dix secondes sans répondre — rien n'est
appliqué, l'erreur (avec le stderr de l'outil) s'affiche, et la
sauvegarde demande s'il faut continuer sans traitement. On peut
répondre à cette question une fois pour toutes : When a chain fails,
save anyway (Réglages ▸ Preprocessors ; "on_failure": "save" dans
la section ci-dessous) écrit le document tel quel et le dit un instant
dans la barre d'état, les mots de l'outil allant dans le journal — pour
qui répond toujours oui.
{
"preprocessors": {
"defaults": {
"python": ["ruff check --fix -", "black -"],
"go": ["gofmt"]
},
"projects": {
"/work/site": { "javascript": ["prettier --stdin-filepath {filename}"] }
}
}
}
{path} et {filename} n'importe où dans une commande se développent
en le chemin absolu du document et son nom — pour les outils qui lisent
stdin mais déduisent leur comportement du nom, comme le
--stdin-filepath de Prettier. Un document sans titre offre Untitled
plus l'extension de son langage.
{edition} se développe en l'édition Rust du crate auquel appartient
le document : celle du Cargo.toml le plus proche, ou celle de son
workspace quand le paquet en hérite. rustfmt, lisant l'entrée
standard, ne voit jamais le manifeste et formate en 2015 si on ne lui
dit rien ; rustfmt --edition {edition} est donc la façon dont une
chaîne formate comme le ferait cargo fmt. Un fichier hors de tout
crate reçoit 2021.
Une entrée de projet remplace la chaîne par défaut pour ce langage,
elle ne s'y ajoute jamais. La fenêtre Réglages édite cette même section
sous Serveurs de langage, et Édition ▸ Lancer les préprocesseurs
(⌃⌥⌘F, nom d'action runPreprocessors) lance la chaîne à la demande
sans sauvegarder — formater avec vos outils plutôt qu'avec le
formateur du serveur. Le résultat arrive en une seule édition, donc ⌘Z
l'annule.
Le maillon @format n'est pas un programme : il demande au serveur de
langage du document de formater, et l'enregistrement attend la réponse
jusqu'à deux secondes. C'est ainsi qu'un formateur qui a besoin des
réglages propres au projet les obtient — rust-analyzer formate avec
l'édition du crate, gopls avec ses options configurées — et il peut
se placer n'importe où dans une chaîne, avant ou après des commandes. Un
serveur qui ne peut pas formater (aucun en marche, aucun qui formate,
aucun qui ait répondu à temps) ne fait pas échouer la chaîne : le texte
continue tel quel et la zone de notifications dit pourquoi.
Outils connus
Personne ne devrait avoir à chercher que black veut un tiret final et prettier un nom de fichier. Le menu Known tools — à côté de chaque chaîne dans Settings ▸ Preprocessors, et dans le formulaire qui en ajoute une — liste les outils dont l'éditeur connaît la ligne de commande pour le langage en question, marque ceux qui sont installés, et écrit la ligne quand on en choisit un. Un outil qui n'est pas installé est proposé quand même, avec la commande qui l'installe. Known servers fait de même pour la commande du serveur dans Language Servers.
| Outil | Langages | La ligne qu'il écrit |
|---|---|---|
| rustfmt | rust | rustfmt --edition {edition} |
| gofmt | go | gofmt |
| goimports | go | goimports |
| gofumpt | go | gofumpt |
| golines | go | golines |
| ruff format | python | ruff format --stdin-filename {path} - |
| ruff check --fix | python | ruff check --fix --exit-zero --no-cache --stdin-filename {path} - |
| ruff (imports) | python | ruff check --select I --fix --exit-zero --no-cache --stdin-filename {path} - |
| black | python | black --quiet --stdin-filename {path} - |
| isort | python | isort --stdout --filename {path} - |
| autopep8 | python | autopep8 - |
| yapf | python | yapf --quiet |
| autoflake | python | autoflake --remove-all-unused-imports --stdin-display-name {path} - |
| usort | python | usort format - |
| prettier | javascript, typescript, tsx, css, html, json, yaml, markdown | prettier --stdin-filepath {path} |
| prettierd | javascript, typescript, tsx, css, html, json, yaml, markdown | prettierd {path} |
| biome | javascript, typescript, tsx, json, css | biome format --stdin-file-path {path} |
| dprint | javascript, typescript, tsx, json, markdown, toml | dprint fmt --stdin {path} |
| eslint_d | javascript, typescript, tsx | eslint_d --fix-to-stdout --stdin --stdin-filename {path} |
| clang-format | c, cpp, java, csharp | clang-format --assume-filename={path} |
| astyle | c, cpp | astyle |
| shfmt | bash | shfmt -filename {path} |
| beautysh | bash | beautysh - |
| stylua | lua | stylua --search-parent-directories --stdin-filepath {path} - |
| zig fmt | zig | zig fmt --stdin |
| terraform fmt | hcl | terraform fmt - |
| tofu fmt | hcl | tofu fmt - |
| packer fmt | hcl | packer fmt - |
| nomad fmt | hcl | nomad fmt - |
| jq | json | jq . |
| fixjson | json | fixjson |
| taplo | toml | taplo format - |
| yamlfmt | yaml | yamlfmt - |
| mdformat | markdown | mdformat - |
| xmllint | xml | xmllint --format - |
| sql-formatter | sql | sql-formatter |
| sqlfmt | sql | sqlfmt - |
| pg_format | sql | pg_format |
| swiftformat | swift | swiftformat --stdinpath {path} |
| rubocop | ruby | rubocop --auto-correct-all --fail-level fatal --stderr --force-exclusion --stdin {path} |
| standardrb | ruby | standardrb --fix --fail-level fatal --stderr --stdin {path} |
| nixfmt | nix | nixfmt |
| alejandra | nix | alejandra --quiet - |
| nixpkgs-fmt | nix | nixpkgs-fmt |
| ormolu | haskell | ormolu --stdin-input-file {path} |
| fourmolu | haskell | fourmolu --stdin-input-file {path} |
| stylish-haskell | haskell | stylish-haskell |
| ocamlformat | ocaml | ocamlformat --enable-outside-detected-project --name {path} - |
| ocp-indent | ocaml | ocp-indent |
| mix format | elixir | mix format --stdin-filename {path} - |
| scalafmt | scala | scalafmt --stdin |
| google-java-format | java | google-java-format - |
| cmake-format | cmake | cmake-format - |
| gersemi | cmake | gersemi - |
Une ligne est du texte ordinaire une fois écrite : on peut lui ajouter des options, réordonner la chaîne ou la supprimer, comme pour toute autre. La table est testée contre ceux de ces outils que possède la machine qui lance les tests.
Pas encore là : un outil hors de cette table doit toujours être tapé, et les outils qui ne font que réécrire les fichiers sur place, au lieu de travailler sur l'entrée standard, ne sont pas listés.
Profils de langage
Un langage est rarement un seul outil ; Settings ▸ Presets propose donc les plus courants comme un ensemble : le serveur, les réglages avec lesquels il doit tourner, et la chaîne de préprocesseurs de sauvegarde.
| Profil | Serveur | Réglages | Avant chaque enregistrement |
|---|---|---|---|
| Rust | rust-analyzer | vérifie avec clippy | rustfmt --edition {edition} |
| Python | pyright, avec ruff à côté | — | ruff check --fix …, puis ruff format |
| Go | gopls | staticcheck, sa coloration des noms, et ses types inférés et noms de paramètres en indications | goimports |
| TypeScript et JavaScript | typescript-language-server | — | prettier --stdin-filepath {path} |
| C et C++ | clangd | — | clang-format --assume-filename={path} |
Apply écrit des entrées ordinaires — lsp.defaults, lsp.settings
et preprocessors.defaults — et redémarre les serveurs. Rien ne retient
qu'un profil a servi : ce qu'il a écrit est à vous, à modifier dans
Language Servers et Preprocessors, et le bouton affiche Applied
tant que la configuration dit encore tout ce que le profil écrirait.
Chaque profil liste les programmes qu'il lance et si chacun a été
trouvé dans PATH ; celui qui manque montre la commande qui l'installe.
L'éditeur n'installe rien lui-même.
Pas encore là : un profil s'applique à tous les projets (les entrées qu'il écrit peuvent être déplacées sous un projet à la main). Et le profil TypeScript ne configure pas eslint comme second serveur, car le serveur d'eslint a besoin de la configuration propre au projet pour dire quoi que ce soit.
Correction orthographique
La prose passe par le correcteur du système — les dictionnaires que partagent toutes les apps du Mac — restreint à là où la prose vit vraiment : les commentaires dans le code, et le document entier en Markdown, dans les messages de commit git et le texte brut. Les identifiants et les littéraux de chaîne ne sont jamais signalés. Les fautes portent une teinte violette, distincte du rouge/orange/bleu des diagnostics.
Choisissez la langue dans Réglages ▸ Général ▸ « Spell check prose » —
Désactivé (le défaut), automatique selon le contenu, ou un dictionnaire
précis — ou réglez editor.spell à la main : "auto" ou un
identifiant comme "fr" ou "en_US". Les dictionnaires disponibles
sont ceux activés dans Réglages Système ▸ Clavier ▸ Saisie de texte.
{ "editor": { "spell": "auto" } }
Plusieurs dictionnaires peuvent s'appliquer à la fois : nommez-les séparés par des virgules. Un mot que l'un d'eux connaît est bien orthographié, ce dont a besoin un texte qui change de langue au milieu d'un paragraphe :
{ "editor": { "spell": "en_US, fr_FR" } }
editor.spell_words est votre propre liste : noms de projet, sigles, et
tout ce qu'aucun dictionnaire ne fournit. Un clic droit sur un mot
signalé propose les suggestions, Ajouter au dictionnaire, qui écrit
le mot ici, et Ignorer, qui l'accepte jusqu'à la fermeture de
l'éditeur. La liste s'édite aussi dans les préférences.
{ "editor": { "spell_words": ["SBX", "Textchum"] } }
Sous Linux les mêmes réglages passent par hunspell : installez
hunspell plus un paquet de dictionnaire (hunspell-fr,
hunspell-en-us, …) et les marques apparaissent ; "auto" suit
$LANG, et les dictionnaires que hunspell trouve sont listés à côté du
champ dans les préférences.
Enregistrement automatique
Désactivé par défaut. editor.autosave est un nombre de secondes ; le
compte repart à chaque frappe, donc l'enregistrement a lieu une fois la
saisie terminée et non au milieu d'une phrase.
{ "editor": { "autosave": 30 } }
Deux choses qu'il ne fait délibérément pas. Il n'enregistre jamais un document sans nom : il n'y a nulle part où le mettre, et lui en inventer un n'appartient pas à l'éditeur. Et il n'exécute pas les préprocesseurs d'enregistrement : un formateur qui reflue la ligne que vous êtes en train d'écrire ne rend pas service, cela reste donc l'affaire des enregistrements explicites.
Raccourcis clavier
Réglages ▸ Clavier les tient : un profil, et chaque commande avec le raccourci auquel elle répond, modifiable sur place.
Profils. On arrive d'un autre éditeur avec ses raccourcis dans les
doigts : les trois pour lesquels ces éditeurs sont connus sont donc
fournis avec la compilation — Visual Studio Code, Sublime Text et
IntelliJ IDEA. Un profil nomme les commandes qu'il déplace et laisse le
reste tranquille : en choisir un change ce pour quoi cet éditeur est
connu, et rien d'autre. keys_profile garde le choix ; vide, ce sont
les raccourcis propres à Textchum.
Changer un raccourci par-dessus un profil garde le profil : le
changement est un remplacement, et Reset changes les abandonne
tous. Save as profile transforme ce qui est en vigueur en un profil
à vous — la façon de modifier un préréglage, puisque ceux-là sont
fournis avec la compilation. Les profils enregistrés vivent dans
key_profiles, et un profil qui reprend un nom fourni le remplace.
Une touche répond à une seule commande. Quand un profil ou un
remplacement place une commande sur une touche qu'une autre a par
défaut, cette autre commande la cède et son champ reste vide : un
profil qui met new sur cmd+n veut que Ctrl+N ouvre un onglet, pas
une fenêtre. Sous Linux, où cmd et ctrl sont la même touche, un
profil qui nomme les deux pour une lettre — cmd+g pour findNext,
ctrl+g pour goToLine — laisse la touche au raccourci en cmd, et
l'autre commande garde le sien. La version macOS ne règle pas encore
ces cas : une touche partagée par deux commandes n'y atteint que l'une
d'elles.
Le fichier écrit les remplacements dans une section keys : un objet
de noms d'action vers des spécifications modificateurs+touche,
appliqué par-dessus le profil.
{
"keys": {
"openQuickly": "cmd+p",
"goToBlockEnd": "ctrl+alt+down",
"findInProject": "cmd+shift+g"
}
}
Modificateurs : cmd, shift, alt, ctrl — cmd est Command sur
macOS et Ctrl sur Linux, un profil veut donc dire la même chose des
deux côtés. Touches : un caractère, de f1 à f20, ou
up/down/left/right/return/escape/space/tab/delete.
Parmi les actions : new, open, openQuickly, save, saveAs, showLocalHistory,
close, undo, redo, find, findAndReplace, findNext,
findPrevious, useSelectionForFind, findInProject, searchEverywhere, runCommand, runAtCaret, runAgain, rerunFailedTests, debugAtCaret, toggleBreakpoint, debugContinue, stepOver, stepInto, stepOut, stopDebugging, toggleDebugPanel, stopRun, nextOutputPlace, previousOutputPlace, toggleRunPanel,
jumpToDefinition, goToImplementation, goToTypeDefinition, findReferences, showUsages, codeActions, renameSymbol, formatDocument,
runPreprocessors,
documentOutline, goBack, goForward,
blameLine, goToLine,
goToBlockStart, goToBlockEnd, expandSelection, shrinkSelection, addNextOccurrence, addCaretAbove, addCaretBelow,
toggleNavigator, togglePreview, toggleLineNumbers,
toggleHover, showHover, serverStatus, newWithFormat, revealInTree, reopenClosed,
goToLastEdit, recentFiles, recentPlaces, nextFinding, previousFinding, togglePathDisplay, redraw, commandPalette, settings —
un nom inconnu est journalisé avec la liste complète. Aller au début/à
la fin du bloc (⌃⌥↑/⌃⌥↓ par défaut) saute par-dessus le bloc syntaxique
multiligne le plus interne autour du curseur, grâce à l'arbre qui
alimente déjà la coloration. Le même arbre est derrière Étendre la
sélection (⌥↑, Alt+Haut sous Linux) : chaque appui agrandit la
sélection d'un pas du code — le nom, l'intérieur de ses parenthèses ou
guillemets, les parenthèses avec, l'expression, l'instruction, le bloc,
jusqu'au fichier entier — et Réduire la sélection (⌥↓, Alt+Bas
sous Linux) redescend les mêmes pas. Un fichier qu'aucune grammaire ne
lit grandit par mot, puis par ligne, puis au tout. Réduire ne rend que
ce qu'étendre a pris ; une sélection faite à la souris ou aux flèches
n'a pas de pas à défaire. Et quand un raccourci échappe tout à fait
à la mémoire, la palette de commandes (⇧⌘P) cherche floue n'importe
quelle action de menu par son nom et exécute la sélection.
La fenêtre d'accords
Un raccourci de trois ou quatre touches, il faut s'en souvenir puis l'atteindre. La fenêtre d'accords fait l'inverse : maintenez deux touches de modification, et un petit panneau liste ce qui peut être fait, une touche à côté de chaque commande. Appuyez sur la touche et la commande s'exécute.
Elle est désactivée tant qu'une paire n'est pas choisie dans Réglages ▸
Clavier ▸ Fenêtre d'accords. Le fichier garde ce choix sous
keys_chord :
{ "keys_chord": "ctrl+alt" }
Les paires sont ctrl+alt, alt+cmd, ctrl+cmd, ctrl+shift et
alt+shift ; toute autre valeur laisse la fenêtre désactivée. Ici cmd
est la touche elle-même — Commande sur macOS, Super sur Linux — car une
paire, ce sont deux touches maintenues, pas un raccourci.
La fenêtre s'ouvre quand la paire est maintenue seule depuis un quart de
seconde ; un raccourci qui utilise ces deux mêmes modificateurs n'est
donc ni retardé ni intercepté : sa touche arrive avant. Une fois
ouverte, elle le reste, touches relâchées. Les commandes sont rangées
en groupes — Fichier, Code, Aller, Rechercher,
Présentation — marqués d'un + ; la touche d'un groupe ouvre sa
liste, et les commandes les plus utilisées (la palette de commandes,
le blâme de la ligne, Préférences) sont en haut, sans groupe entre deux. Une touche
qui ne correspond à rien est refusée et la fenêtre reste ; Échap ou un
clic la ferme.
Le menu est le même sur macOS et sur Linux, et il est fixe : ses touches ne peuvent pas encore être changées, et une commande qui n'y figure pas s'atteint par son raccourci ou par la palette de commandes. Sur Linux, le bureau peut garder une paire pour lui — Super seule ouvre souvent la vue d'activités, et Alt+Maj change la disposition du clavier sur certaines configurations —, auquel cas la fenêtre ne la reçoit jamais ; choisissez une autre paire.
Rechargement à chaud
Le fichier est surveillé pendant que Textchum tourne : éditez
config.json ailleurs et le changement s'applique dès qu'il atterrit —
apparence, thème, polices, raccourcis, table des serveurs, tout, y
compris la fenêtre Réglages si elle est ouverte. Les sauvegardes de
l'app elle-même sont reconnues et ignorées, et un fichier qui échoue
momentanément à parser retombe sur les défauts sans être écrasé,
exactement comme au lancement.
Réglages d'éditeur par projet
Une racine de projet peut remplacer la police, sa taille et la largeur
de tabulation pour toutes les fenêtres qu'elle contient — les lignes de
l'onglet Projets portent les trois champs (vide signifie « hériter de
la valeur générale »), et le fichier l'écrit comme un objet editor
sur l'entrée workspace :
{
"workspace": {
"projects": {
"/work/legacy": { "editor": { "tab_width": 8, "font_size": 12 } }
}
}
}
Ajouter un projet
L'onglet Projets liste les racines des documents ouverts : un projet s'ajoute donc en le choisissant, pas en tapant son chemin. Copier les réglages de part d'un projet déjà configuré — ses serveurs, ses commandes d'enregistrement, ses indicateurs et ses réglages d'éditeur, le tout — ce dont a besoin un second service de même disposition. Le même choix figure sur la ligne de chaque projet, pour copier vers un qui existe déjà.
Un champ de remplacement vide montre ce qu'il hérite : une case blanche dit donc ce qui s'applique au lieu de vous laisser aller voir.
Une racine configurée dont le répertoire a disparu est marquée missing, et Remove missing les oublie toutes : plus rien ne correspondra jamais à ces entrées.
Les langages que la compilation ignore
La coloration de Textchum vient de grammaires tree-sitter compilées
dans le binaire. Un langage qu'il ne porte pas se nomme dans
languages, avec la grammaire comme bibliothèque compilée et sa
requête de coloration comme fichier :
{
"languages": {
"kdl": {
"grammar": "~/.local/share/textchum/grammars/libtree-sitter-kdl.dylib",
"highlights": "~/.local/share/textchum/grammars/kdl/highlights.scm",
"extensions": ["kdl"]
}
}
}
aliases, filenames et injections sont facultatifs, symbol
aussi : le constructeur est tree_sitter_<nom> sauf mention contraire,
tirets et points devenant des soulignés. Une grammaire faite pour un
autre tree-sitter est refusée d'après son numéro d'ABI plutôt que
suivie jusqu'au plantage, et un nom que la compilation connaît déjà est
remplacé par celui de la configuration — de quoi corriger une grammaire
dépassée sans attendre une version.
Textchum ▸ Install Grammar… écrit cette entrée pour vous à partir du
dépôt d'une grammaire — un checkout de n'importe quelle grammaire
tree-sitter, ce avec quoi Neovim, Helix et Zed colorent aussi. Il
compile l'analyseur avec le compilateur C du système dans le dossier
grammars du profil, copie les requêtes de coloration et d'injection
à côté, lit le nom et les types de fichier dans le tree-sitter.json
du dépôt et charge le résultat, si bien qu'un fichier de ce type ouvert
dans un onglet est coloré avant que le dialogue ne se ferme. Les
requêtes écrites pour un autre éditeur sont réécrites pour celui-ci :
les noms de capture pour lesquels les thèmes d'ici n'ont pas de couleur
sont rapportés à ceux qu'ils connaissent (keyword.repeat devient
repeat, markup.bold devient text.strong), un prédicat à motif Lua
devient l'expression régulière que tree-sitter évalue, et un prédicat
que l'éditeur ne sait pas évaluer est nommé dans le compte rendu plutôt
que laissé à tout faire correspondre en silence. Sans compilateur C, la
commande dit quoi installer ; un dépôt sans src/parser.c demande
d'abord tree-sitter generate, et le dit. Pas encore là : cloner
depuis une URL — clonez-le, puis choisissez le dossier — et une liste
de ce qui est installé, qui pour l'instant est la section languages
elle-même.
Pour en construire une à la main, depuis le dépôt de la grammaire :
cc -O2 -fPIC -shared -I src -o libtree-sitter-NOM.dylib src/parser.c src/scanner.c
(.so sous Linux, et sans src/scanner.c quand la grammaire n'en a
pas.) Une entrée qui ne charge pas coûte ce langage et rien d'autre :
l'éditeur dit ce qui s'est passé et continue. La section est relue à
chaque changement du fichier : une grammaire ajoutée ou corrigée
pendant que l'éditeur tourne arrive sans redémarrage, et les documents
déjà ouverts sont analysés avec elle — sauf celui dont vous avez choisi
le langage à la main, qui garde votre choix.
La langue de l'interface
L'interface parle anglais, espagnol ou français ; Interface language dans les Préférences en choisit une, et System suit celle de la machine. Le changement s'applique au démarrage suivant.
Les catalogues sont ceux de gettext et vivent dans le cœur : les deux
shells et le cœur disent donc les mêmes choses avec les mêmes mots. La
source est core/textchum-core/i18n/<langue>.po — le format que
parlent les traducteurs et leurs outils — et la compilation en tire le
.mo que lit l'éditeur. Une phrase sans traduction se lit telle
qu'elle est écrite en anglais plutôt que comme une clé manquante.
Un catalogue à soi se met dans le profil, et est lu à la place de celui de la compilation :
msgfmt -o ~/.config/textchum/translations/fr.mo mon-fr.po
~/Library/Application Support/Textchum/translations/<langue>.mo~/.config/textchum/translations/<langue>.mo
Un fichier nommé d'après une langue que la compilation ne porte pas se lit de la même façon : une quatrième langue est un catalogue, pas une version.
Pour travailler les traductions du dépôt, scripts/i18n.sh extrait les
chaînes et les fusionne dans chaque catalogue comme gettext l'entend :
xgettext les trouve, msgmerge reporte l'existant et marque comme
douteux ce dont l'anglais a changé, et msgfmt vérifie le résultat.
make check lance scripts/i18n.sh --check : une chaîne ajoutée sans
traduction casse la compilation au lieu de sortir en anglais.
Registres de projet
Un fichier se souvient de la façon dont il est divisé, de l'endroit que
chaque vue regardait, de ce qui est replié et de ce qu'on lui a dit
qu'il était quand son nom ne le dit pas. Ce sont des données du
fichier : elles vivent avec le projet plutôt que dans config.json, un
registre par racine de projet, en JSON comme le reste.
{
"version": 1,
"root": "/work/engine",
"files": {
"src/parser.rs": {
"views": 2,
"dividers": [0.45],
"folds": [[12, 48]],
"language": "rust",
"places": [{"caret": 812, "scroll": 240.0, "top": 790}]
}
}
}
Les registres sont gardés dans le profil, à côté de la session et des
thèmes : une exécution pointée sur un profil d'essai écrit les siens.
Keep each project's state with the checkout place le registre dans
<root>/.tchum, pour une disposition qui voyage avec le clone ; le
choix est global, car une réponse par projet devrait de toute façon
être notée quelque part de central pour être retrouvée.
Le balayage se fait au démarrage, sur un fil à lui : il oublie les registres des projets qui ne sont plus là et ceux qui n'ont pas été écrits depuis plus longtemps que la fenêtre (90 jours par défaut ; zéro les garde jusqu'à ce qu'on les efface). Forget records at launch l'arrête, et Manage… à côté du dossier des registres liste ce qui existe, ce dont chacun parle et quand il a été écrit, pour les oublier un par un ou d'un coup.
Pas encore là
- Rien pour le moment — notez la prochaine gêne quand elle se présentera.