Architecture — explication
De quoi s'agit-il ?
Turbo Go est une commande, un profil et un analyseur. Tout le reste — le composant d'édition, les fenêtres, les menus, les dialogues, les thèmes, l'émulateur de terminal, l'arborescence de fichiers, le client LSP — est turbo-core, la bibliothèque sur laquelle repose chaque éditeur Turbo.
Cela n'a pas toujours été vrai. Turbo Go était un unique programme d'environ onze mille cinq cents lignes en quatorze paquets jusqu'à ce que l'on veuille un second éditeur ; à ce moment-là, les quatorze ont migré dans une bibliothèque et un seul est resté ici. Cette page parle de la coupure qui en résulte.
Ce qu'il y a dans ce dépôt
main.go les drapeaux, le terminal, et le câblage
internal/golang la totalité de ce qui fait Turbo Go
golang.go le profil : nom, menu, serveur, marqueur de racine
scan.go l'analyseur Go, bâti sur go/scanner
templates.go trois déclarations //go:embed
*.toml.tmpl les trois fichiers de départ qu'un projet reçoit, embarqués
Environ quatre cents lignes. 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 chaque éditeur bâti dessus s'en sert sans les modifier.
Ce que fait main
Six choses, dans cet ordre :
- Analyse les drapeaux.
- Appelle
golang.Register(), qui apprend à la bibliothèque à colorer les fichiers.go. - Construit
golang.Profile()— la valeur qui dit que cet éditeur est Turbo Go. - Lit
.turbo-go/settings.tomldans le répertoire courant, s'il y en a un. - Ouvre le terminal et passe l'écran, le nom du thème et le profil à
app.New. - Démarre gopls à la racine du module, et lance la boucle d'événements.
C'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 Go.
Le profil est la couture
profile.Profile{
Name: "Turbo Go",
Slug: "turbo-go",
Language: "Go",
ToolsMenu: "~G~o",
RootMarkers: []string{"go.mod"},
Server: profile.Server{Command: "gopls", Args: []string{"serve"}, …},
Templates: profile.Templates{Settings: …, Snippets: …, Tools: …},
}
Tout ce qui était un "turbo-go", un "gopls" ou un "go.mod" en dur quelque part dans onze mille lignes est ici un champ. La bibliothèque les lit ; rien dans la bibliothèque ne sait ce qu'ils veulent dire.
Slug porte plus qu'il n'y paraît. Le binaire est turbo-go, le répertoire de projet est .turbo-go, la configuration de l'utilisateur vit dans ~/.config/turbo-go, et les variables d'environnement qui la remplacent sont TURBO_GO_THEME_DIR et TURBO_GO_SNIPPET_DIR — toutes dérivées de ce seul mot. Ces noms sont inchangés par le refactoring, et délibérément : quelqu'un qui a posé TURBO_GO_THEME_DIR l'a fait face à un binaire publié.
Pourquoi l'analyseur Go est ici et pas dans la bibliothèque
turbo-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 objet — la configuration d'un projet est du TOML ou du YAML, sa documentation du Markdown, ses scripts du shell, sa construction d'image un Dockerfile.
Go n'en fait pas partie. Le langage qui définit un éditeur est enregistré par cet éditeur, ce qui explique qu'un fichier .rs s'ouvre ici en texte brut et qu'un fichier .go s'ouvre en texte brut dans Turbo Rust.
L'analyseur Go est aussi celui qui ressemble le moins aux autres. Tous les langages de la bibliothèque sont analysés ligne à ligne avec syntax.LineScanner ; Go passe par go/scanner, l'analyseur lexical qu'utilise la chaîne d'outils Go elle-même, et convertit ses décalages en octets avec syntax.LineIndex. Si la bibliothèque accepte les deux formes, c'est à cause de cet analyseur.
Ce qui a bougé, et ce qui n'a pas bougé
| Était | Est |
|---|---|
internal/buffer, internal/ui, internal/editor, … |
turbo-core/buffer, turbo-core/ui, turbo-core/editor, … |
internal/syntax — six langages |
turbo-core/syntax — huit, plus un registre ; Go vit ici |
internal/app avec une constante Name |
turbo-core/app recevant un profile.Profile |
internal/lsp codant gopls en dur |
turbo-core/lsp recevant un profile.Server |
moduleRoot dans main.go |
app.ProjectRoot(p, files), avec go.mod dans le profil |
settings.DirName = ".turbo-go" |
p.ProjectDir() |
Rien du comportement de l'éditeur n'a changé. Les menus, les touches, les thèmes, les formats de fichiers et les variables d'environnement sont ce qu'ils étaient. Ce qui a changé, c'est l'endroit où le code habite.
Pourquoi une bibliothèque plutôt qu'un fork
L'alternative à l'extraction de turbo-core était de copier Turbo Go et d'en changer les parties Go. Elle a été rejetée avant d'être entreprise : deux copies de onze mille lignes divergent en un mois, et chaque correctif doit être fait deux fois par quelqu'un qui se souvient qu'il y en a deux.
Le coût, accepté : une modification d'un menu touche désormais tous les éditeurs à la fois, et Turbo Go ne peut plus prendre une décision qui n'arrange que Go sans soit la mettre dans le profil, soit la défendre dans la bibliothèque. C'est une vraie contrainte, et c'est elle qui fait que les éditeurs restent le même éditeur.
Comment cela se relie au reste
- Ce que fait chaque paquet de la bibliothèque : la référence des paquets de turbo-core
- Comment marche la coloration ici : Coloration et complétion
- Pourquoi le menu d'outils est une donnée : Outils Go
- Les décisions qui ont survécu au refactoring : Décisions de conception
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 |
|