turbo-editors/turbo-golopublic Fork 0
49c12f54ef399972152fccd592ae27e94977f9b5
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-golo.git
git clone ssh://git@rickub.com/turbo-editors/turbo-golo.git

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

architecture.md · 111 lines · 11.4 KBmarkdown Blame HistoryRaw
📦 Turbo Golo d710c1b k33g yesterday1# Architecture — explication
2
3## De quoi s'agit-il ?
4
5Turbo Golo, c'est une commande, un profil et un scanner. Tout le reste — le widget d'édition, les fenêtres, les menus, les dialogues, les thèmes, l'émulateur de terminal, l'arbre de fichiers, le client LSP — c'est [turbo-core](https://rickub.com/turbo-editors/turbo-core), la bibliothèque sur laquelle tous les éditeurs Turbo sont construits.
6
7Cette page parle de cette séparation : ce qui est ici, ce qui est là-bas, et pourquoi la frontière passe où elle passe.
8
9## Ce que contient ce dépôt
10
11```
12main.go les options, le terminal et le câblage
13internal/gololang tout ce qui fait de cet éditeur Turbo Golo
14 gololang.go le profil : nom, menu, serveur, où golo est installé
15 scan.go le répartiteur du scanner, les commentaires, ce qui franchit une ligne
16 literals.go les trois formes entre guillemets — "…", """…""" et '…'
17 words.go les nombres, les mots-clés, les 157 builtins, les conventions de nommage
18 templates.go trois déclarations //go:embed
19 *.toml.tmpl les trois fichiers de départ d'un projet, embarqués
20```
21
22Un millier de lignes environ en comptant les commentaires, dont quelque six cents pour le scanner — moins de cinq cents lignes de code au compte de qlty, et un tiers de celles-ci est la table des builtins. Il n'y a pas d'`internal/app`, pas d'`internal/ui`, pas d'`internal/buffer` : ceux-là existent une fois, dans la bibliothèque, et les six éditeurs les utilisent tels quels.
23
24## Ce que fait `main`
25
26Six choses, dans cet ordre :
27
281. Il lit les options.
292. Il appelle `gololang.Register()`, qui apprend à la bibliothèque à colorer les fichiers `.golo` et les scripts dont la première ligne nomme `golo`.
303. Il construit `gololang.Profile()` — la valeur qui dit que cet éditeur est Turbo Golo.
314. Il lit `.turbo-golo/settings.toml` dans le répertoire courant, s'il existe.
325. Il ouvre le terminal et confie l'écran, le nom du thème et le profil à `app.New`.
336. Il démarre `golo lsp` dans le répertoire du fichier édité, et lance la boucle d'événements.
34
35C'est toute la commande. Chaque décision qu'elle prend — quel thème l'emporte, quels fichiers ouvrir, faut-il démarrer un serveur de langage — porte sur *cette exécution*, pas sur Golo.
36
37## Le profil est la couture
38
39```go
40profile.Profile{
41 Name: "Turbo Golo",
42 Slug: "turbo-golo",
43 Language: "Golo",
44 ToolsMenu: "~G~olo",
45 RootMarkers: nil,
46 Server: profile.Server{Command: "golo", Args: []string{"lsp"}, },
47 Templates: profile.Templates{Settings: , Snippets: , Tools: },
48}
49```
50
51Tout ce qui serait sinon un `"turbo-golo"`, un `"golo"` ou un `".golo"` codé en dur quelque part dans onze mille lignes est un champ ici. La bibliothèque les lit ; rien dans la bibliothèque ne sait ce qu'ils signifient.
52
53`Slug` porte plus qu'il n'y paraît. Le binaire s'appelle `turbo-golo`, le répertoire de projet `.turbo-golo`, la configuration personnelle vit dans `~/.config/turbo-golo`, et les variables d'environnement qui la remplacent sont `TURBO_GOLO_THEME_DIR` et `TURBO_GOLO_SNIPPET_DIR` — toutes dérivées de ce seul mot.
54
55`RootMarkers` est le seul champ vide ici et rempli chez tous les frères. Go a `go.mod`, Rust `Cargo.toml`, Python `pyproject.toml`, MoonBit `moon.mod` ; Golo n'a aucun manifeste. Un script est un fichier et un programme est un répertoire de fichiers, il n'y a donc rien vers quoi remonter, et le `ProjectRoot` de la bibliothèque — sans marqueur — répond par le répertoire du fichier. C'est aussi tout ce dont `golo lsp` a besoin : il répond à propos du fichier qu'on lui donne et résout les imports depuis les modules embarqués dans le binaire, jamais depuis le disque.
56
57## Pourquoi le scanner est ici et pas dans la bibliothèque
58
59turbo-core colore huit langages lui-même : TOML, YAML, Markdown, JavaScript, HTML, XML, les Dockerfiles et le shell. Ce sont ceux que tout éditeur rencontre quel que soit son langage — la configuration d'un projet est en TOML ou en YAML, sa documentation en Markdown, ses scripts en shell, la construction de son image dans un Dockerfile.
60
61Golo n'en fait pas partie, ni Go, ni Rust, ni Python, ni MoonBit. Le langage qui *définit* un éditeur est enregistré par cet éditeur, et c'est pourquoi un fichier `.mbt` s'ouvre en texte brut ici et un fichier `.golo` s'ouvre en texte brut dans Turbo MoonBit.
62
63Cela aurait pu aller dans l'autre sens. Mettre les six scanners dans la bibliothèque permettrait à n'importe quel éditeur de colorer n'importe lequel de ces langages, sans coût en dépendances — un scanner Golo est du Go ordinaire. L'idée a été rejetée parce que la bibliothèque grossirait d'un langage chaque fois que quelqu'un construit un éditeur, et parce que « qu'est-ce que cet éditeur enregistre ? » cesserait d'être la première question à poser sur un nouveau venu.
64
65## Pourquoi le scanner n'a pas été emprunté à GoloScript
66
67GoloScript est écrit en Go, et son paquet `lexer` est exactement le tokeniseur que ce scanner réimplémente. Turbo Go se sert de `go/scanner` dans la même situation, la question est donc légitime.
68
69La réponse tient au nom du module. Le `go.mod` de GoloScript déclare `module golo`, un nom nu sans hôte, et le système de modules de Go ne sait pas aller chercher un module portant un tel chemin où que ce soit : importer `golo/lexer` depuis un autre module exige une directive `replace` pointant vers un dépôt cloné à côté, et un `replace` committé est précisément ce que `01-release.tag.sh` refuse de publier. Les règles du lexer sont donc reportées ici plutôt qu'appelées — et `lexer/lexer.go` et `token/token.go` sont la spécification contre laquelle le scanner est écrit, ligne pour ligne là où cela compte. La [page sur la coloration](colouring-and-completion.md) nomme les endroits où cette spécification et le parseur de l'interpréteur ne sont pas d'accord.
70
71## Pourquoi le menu de l'outillage s'appelle `~G~olo` et non `golo`, `gogolo` ou `wagolo`
72
73La touche chaude était la partie facile. Neuf lettres sont prises par les menus fixes — F, E, S, R, C, O, W, N et H — et `G` n'en fait pas partie, elle tombe donc sur la première lettre du mot, ce qui ne coûte à personne un second regard. Turbo Rust n'a pas eu cette chance et a fini sur `Rus~t~`.
74
75Le nom était la vraie décision, et elle a été prise comme chez tous les frères. Le menu contient ce que le projet a mis dans son fichier d'outils, et ce n'est pas toujours l'interpréteur : GoloScript lui-même, c'est trois binaires — `golo`, `gogolo`, `wagolo` — et le premier fichier d'outils qu'on écrit dépasse les trois, parce que les commandes d'un projet comprennent des conteneurs, des bases de données et une cible de `Makefile` ajoutée en 2019. Un menu appelé **golo** qui contient `wagolo build` est déjà un petit mensonge, et un qui contient `docker compose up` en est un gros. `Golo` est le langage, et le langage est ce pour quoi cet éditeur existe.
76
77## Pourquoi les tests pilotent le vrai éditeur
78
79`internal/gololang/editor_test.go` construit un Turbo Golo entier sur un terminal simulé — `app.New(screen, "turbo-classic", gololang.Profile())` — ouvre un fichier et vérifie la coloration, la barre de menus et les touches chaudes. Il n'utilise que l'API publique de la bibliothèque.
80
81C'est délibéré. La suite de la bibliothèque prouve que la bibliothèque fonctionne ; ce que ces tests prouvent, c'est que *cet éditeur est correctement assemblé* — que `Register` a été appelé, que le profil a atteint la barre de menus, qu'un fichier `.golo` sort coloré et qu'un script avec un shebang `golo` aussi. Un bogue où `main` oublierait d'enregistrer Golo passerait tous les tests de turbo-core.
82
83Le même fichier pilote un **vrai `golo lsp`** de bout en bout, dix fois. Il écrit un script, l'ouvre, démarre le serveur, puis :
84
85- **tape une déclaration de fonction qui n'existe que dans le tampon**, puis tape ses premières lettres sur une autre ligne et demande une complétion — `golo lsp` propose les mots-clés et les builtins pour n'importe quel fichier, une complétion contenant `println` ne prouverait donc rien ; une qui contient une fonction absente du disque prouve que le tampon a été envoyé ;
86- demande la **complétion d'un préfixe vide** et compare la réponse aux tables du scanner dans les deux sens — chaque mot-clé et chaque builtin que le scanner colore doit être proposé par le serveur, et tout ce que le serveur propose doit être connu du scanner, ce qui tient la table de 157 builtins à l'interpréteur plutôt qu'à la mémoire ;
87- demande la **définition** d'un appel et le **survol** de celui-ci, qui revient avec le commentaire `#` écrit au-dessus de la déclaration ;
88- demande les **symboles du fichier** ;
89- demande les **références** d'un appel — la déclaration et chaque appel dans le fichier — et son **implémentation**, qui est la déclaration elle-même, Golo n'ayant pas d'interfaces ;
90- cherche un **symbole dans le projet**, y compris dans un fichier que l'éditeur n'a jamais ouvert ;
91- ouvre un fichier qui **ne parse pas** et attend qu'un diagnostic arrive sans qu'on le demande — la seule fonctionnalité dont l'échec ressemble exactement au succès, parce qu'un éditeur qui n'a aucune erreur à montrer et un éditeur qui ne trouve pas l'erreur ont la même gouttière vide ;
92- ouvre un fichier contenant un **commentaire `//` à la C** et attend le lint qui dit que Golo utilise `#`.
93
94Un dernier test épingle ce que `golo lsp` *ne sait toujours pas* faire : il n'annonce pas `typeDefinition`, la documentation le dit, et le test échoue si un futur golo se met à répondre — la page est alors revue plutôt que de vieillir en silence. C'est ainsi que les trois tests qui le précèdent sont nés : jusqu'à GoloScript v0.2.0 le même test épinglait les références, les implémentations et les symboles du projet comme refus, et il est passé au rouge le jour où le serveur les a appris.
95
96## Alternatives rejetées
97
98**Forker Turbo MoonBit.** La façon évidente d'obtenir un cinquième éditeur, et la raison pour laquelle la bibliothèque existe à la place : cinq copies de onze mille lignes divergent en un mois, et chaque correction doit être faite cinq fois par quelqu'un qui se souvient qu'il y en a cinq.
99
100**Un système de plugins.** Turbo Golo est un programme Go qui importe une bibliothèque. Il n'y a ni chargement dynamique ni ABI. En ajouter un reviendrait à figer l'API de chaque paquet de turbo-core plutôt que celle des quelques-uns qu'un profil touche.
101
102**Un fichier de configuration au lieu d'un profil.** Le profil aurait pu être du TOML lu au démarrage, ce qui ferait d'un nouvel éditeur un fichier plutôt qu'un programme. Cela rendrait aussi le scanner inexprimable, et un éditeur à moitié configurable — tout sauf la coloration — est pire que l'une ou l'autre des réponses entières.
103
104**Importer le lexer de GoloScript.** Pesé plus haut : le module ne peut pas être récupéré, et un `replace` ne peut pas être publié.
105
106## Liens avec le reste
107
108- Ce que fait chaque paquet de la bibliothèque : [la référence des paquets de turbo-core](https://rickub.com/turbo-editors/turbo-core/blob/main/docs/fr/reference/packages.md)
109- Comment la coloration fonctionne ici : [Coloration et complétion](colouring-and-completion.md)
110- Pourquoi le menu des outils est une donnée : [Outils Golo](golo-tools.md)
111- Les décisions qui ont survécu au refactoring : [Décisions de conception](design-decisions.md)