Aller au contenu

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_pairs dans 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 :

  1. Le nom entier du fichier (Dockerfile, cargo.toml), en minuscules.
  2. L'extension la plus longue qui corresponde : component.test.ts essaie test.ts avant ts.
  3. 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.
  4. 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.toml ou pyproject.toml entre 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 .venv par 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.