turbo-editors/turbo-rustpublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-rust.git
git clone ssh://git@rickub.com/turbo-editors/turbo-rust.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

📦 Turbo Rust 713ea5c · on main · k33g · 10h ago
rust-tools.md · 117 lines · 15.2 KBmarkdown
Blame HistoryOpen raw

Outils Rust — explication

De quoi s'agit-il ?

Un menu Rust dont les commandes viennent d'un fichier TOML, chacune lancée dans une fenêtre terminal, et les fichiers ouverts relus ensuite. Cette page explique pourquoi chacun de ces trois points est ce qu'il est.

Pourquoi la sortie a trois destinations, et une popup par défaut

La première version mettait chaque commande dans une fenêtre terminal, et c'était le mauvais défaut pour quatre des cinq.

Un terminal est la bonne réponse quand le programme est interactif ou long : cargo run sur quelque chose qui lit l'entrée standard doit pouvoir recevoir une réponse, et une compilation qui s'avère durer une minute doit pouvoir être interrompue par Ctrl-C. Ni l'un ni l'autre n'est vrai de cargo clippy --all-targets, qui affiche quatre lignes et s'arrête. Lui donner une fenêtre entière — qu'il faut ensuite fermer, sur un bureau où les fenêtres se recouvrent et sont numérotées — est plus de cérémonie que le résultat n'en mérite.

Une popup est la bonne réponse pour une commande qu'on lance, qu'on lit et qu'on referme. Elle est modale, ce qui est un coût réel et nommé dans le guide : un go build qu'on n'attendait pas lent immobilise l'éditeur jusqu'à sa fin ou jusqu'à Échap. Ce coût a été accepté exprès, parce que l'alternative — un dialogue surgissant trois secondes plus tard — avale ce qu'on était en train de taper à cet instant.

La popup s'ouvre donc immédiatement et se remplit. On voit la progression, rien ne surprend, et Échap la ferme et arrête la commande — le seul moyen d'interrompre quelque chose dont la sortie n'est pas dans un terminal.

Une fenêtre d'édition est la bonne réponse pour une sortie qu'on va éplucher : un long go test -v, un rapport de couverture. C'est un buffer ordinaire, donc Ctrl-F y cherche et Save as le conserve. Elle est remplie à la fin de la commande plutôt qu'au fil de l'eau, parce qu'un buffer qui grandit sous le curseur pendant qu'on y cherche est l'inverse de ce que ce mode vise.

Aucune des trois ne convient à tout, et c'est pourquoi output est dans le fichier et non dans le code. Run en est l'exemple travaillé : c'est la seule commande du fichier de départ qui dit terminal, et le commentaire à côté dit pourquoi.

Pourquoi la fenêtre terminal reste

L'éditeur en avait déjà une — un vrai pseudo-terminal avec émulateur VT, construit pour les fenêtres F8 — donc output = "terminal" coûte un champ dans ses options et offre gratuitement les couleurs, la pagination, Ctrl-C, l'entrée clavier et l'historique, parce que ce sont les mêmes mécanismes que ceux de tous les autres terminaux.

La fenêtre reste après la fin de la commande, et c'est le but : la sortie est ce qu'on a demandé, et une fenêtre qui disparaîtrait avec elle serait inutile.

Cela a demandé un correctif à part. Une vue terminal consommait toute touche qu'on lui donnait et l'écrivait au shell ; une fois le shell parti, l'écriture échouait en silence et la touche était consommée quand même — Ctrl-W ne pouvait donc jamais fermer une fenêtre terminée, et la souris était la seule issue. Une vue terminée ne prend plus que les touches de défilement et laisse passer le reste vers l'éditeur.

Pourquoi le code de sortie est toujours dans le titre

cargo build qui réussit n'affiche rien du tout. Une popup au corps vide et au titre neutre est indistinguable d'une popup dont la commande n'a pas démarré, et le lecteur en est réduit à deviner la seule chose qu'il voulait savoir.

Le titre porte donc le verdict — — ok ou — exit 1 — et un corps vide affiche (no output) une fois la commande terminée. Pendant qu'elle tourne, le corps reste vide : « (no output) » est un verdict, et une commande en cours n'y est pas parvenue.

Pourquoi les commandes sont dans un fichier

Cinq commandes codées en dur auraient répondu à la demande. Elles auraient aussi été fausses en une semaine.

go vet est le linter par défaut parce qu'il fait partie de la chaîne d'outils et n'est jamais absent — mais bien des projets veulent golangci-lint. cargo run suppose le paquet main à la racine. Un projet avec un Makefile veut make check. Un projet qui génère du code veut go generate ./... avant tout. Rien de cela n'est connaissable d'ici, et tout cela fait une ligne dans un fichier.

Les cinq sont donc des défauts, pas du code : c'est le contenu du fichier de départ qu'écrit Rust ▸ Create tools file, et en changer une consiste à éditer un fichier plutôt qu'à recompiler un éditeur. Le fichier est relu à chaque ouverture du menu, pour la même raison que le menu Snippets : une modification doit prendre effet aussitôt, et le fichier est souvent ouvert dans la fenêtre derrière le menu.

Les commandes passent par sh -ccmd.exe /S /C sous Windows — plutôt que d'être découpées en argv ici. Le fichier est celui de l'utilisateur, donc les tubes, les globs et && sont des fonctionnalités plutôt que des dangers, et une entrée peut être cargo fmt && cargo clippy --all-targets && cargo test. Découper un argv supposerait d'inventer des règles de citation pour une chaîne écrite à la main.

Pourquoi il n'y a pas de fichier d'outils utilisateur

Les snippets sont lus depuis deux fichiers — le vôtre et celui du projet — parce que vos snippets sont vos habitudes et doivent vous suivre.

Les outils ne sont pas ainsi. Ils appartiennent à la chaîne de compilation d'un projet : un fichier d'outils global proposerait cargo build dans un dépôt Rust et cargo test dans un dépôt Go. Le fichier est par projet, et c'est toute la règle.

Pourquoi un outil peut nommer son propre menu

Un menu nommé Rust contenant docker compose up ment sur ce qu'il est. Le premier fichier d'outils que l'on écrit déborde de Rust, parce que les commandes d'un projet ne parlent pas toutes du langage dans lequel il est écrit : conteneurs, bases de données, déploiements, une cible de Makefile ajoutée en 2019.

Deux formes ont été envisagées. Un second menu fixe nommé Tools — tout ce qui est Rust dans Rust, le reste dans Tools — c'est une clé de plus dans le format et aucun problème de nommage, mais cela ne fait que déplacer le mensonge : un menu Tools contenant docker compose up, psql et un script de déploiement est tout aussi indifférencié, et dès qu'il y a dix entrées personne n'en retrouve une. Et un second fichier, menus.toml, garde le fichier d'outils simple au prix de deux fichiers qui doivent s'accorder sur les outils qui existent.

Le menu est donc un nom libre porté par l'outil, dans l'unique fichier : menu = "Docker". Un nom que rien d'autre n'emploie crée le menu ; omettre la clé signifie Rust. Il n'y a pas de liste de noms autorisés, parce qu'une liste serait la liste des projets de quelqu'un d'autre.

Rust reste fixe sur la barre plutôt que de devenir un nom parmi d'autres venu du fichier. Rust ▸ Create tools file doit être atteignable dans un projet qui n'a aucun fichier d'outils — c'est précisément le projet qui en a besoin — et un menu qui n'existe qu'une fois le fichier créé ne peut pas proposer de créer le fichier.

Pourquoi la touche d'accès n'appartient pas au fichier

L'auteur d'un fichier d'outils ne peut pas savoir quelles lettres sont libres. Il voit File, Edit, Search, Run, Options, Window, Snippets, Rust et Help sur la barre, mais seulement en comptant les soulignements, et un projet partagé à plusieurs dépendrait alors du fait que personne n'ajoute un menu qui entre en collision.

Les collisions sont ici silencieuses, et c'est ce qui vaut qu'on se prémunisse contre elles. La barre répond au premier menu dont la touche correspond ; un second menu revendiquant la même lettre n'est pas une erreur et se dessine normalement — il ne s'ouvre simplement jamais. Le piège s'est déjà refermé une fois dans cet éditeur : Snippets et Search voulaient tous deux le S, Snippets était celui qu'on ne pouvait pas ouvrir, et tous les tests passaient. Le correctif d'alors fut de déplacer Snippets sur N à la main. Laisser un fichier nommer des menus en ferait un danger permanent plutôt qu'une erreur isolée, donc l'attribution revient à l'éditeur : la première lettre du nom que rien d'autre ne revendique.

Les tildes écrits dans le nom sont honorés quand la lettre est libre, et écartés sans bruit sinon. Refuser le fichier était l'alternative, et elle est pire : la collision dépend des menus qui existent, donc un fichier d'outils qui marchait cesserait de marcher le jour où une version de l'éditeur ajoute un menu. Entre un menu sur une lettre que vous n'avez pas demandée et un menu que vous ne pouvez pas ouvrir, la première est la moindre perte.

Quand toutes les lettres d'un nom sont prises, le menu n'a pas de touche d'accès du tout. F10, les flèches et la souris l'atteignent encore, et l'alternative — aller chercher une lettre qui n'est pas dans le nom — mettrait un soulignement sous rien.

Pourquoi la barre est reconstruite depuis un stat

Menu.OnOpen remplit les entrées d'un menu juste avant qu'il ne se déroule, et c'est ainsi que les menus Rust et Snippets suivent leurs fichiers sans redémarrage. Cela ne suffit pas ici : l'ensemble des menus appartient à la barre et non à un menu, et ajouter menu = "Docker" au fichier doit poser Docker sur la barre.

Lire et analyser le fichier à chaque tour de la boucle d'événements y parviendrait, et ferait aussi ce travail pour rien à chaque frappe dans un fichier que personne n'a modifié. La barre porte donc la taille et la date de modification du fichier d'outils dont elle est issue, et un stat par tour décide s'il faut reconstruire. Modifier le fichier dans la fenêtre devant soi, l'enregistrer et voir la barre changer, c'est le cas visé.

Pourquoi les fichiers ouverts sont relus, et seulement certains

Format est le premier item du menu et il réécrit les fichiers sur le disque — y compris celui qu'on regarde. Sans rien de plus, l'éditeur resterait assis sur une copie périmée, et le F2 suivant réécrirait votre version non formatée par-dessus le travail de gofmt. Ce n'est pas une aspérité : c'est la fonctionnalité qui se défait toute seule, en silence.

À la fin d'une commande, l'éditeur relit donc chaque fichier ouvert. La partie intéressante est ceux qu'il refuse de toucher.

Un fichier ayant des modifications non enregistrées est laissé tel quel, et la barre d'état dit combien ont été ignorés. Le recharger jetterait un travail que l'utilisateur n'a pas enregistré, ce qu'aucune commodité ne justifie. Et le conflit est réel : le formateur et la modification non enregistrée ne sont pas d'accord sur ce que le fichier doit dire, et l'éditeur n'est pas en position de trancher. Le nommer et s'arrêter est l'issue honnête — l'utilisateur peut enregistrer et relancer, ou continuer à éditer et formater plus tard.

Deux décisions plus petites à l'intérieur :

  • Le curseur reste où il était, borné à ce que le fichier contient désormais. Un formateur déplace les lignes ; remettre le curseur en haut ferait perdre sa place au lecteur pour rien.
  • L'historique d'annulation est jeté. Annuler au-delà d'un rechargement restaurerait un texte que le fichier n'a plus, ce qui est pire que de ne pas pouvoir annuler.

Pourquoi le rechargement a lieu sur la boucle d'événements

La fin de la commande est remarquée par la goroutine qui lit le terminal, laquelle ne peut toucher ni un buffer ni le bureau. Elle positionne donc un drapeau, et le rechargement se fait en tête du tour suivant de la boucle.

C'est la quatrième chose construite ainsi dans cet éditeur — l'annonce au serveur de langage, les redessins de terminal, l'échéance d'autosave, et maintenant ceci. La règle qu'elles partagent mérite d'être énoncée une fois de plus : le réveil peut être perdu, l'état ne doit pas l'être. PostEvent jette ce qui ne tient pas dans sa file, donc tout ce qui dépend de l'arrivée d'un message est un bug qui attend un moment de charge. Un drapeau que la boucle vérifie elle-même ne peut pas disparaître.

Pourquoi une commande peut demander une valeur, et pourquoi en doubles accolades

go mod init a besoin d'un chemin de module. cargo new a besoin d'un nom de caisse. go test -run a besoin d'un motif. Aucun de ces éléments ne peut vivre dans le fichier d'outils, parce que la réponse change à chaque fois — et un outil qui ne peut pas demander est un outil qu'il faut éditer avant chaque usage, ce qui n'est pas un outil.

Un {{libellé}} dans une commande est donc une valeur que l'éditeur demande d'abord, dans une boîte portant le nom de l'outil.

Une seule accolade était l'écriture évidente, et elle est fausse. awk '{print $1}' et find . -exec rm {} + sont des choses ordinaires à mettre dans un fichier d'outils, et lire la première comme un libellé transforme une commande qui marche en une boîte demandant « print $1 ». Les doubles accolades n'entrent en collision presque avec rien, et la seule construction avec laquelle elles le font — un bloc imbriqué en awk — est assez rare pour être signalée plutôt que contournée par la conception.

La valeur est protégée par défaut, parce que l'inverse échoue en silence. Un chemin contenant une espace, substitué tel quel, devient deux arguments et la commande parle d'un fichier qui n'existe pas. La protection fait marcher ce cas et rend l'autre — « ajoute ces trois options à la fin » — impossible ; c'est pourquoi un ... dans les accolades demande la valeur telle quelle. Deux comportements, tous deux documentés, plutôt qu'un seul faux une fois sur deux.

Rien n'est retenu sur le disque. La boîte repart de ce qui avait été tapé, pour la session. L'écrire dans le répertoire propre au projet a été envisagé puis rejeté : ce répertoire contient ce que le projet a décidé, et un filtre tapé en poursuivant un test n'en fait pas partie. Ce serait aussi la première chose qui y changerait sans que personne ne modifie un fichier.

Un fichier illisible est refusé à la lecture, pas au moment où l'outil est choisi. Un {{ non fermé atteignant le shell donne une commande qui échoue avec des accolades dedans, ce qui ne nomme ni l'outil ni le fichier ; refuser au chargement nomme les deux. C'est la règle que suit déjà une valeur d'output inconnue.

La boîte est refusée quand elle ne tient pas. Un outil demandant plus de valeurs que le terminal n'a de lignes donnerait une boîte dont le bouton OK est sous le bas de l'écran — à laquelle on ne peut répondre que par Échap, qui annule. Dire « celui-ci demande douze valeurs et neuf tiennent » n'est pire que rien que si l'on préfère l'apprendre en essayant.

Liens avec le reste

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# Outils Rust — explication

## De quoi s'agit-il ?

Un menu **Rust** dont les commandes viennent d'un fichier TOML, chacune lancée dans une fenêtre terminal, et les fichiers ouverts relus ensuite. Cette page explique pourquoi chacun de ces trois points est ce qu'il est.

## Pourquoi la sortie a trois destinations, et une popup par défaut

La première version mettait chaque commande dans une fenêtre terminal, et c'était le mauvais défaut pour quatre des cinq.

Un terminal est la bonne réponse quand le programme est *interactif ou long* : `cargo run` sur quelque chose qui lit l'entrée standard doit pouvoir recevoir une réponse, et une compilation qui s'avère durer une minute doit pouvoir être interrompue par `Ctrl-C`. Ni l'un ni l'autre n'est vrai de `cargo clippy --all-targets`, qui affiche quatre lignes et s'arrête. Lui donner une fenêtre entière — qu'il faut ensuite fermer, sur un bureau où les fenêtres se recouvrent et sont numérotées — est plus de cérémonie que le résultat n'en mérite.

Une popup est la bonne réponse pour une commande qu'on lance, qu'on lit et qu'on referme. Elle est modale, ce qui est un coût réel et nommé dans le [guide](../how-to/run-cargo-commands.md) : un `go build` qu'on n'attendait pas lent immobilise l'éditeur jusqu'à sa fin ou jusqu'à `Échap`. Ce coût a été accepté exprès, parce que l'alternative — un dialogue surgissant trois secondes plus tard — avale ce qu'on était en train de taper à cet instant.

La popup s'ouvre donc **immédiatement et se remplit**. On voit la progression, rien ne surprend, et `Échap` la ferme et arrête la commande — le seul moyen d'interrompre quelque chose dont la sortie n'est pas dans un terminal.

Une fenêtre d'édition est la bonne réponse pour une sortie qu'on va éplucher : un long `go test -v`, un rapport de couverture. C'est un buffer ordinaire, donc `Ctrl-F` y cherche et `Save as` le conserve. Elle est remplie à la fin de la commande plutôt qu'au fil de l'eau, parce qu'un buffer qui grandit sous le curseur pendant qu'on y cherche est l'inverse de ce que ce mode vise.

Aucune des trois ne convient à tout, et c'est pourquoi `output` est dans le fichier et non dans le code. `Run` en est l'exemple travaillé : c'est la seule commande du fichier de départ qui dit `terminal`, et le commentaire à côté dit pourquoi.

## Pourquoi la fenêtre terminal reste

L'éditeur en avait déjà une — un vrai pseudo-terminal avec émulateur VT, construit pour les fenêtres `F8` — donc `output = "terminal"` coûte un champ dans ses options et offre gratuitement les couleurs, la pagination, `Ctrl-C`, l'entrée clavier et l'historique, parce que ce sont les mêmes mécanismes que ceux de tous les autres terminaux.

La fenêtre reste après la fin de la commande, et c'est le but : la sortie est ce qu'on a demandé, et une fenêtre qui disparaîtrait avec elle serait inutile.

Cela a demandé un correctif à part. Une vue terminal consommait toute touche qu'on lui donnait et l'écrivait au shell ; une fois le shell parti, l'écriture échouait en silence et la touche était consommée quand même — `Ctrl-W` ne pouvait donc jamais fermer une fenêtre terminée, et la souris était la seule issue. Une vue terminée ne prend plus que les touches de défilement et laisse passer le reste vers l'éditeur.

## Pourquoi le code de sortie est toujours dans le titre

`cargo build` qui réussit n'affiche rien du tout. Une popup au corps vide et au titre neutre est indistinguable d'une popup dont la commande n'a pas démarré, et le lecteur en est réduit à deviner la seule chose qu'il voulait savoir.

Le titre porte donc le verdict — `— ok` ou `— exit 1` — et un corps vide affiche `(no output)` une fois la commande terminée. Pendant qu'elle tourne, le corps reste vide : « (no output) » est un verdict, et une commande en cours n'y est pas parvenue.

## Pourquoi les commandes sont dans un fichier

Cinq commandes codées en dur auraient répondu à la demande. Elles auraient aussi été fausses en une semaine.

`go vet` est le linter par défaut parce qu'il fait partie de la chaîne d'outils et n'est jamais absent — mais bien des projets veulent `golangci-lint`. `cargo run` suppose le paquet main à la racine. Un projet avec un `Makefile` veut `make check`. Un projet qui génère du code veut `go generate ./...` avant tout. Rien de cela n'est connaissable d'ici, et tout cela fait une ligne dans un fichier.

Les cinq sont donc des **défauts, pas du code** : c'est le contenu du fichier de départ qu'écrit **Rust ▸ Create tools file**, et en changer une consiste à éditer un fichier plutôt qu'à recompiler un éditeur. Le fichier est relu à chaque ouverture du menu, pour la même raison que le menu Snippets : une modification doit prendre effet aussitôt, et le fichier est souvent ouvert dans la fenêtre derrière le menu.

Les commandes passent par `sh -c``cmd.exe /S /C` sous Windows — plutôt que d'être découpées en argv ici. Le fichier est celui de l'utilisateur, donc les tubes, les globs et `&&` sont des fonctionnalités plutôt que des dangers, et une entrée peut être `cargo fmt && cargo clippy --all-targets && cargo test`. Découper un argv supposerait d'inventer des règles de citation pour une chaîne écrite à la main.

## Pourquoi il n'y a pas de fichier d'outils utilisateur

Les snippets sont lus depuis deux fichiers — le vôtre et celui du projet — parce que vos snippets sont vos habitudes et doivent vous suivre.

Les outils ne sont pas ainsi. Ils appartiennent à la chaîne de compilation d'un projet : un fichier d'outils global proposerait `cargo build` dans un dépôt Rust et `cargo test` dans un dépôt Go. Le fichier est par projet, et c'est toute la règle.

## Pourquoi un outil peut nommer son propre menu

Un menu nommé **Rust** contenant `docker compose up` ment sur ce qu'il est. Le premier fichier d'outils que l'on écrit déborde de Rust, parce que les commandes d'un projet ne parlent pas toutes du langage dans lequel il est écrit : conteneurs, bases de données, déploiements, une cible de `Makefile` ajoutée en 2019.

Deux formes ont été envisagées. Un **second menu fixe** nommé Tools — tout ce qui est Rust dans Rust, le reste dans Tools — c'est une clé de plus dans le format et aucun problème de nommage, mais cela ne fait que déplacer le mensonge : un menu Tools contenant `docker compose up`, `psql` et un script de déploiement est tout aussi indifférencié, et dès qu'il y a dix entrées personne n'en retrouve une. Et un **second fichier**, `menus.toml`, garde le fichier d'outils simple au prix de deux fichiers qui doivent s'accorder sur les outils qui existent.

Le menu est donc un **nom libre porté par l'outil**, dans l'unique fichier : `menu = "Docker"`. Un nom que rien d'autre n'emploie crée le menu ; omettre la clé signifie Rust. Il n'y a pas de liste de noms autorisés, parce qu'une liste serait la liste des projets de quelqu'un d'autre.

Rust reste fixe sur la barre plutôt que de devenir un nom parmi d'autres venu du fichier. **Rust ▸ Create tools file** doit être atteignable dans un projet qui n'a aucun fichier d'outils — c'est précisément le projet qui en a besoin — et un menu qui n'existe qu'une fois le fichier créé ne peut pas proposer de créer le fichier.

## Pourquoi la touche d'accès n'appartient pas au fichier

L'auteur d'un fichier d'outils ne peut pas savoir quelles lettres sont libres. Il voit `File`, `Edit`, `Search`, `Run`, `Options`, `Window`, `Snippets`, `Rust` et `Help` sur la barre, mais seulement en comptant les soulignements, et un projet partagé à plusieurs dépendrait alors du fait que personne n'ajoute un menu qui entre en collision.

Les collisions sont ici **silencieuses**, et c'est ce qui vaut qu'on se prémunisse contre elles. La barre répond au premier menu dont la touche correspond ; un second menu revendiquant la même lettre n'est pas une erreur et se dessine normalement — il ne s'ouvre simplement jamais. Le piège s'est déjà refermé une fois dans cet éditeur : `Snippets` et `Search` voulaient tous deux le `S`, `Snippets` était celui qu'on ne pouvait pas ouvrir, et tous les tests passaient. Le correctif d'alors fut de déplacer Snippets sur `N` à la main. Laisser un fichier nommer des menus en ferait un danger permanent plutôt qu'une erreur isolée, donc l'attribution revient à l'éditeur : la première lettre du nom que rien d'autre ne revendique.

Les tildes écrits dans le nom sont honorés **quand la lettre est libre**, et écartés sans bruit sinon. Refuser le fichier était l'alternative, et elle est pire : la collision dépend des menus qui existent, donc un fichier d'outils qui marchait cesserait de marcher le jour où une version de l'éditeur ajoute un menu. Entre un menu sur une lettre que vous n'avez pas demandée et un menu que vous ne pouvez pas ouvrir, la première est la moindre perte.

Quand toutes les lettres d'un nom sont prises, le menu n'a pas de touche d'accès du tout. `F10`, les flèches et la souris l'atteignent encore, et l'alternative — aller chercher une lettre qui n'est pas dans le nom — mettrait un soulignement sous rien.

## Pourquoi la barre est reconstruite depuis un stat

`Menu.OnOpen` remplit les entrées d'un menu juste avant qu'il ne se déroule, et c'est ainsi que les menus Rust et Snippets suivent leurs fichiers sans redémarrage. Cela ne suffit pas ici : l'*ensemble* des menus appartient à la barre et non à un menu, et ajouter `menu = "Docker"` au fichier doit poser Docker sur la barre.

Lire et analyser le fichier à chaque tour de la boucle d'événements y parviendrait, et ferait aussi ce travail pour rien à chaque frappe dans un fichier que personne n'a modifié. La barre porte donc la taille et la date de modification du fichier d'outils dont elle est issue, et un `stat` par tour décide s'il faut reconstruire. Modifier le fichier dans la fenêtre devant soi, l'enregistrer et voir la barre changer, c'est le cas visé.

## Pourquoi les fichiers ouverts sont relus, et seulement certains

`Format` est le premier item du menu et il réécrit les fichiers sur le disque — y compris celui qu'on regarde. Sans rien de plus, l'éditeur resterait assis sur une copie périmée, et le `F2` suivant réécrirait votre version non formatée par-dessus le travail de gofmt. Ce n'est pas une aspérité : c'est la fonctionnalité qui se défait toute seule, en silence.

À la fin d'une commande, l'éditeur relit donc chaque fichier ouvert. La partie intéressante est ceux qu'il refuse de toucher.

**Un fichier ayant des modifications non enregistrées est laissé tel quel**, et la barre d'état dit combien ont été ignorés. Le recharger jetterait un travail que l'utilisateur n'a pas enregistré, ce qu'aucune commodité ne justifie. Et le conflit est réel : le formateur et la modification non enregistrée ne sont pas d'accord sur ce que le fichier doit dire, et l'éditeur n'est pas en position de trancher. Le nommer et s'arrêter est l'issue honnête — l'utilisateur peut enregistrer et relancer, ou continuer à éditer et formater plus tard.

Deux décisions plus petites à l'intérieur :

- **Le curseur reste où il était**, borné à ce que le fichier contient désormais. Un formateur déplace les lignes ; remettre le curseur en haut ferait perdre sa place au lecteur pour rien.
- **L'historique d'annulation est jeté.** Annuler au-delà d'un rechargement restaurerait un texte que le fichier n'a plus, ce qui est pire que de ne pas pouvoir annuler.

## Pourquoi le rechargement a lieu sur la boucle d'événements

La fin de la commande est remarquée par la goroutine qui lit le terminal, laquelle ne peut toucher ni un buffer ni le bureau. Elle positionne donc un drapeau, et le rechargement se fait en tête du tour suivant de la boucle.

C'est la quatrième chose construite ainsi dans cet éditeur — l'annonce au serveur de langage, les redessins de terminal, l'échéance d'autosave, et maintenant ceci. La règle qu'elles partagent mérite d'être énoncée une fois de plus : **le réveil peut être perdu, l'état ne doit pas l'être.** `PostEvent` jette ce qui ne tient pas dans sa file, donc tout ce qui dépend de l'arrivée d'un message est un bug qui attend un moment de charge. Un drapeau que la boucle vérifie elle-même ne peut pas disparaître.

## Pourquoi une commande peut demander une valeur, et pourquoi en doubles accolades

`go mod init` a besoin d'un chemin de module. `cargo new` a besoin d'un nom de caisse. `go test -run` a besoin d'un motif. Aucun de ces éléments ne peut vivre dans le fichier d'outils, parce que la réponse change à chaque fois — et un outil qui ne peut pas demander est un outil qu'il faut éditer avant chaque usage, ce qui n'est pas un outil.

Un `{{libellé}}` dans une commande est donc une valeur que l'éditeur demande d'abord, dans une boîte portant le nom de l'outil.

**Une seule accolade était l'écriture évidente, et elle est fausse.** `awk '{print $1}'` et `find . -exec rm {} +` sont des choses ordinaires à mettre dans un fichier d'outils, et lire la première comme un libellé transforme une commande qui marche en une boîte demandant « print $1 ». Les doubles accolades n'entrent en collision presque avec rien, et la seule construction avec laquelle elles le font — un bloc imbriqué en awk — est assez rare pour être signalée plutôt que contournée par la conception.

**La valeur est protégée par défaut**, parce que l'inverse échoue en silence. Un chemin contenant une espace, substitué tel quel, devient deux arguments et la commande parle d'un fichier qui n'existe pas. La protection fait marcher ce cas et rend l'autre — « ajoute ces trois options à la fin » — impossible ; c'est pourquoi un `...` dans les accolades demande la valeur telle quelle. Deux comportements, tous deux documentés, plutôt qu'un seul faux une fois sur deux.

**Rien n'est retenu sur le disque.** La boîte repart de ce qui avait été tapé, pour la session. L'écrire dans le répertoire propre au projet a été envisagé puis rejeté : ce répertoire contient ce que le projet a décidé, et un filtre tapé en poursuivant un test n'en fait pas partie. Ce serait aussi la première chose qui y changerait sans que personne ne modifie un fichier.

**Un fichier illisible est refusé à la lecture**, pas au moment où l'outil est choisi. Un `{{` non fermé atteignant le shell donne une commande qui échoue avec des accolades dedans, ce qui ne nomme ni l'outil ni le fichier ; refuser au chargement nomme les deux. C'est la règle que suit déjà une valeur d'`output` inconnue.

**La boîte est refusée quand elle ne tient pas.** Un outil demandant plus de valeurs que le terminal n'a de lignes donnerait une boîte dont le bouton OK est sous le bas de l'écran — à laquelle on ne peut répondre que par Échap, qui annule. Dire « celui-ci demande douze valeurs et neuf tiennent » n'est pire que rien que si l'on préfère l'apprendre en essayant.

## Liens avec le reste

- Toutes les clés du fichier et toutes les règles : [Référence des outils go](../reference/rust-tools.md)
- L'utiliser : [Lancer les commandes cargo depuis l'éditeur](../how-to/run-cargo-commands.md)
- Les fenêtres qu'emploie `output = "terminal"`, et pourquoi ce sont de vrais terminaux : [Fenêtres terminal](terminal-windows.md)
- L'autre menu construit depuis un fichier : [Snippets](snippets.md)