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

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

📦 Turbo Go 3d7798b · on main · k33g · 12h ago
snippets.md · 62 lines · 6.9 KBmarkdown
Blame HistoryOpen raw

Snippets — explication

De quoi s'agit-il ?

Un menu Snippets dont le contenu vient d'un fichier TOML, et un snippet choisi déposé dans le fichier que vous éditez. Cette page traite des trois décisions qui lui donnent sa forme : pourquoi le menu est reconstruit à chaque ouverture, pourquoi l'éditeur a gagné de vrais sous-menus pour lui, et pourquoi l'insertion réindente.

Pourquoi le menu est construit au moment où il s'ouvre

Tous les autres menus de l'éditeur sont décidés une fois, dans New(). Celui-ci ne peut pas l'être, et pour deux raisons indépendantes.

La première est le fichier. Les snippets vivent dans du TOML, et tout l'intérêt est que vous l'éditiez — souvent dans cet éditeur, dans la fenêtre que l'entrée Create snippets file vient d'ouvrir pour vous. Un menu construit au démarrage montrerait l'état du fichier au lancement, et il faudrait redémarrer pour voir un snippet qu'on vient d'écrire. C'est le genre de friction qui fait qu'une fonctionnalité n'est pas utilisée du tout.

La seconde est la fenêtre au premier plan. Le menu est filtré par ce que vous éditez, donc il change quand vous appuyez sur F6. Il n'existe aucun instant du démarrage où la réponse existe.

ui.Menu a donc gagné un champ OnOpen : une fonction que la barre appelle juste avant de dérouler un menu, laissant son propriétaire regarnir Items d'abord. C'est le même mécanisme de communication ascendante que partout ailleurs dans ce code — un champ fonction, pas une interface — et il s'exécute exactement au moment où le contenu va être vu, pas plus souvent.

Pourquoi l'éditeur a gagné des sous-menus

ui.MenuItem ne savait pas imbriquer, et l'ajouter a été la plus grosse pièce de ce travail : un second panneau à placer et à dessiner, des flèches qui signifient « plus profond » et « ressortir », le pointeur qui ouvre une branche au survol et la referme en la quittant, et une fermeture qui range les deux panneaux d'un coup.

L'alternative était un seul panneau plat avec les groupes en intitulés grisés entre des filets. Cela fonctionne, ne demande rien de neuf, et s'effondre sur le cas même pour lequel la fonctionnalité existe : un projet de trente snippets donne un menu plus haut que le terminal. Un regroupement qui étiquette sans replier ne résout pas le problème qu'il semble résoudre.

C'est délibérément un seul niveau. Le format est des groupes contenant des snippets — exactement un niveau — et une profondeur générale supposerait de remplacer les deux indices de la barre par un chemin, dans le widget dont dépendent déjà tous les dialogues et tous les tests de menu. C'est du travail spéculatif sur la partie la plus porteuse de l'interface.

Deux détails du sous-menu méritent d'être nommés, parce qu'ils ont été choisis et non subis :

  • Droite et gauche sont asymétriques avec Échap. Droite ouvre une branche, ou passe au menu suivant quand l'entrée n'en a pas : elle signifie donc toujours « plus profond », où que l'on soit. Gauche ressort d'un sous-menu vers son parent, tandis qu'Échap referme tout le menu — parce qu'annuler doit vouloir dire annuler, de n'importe où.
  • Le panneau bascule à gauche, et sa largeur est aussi bornée. Un sous-menu qui dépasserait le bord droit est dessiné de l'autre côté de son parent. Basculer ne suffit pas : un panneau plus large que le terminal ne peut pas être rendu visible en le déplaçant, donc la largeur est bornée aussi et les intitulés longs sont coupés par le peintre. Un cadre sans bord droit paraît cassé d'une façon dont un intitulé tronqué ne l'est pas.

Pourquoi l'insertion réindente

Un snippet est du texte, et l'implémentation évidente est de l'insérer. C'est juste pour une seule ligne et faux pour tout le reste, c'est-à-dire pour l'essentiel de ce que les gens gardent en snippets.

Déposé tel quel, un corps multi-ligne repart en colonne zéro. Inséré dans une fonction, dans une boucle, dans un switch — là où l'on insère justement un if err != nil — le résultat est un texte dont aucun formateur, aucun compilateur et aucun lecteur ne se satisfait, et la première chose qu'on fait est de le réindenter à la main. Une fonctionnalité dont la sortie doit être corrigée chaque fois ne fait gagner de temps à personne.

Les lignes après la première reçoivent donc l'indentation de la ligne où était le curseur. Cela recopie ce que le fichier emploie déjà — tabulations ou espaces, en telle quantité — plutôt que d'imposer un choix, ce qui compte dans un projet à l'histoire mêlée.

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

  • Une ligne vide du corps reste vide. La compléter jusqu'à l'indentation y mettrait des espaces en fin de ligne, que tout formateur supprime ensuite — du bruit dans le diff de l'enregistrement suivant.
  • C'est une seule annulation. Un snippet est une seule action pour qui l'a choisi, donc Ctrl-Z doit tout reprendre. Cela découle de faire toute l'insertion en un seul ReplaceRange, la règle que le buffer impose déjà à toute autre modification.

Les emplacements et les tabulations successives — ${1:nom} et le passage de l'un à l'autre — ont été envisagés et laissés de côté. C'est une seconde fonctionnalité, avec son propre état à maintenir à travers les modifications, alors que ce qui était demandé est du texte réutilisable.

Pourquoi deux fichiers, et pourquoi le projet gagne

Vos snippets vous appartiennent et doivent vous suivre d'un projet à l'autre ; ceux d'un projet lui appartiennent et doivent arriver avec un clone. Ni l'un ni l'autre n'est la réponse complète, donc les deux sont lus.

Quand un nom entre en conflit dans le même groupe, celui du projet remplace le vôtre. C'est le plus spécifique des deux énoncés, et c'est celui dont une équipe a convenu — la même raison qui fait qu'un drapeau -theme l'emporte sur le réglage d'un projet, tandis que le réglage d'un projet l'emporte sur le défaut intégré.

Pourquoi un fichier illisible est bruyant

Une faute de frappe dans le TOML pourrait faire disparaître tous les snippets en silence et laisser un menu ne contenant que Create snippets file — ce qui ressemble exactement à un projet sans snippets, et vous envoie créer un fichier que vous avez déjà.

Le menu affiche donc un Cannot read snippets grisé là où les groupes seraient. Il ne peut pas être choisi, il est là où vous regardiez, et l'entrée de création reste en dessous : il y a une issue dans les deux cas.

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
# Snippets — explication

## De quoi s'agit-il ?

Un menu **Snippets** dont le contenu vient d'un fichier TOML, et un snippet choisi déposé dans le fichier que vous éditez. Cette page traite des trois décisions qui lui donnent sa forme : pourquoi le menu est reconstruit à chaque ouverture, pourquoi l'éditeur a gagné de vrais sous-menus pour lui, et pourquoi l'insertion réindente.

## Pourquoi le menu est construit au moment où il s'ouvre

Tous les autres menus de l'éditeur sont décidés une fois, dans `New()`. Celui-ci ne peut pas l'être, et pour deux raisons indépendantes.

La première est le fichier. Les snippets vivent dans du TOML, et tout l'intérêt est que vous l'éditiez — souvent dans cet éditeur, dans la fenêtre que l'entrée **Create snippets file** vient d'ouvrir pour vous. Un menu construit au démarrage montrerait l'état du fichier au lancement, et il faudrait redémarrer pour voir un snippet qu'on vient d'écrire. C'est le genre de friction qui fait qu'une fonctionnalité n'est pas utilisée du tout.

La seconde est la fenêtre au premier plan. Le menu est filtré par ce que vous éditez, donc il change quand vous appuyez sur `F6`. Il n'existe aucun instant du démarrage où la réponse existe.

`ui.Menu` a donc gagné un champ `OnOpen` : une fonction que la barre appelle juste avant de dérouler un menu, laissant son propriétaire regarnir `Items` d'abord. C'est le même mécanisme de communication ascendante que partout ailleurs dans ce code — un champ fonction, pas une interface — et il s'exécute exactement au moment où le contenu va être vu, pas plus souvent.

## Pourquoi l'éditeur a gagné des sous-menus

`ui.MenuItem` ne savait pas imbriquer, et l'ajouter a été la plus grosse pièce de ce travail : un second panneau à placer et à dessiner, des flèches qui signifient « plus profond » et « ressortir », le pointeur qui ouvre une branche au survol et la referme en la quittant, et une fermeture qui range les deux panneaux d'un coup.

L'alternative était un seul panneau plat avec les groupes en intitulés grisés entre des filets. Cela fonctionne, ne demande rien de neuf, et s'effondre sur le cas même pour lequel la fonctionnalité existe : un projet de trente snippets donne un menu plus haut que le terminal. Un regroupement qui étiquette sans replier ne résout pas le problème qu'il semble résoudre.

C'est délibérément **un seul niveau**. Le format est des groupes contenant des snippets — exactement un niveau — et une profondeur générale supposerait de remplacer les deux indices de la barre par un chemin, dans le widget dont dépendent déjà tous les dialogues et tous les tests de menu. C'est du travail spéculatif sur la partie la plus porteuse de l'interface.

Deux détails du sous-menu méritent d'être nommés, parce qu'ils ont été choisis et non subis :

- **Droite et gauche sont asymétriques avec Échap.** Droite ouvre une branche, ou passe au menu suivant quand l'entrée n'en a pas : elle signifie donc toujours « plus profond », où que l'on soit. Gauche *ressort* d'un sous-menu vers son parent, tandis qu'Échap referme tout le menu — parce qu'annuler doit vouloir dire annuler, de n'importe où.
- **Le panneau bascule à gauche, et sa largeur est aussi bornée.** Un sous-menu qui dépasserait le bord droit est dessiné de l'autre côté de son parent. Basculer ne suffit pas : un panneau plus large que le terminal ne peut pas être rendu visible en le déplaçant, donc la largeur est bornée aussi et les intitulés longs sont coupés par le peintre. Un cadre sans bord droit paraît cassé d'une façon dont un intitulé tronqué ne l'est pas.

## Pourquoi l'insertion réindente

Un snippet est du texte, et l'implémentation évidente est de l'insérer. C'est juste pour une seule ligne et faux pour tout le reste, c'est-à-dire pour l'essentiel de ce que les gens gardent en snippets.

Déposé tel quel, un corps multi-ligne repart en colonne zéro. Inséré dans une fonction, dans une boucle, dans un `switch` — là où l'on insère justement un `if err != nil` — le résultat est un texte dont aucun formateur, aucun compilateur et aucun lecteur ne se satisfait, et la première chose qu'on fait est de le réindenter à la main. Une fonctionnalité dont la sortie doit être corrigée chaque fois ne fait gagner de temps à personne.

Les lignes après la première reçoivent donc l'indentation de la ligne où était le curseur. Cela recopie ce que le fichier emploie déjà — tabulations ou espaces, en telle quantité — plutôt que d'imposer un choix, ce qui compte dans un projet à l'histoire mêlée.

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

- **Une ligne vide du corps reste vide.** La compléter jusqu'à l'indentation y mettrait des espaces en fin de ligne, que tout formateur supprime ensuite — du bruit dans le diff de l'enregistrement suivant.
- **C'est une seule annulation.** Un snippet est une seule action pour qui l'a choisi, donc `Ctrl-Z` doit tout reprendre. Cela découle de faire toute l'insertion en un seul `ReplaceRange`, la règle que le buffer impose déjà à toute autre modification.

Les emplacements et les tabulations successives — `${1:nom}` et le passage de l'un à l'autre — ont été envisagés et laissés de côté. C'est une seconde fonctionnalité, avec son propre état à maintenir à travers les modifications, alors que ce qui était demandé est du texte réutilisable.

## Pourquoi deux fichiers, et pourquoi le projet gagne

Vos snippets vous appartiennent et doivent vous suivre d'un projet à l'autre ; ceux d'un projet lui appartiennent et doivent arriver avec un clone. Ni l'un ni l'autre n'est la réponse complète, donc les deux sont lus.

Quand un nom entre en conflit dans le même groupe, celui du projet remplace le vôtre. C'est le plus spécifique des deux énoncés, et c'est celui dont une équipe a convenu — la même raison qui fait qu'un drapeau `-theme` l'emporte sur le réglage d'un projet, tandis que le réglage d'un projet l'emporte sur le défaut intégré.

## Pourquoi un fichier illisible est bruyant

Une faute de frappe dans le TOML pourrait faire disparaître tous les snippets en silence et laisser un menu ne contenant que **Create snippets file** — ce qui ressemble exactement à un projet sans snippets, et vous envoie créer un fichier que vous avez déjà.

Le menu affiche donc un `Cannot read snippets` grisé là où les groupes seraient. Il ne peut pas être choisi, il est là où vous regardiez, et l'entrée de création reste en dessous : il y a une issue dans les deux cas.

## Liens avec le reste

- Toutes les clés et toutes les règles : [Référence des snippets](../reference/snippets.md)
- Les mettre en place : [Insérer des snippets depuis un menu](../how-to/use-snippets.md)
- L'autre fichier du même dossier : [Réglages de projet](project-settings.md)
- Les noms de langages qu'emploie `languages` : [Langages colorés](../reference/languages.md)