turbo-editors/turbo-jspublic Fork 0
v1.0.1
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-js.git
git clone ssh://git@rickub.com/turbo-editors/turbo-js.git

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

📦 Turbo JS 91999d1 · on v1.0.1 · k33g · 11h ago
architecture.md · 118 lines · 13.8 KBmarkdown
Blame HistoryOpen raw

Architecture — explication

De quoi s'agit-il ?

Turbo JS, c'est une commande, un profil et deux scanners. 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, les fenêtres d'agent — c'est turbo-core, la bibliothèque sur laquelle tous les éditeurs Turbo sont construits.

Cette page parle de cette séparation : ce qui est ici, ce qui est là-bas, et pourquoi la frontière passe où elle passe.

Ce que contient ce dépôt

main.go                  les options, le terminal et le câblage
internal/jslang          tout ce qui fait de cet éditeur Turbo JS
  jslang.go              le profil : nom, menu, serveur, où npm met un binaire global
  scan.go                le répartiteur du scanner JavaScript, les commentaires, les expressions régulières, ce qui franchit une ligne
  literals.go            les template literals et les nombres
  words.go               les mots-clés, les constantes, les globales, les conventions de nommage
  json.go                le scanner JSON
  templates.go           quatre déclarations //go:embed
  *.toml.tmpl            les quatre fichiers de départ d'un projet, embarqués

Douze cents lignes environ en comptant les commentaires, dont quelque six cents pour les deux scanners — moins de sept cents lignes de code au compte de qlty. 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.

Ce que fait main

Six choses, dans cet ordre :

  1. Il lit les options.
  2. Il appelle jslang.Register(), qui apprend à la bibliothèque le JavaScript de cet éditeur — en remplaçant le scanner qu'elle livre pour ce langage — et le JSON.
  3. Il construit jslang.Profile() — la valeur qui dit que cet éditeur est Turbo JS.
  4. Il lit .turbo-js/settings.toml dans le répertoire courant, s'il existe.
  5. Il ouvre le terminal et confie l'écran, le nom du thème et le profil à app.New.
  6. Il démarre typescript-language-server dans la racine du projet — le répertoire le plus proche, au niveau du fichier édité ou au-dessus, qui contient un package.json — 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 JavaScript.

Le profil est la couture

profile.Profile{
	Name:        "Turbo JS",
	Slug:        "turbo-js",
	Language:    "JavaScript",
	ToolsMenu:   "~J~avaScript",
	RootMarkers: []string{"package.json"},
	Server:      profile.Server{Command: "typescript-language-server", Args: []string{"--stdio"}, …},
	Templates:   profile.Templates{Settings: …, Snippets: …, Tools: …, Agents: …},
}

Tout ce qui serait sinon un "turbo-js", un "node" ou un ".js" 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.

Slug porte plus qu'il n'y paraît. Le binaire s'appelle turbo-js, le répertoire de projet .turbo-js, la configuration personnelle vit dans ~/.config/turbo-js, et les variables d'environnement qui la remplacent sont TURBO_JS_THEME_DIR et TURBO_JS_SNIPPET_DIR — toutes dérivées de ce seul mot.

RootMarkers contient un seul fichier, package.json. C'est la frontière d'un projet Node — son nom, ses dépendances, ses scripts — et dans un espace de travail qui contient plusieurs paquets, le plus proche en remontant est le paquet en cours d'édition, c'est-à-dire la racine depuis laquelle le serveur doit résoudre les imports. Le ProjectRoot de la bibliothèque fait la remontée ; le profil dit seulement quoi chercher. Un répertoire sans aucun package.json au-dessus de lui reçoit le répertoire courant, et typescript-language-server déduit un projet des fichiers qu'on lui montre.

Pourquoi le scanner JavaScript est ici alors que la bibliothèque en a un

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 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, et une page web ou le bloc de code d'un README est en JavaScript.

JavaScript est donc le seul langage de cette famille que la bibliothèque colore et pour lequel un éditeur existe. Le scanner de la bibliothèque est écrit pour le README de l'éditeur Rust : il connaît les mots-clés, les chaînes, les commentaires et les nombres, et il refuse délibérément les expressions régulières, parce que distinguer /x/g d'une division exige le token précédent, et qu'une mauvaise supposition à cet endroit colore le reste d'une ligne comme une chaîne — un mauvais marché pour un langage qu'on ne rencontre qu'en passant. Un éditeur pour JavaScript fait le marché inverse : les expressions régulières sont une ligne sur deux dans un programme Node, et la supposition peut être bornée. Il doit aussi à son utilisateur les globales de Node, la ligne shebang, le nom après function, les noms privés et les décorateurs.

La bibliothèque permet exactement cela. syntax.Register remplace un langage enregistré sous le même nom, et l'enregistrement le plus tardif l'emporte parce qu'il est l'énoncé le plus spécifique. Turbo JS s'enregistre sous le nom propre de la bibliothèque, javascript, si bien qu'un fichier de snippets disant languages = ["javascript"] et un bloc ```js dans une fenêtre d'agent atteignent tous deux ce scanner, et rien dans la bibliothèque n'a changé. La page sur la coloration dit ce que le remplacement ajoute et ce qu'il refuse encore.

Pourquoi JSON est ici aussi

JSON n'est pas l'un des huit de la bibliothèque, et un projet Node ne peut pas s'éditer sans lui : package.json est le manifeste et le marqueur de racine, package-lock.json est à côté, tsconfig.json et .eslintrc.json sont des voisins courants. L'éditeur qui est pour Node enregistre donc un scanner JSON, trente lignes, qui distingue une clé d'une valeur et tolère les commentaires que tsconfig.json contient.

Cela aurait pu aller dans la bibliothèque à la place — un éditeur Go rencontre aussi un package.json, dans un dépôt qui a un front-end. Cela n'a pas été fait, pour la raison qui tient les scanners Go et Rust hors de la bibliothèque : elle grossirait d'un langage chaque fois que quelqu'un en veut un, et « qu'est-ce que cet éditeur enregistre ? » cesserait d'être la première question à poser sur un nouveau venu. Si turbo-core apprend JSON un jour, cet enregistrement l'emportera toujours par l'ordre, et celui d'ici pourra alors être retiré.

Pourquoi le menu de l'outillage s'appelle ~J~avaScript et non ~N~ode ou npm

La touche chaude était la partie facile. Dix lettres sont prises par les menus fixes — F, E, S, R, C, O, W, N, H et, depuis le menu Agent, A — ce qui exclut le S, le C et le R de JavaScript, et le N de Node, mais pas le J ; la touche chaude 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~.

Le 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 npm : le fichier de départ lance déjà node, npm et npx, 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é npm qui contient node main.js est déjà un petit mensonge, et un qui contient docker compose up en est un gros. JavaScript est le langage, et le langage est ce pour quoi cet éditeur existe.

Pourquoi le serveur de langage n'est pas celui de TypeScript 7

Il y a deux serveurs pour JavaScript aujourd'hui. typescript-language-server enveloppe tsserver, le moteur sur lequel le support JavaScript de tous les éditeurs tourne depuis dix ans ; TypeScript 7 — le portage natif — livre un serveur de langage à lui dans son compilateur, tsc --lsp --stdio, qui n'exige rien d'installé que typescript.

Les deux ont été mesurés contre les mêmes neuf tests de bout en bout de ce dépôt. Le natif répond aux huit requêtes, quatre fois plus vite. Il ne publie aucun diagnostic : il les propose en mode pull, par textDocument/diagnostic, que turbo-core ne demande pas, si bien qu'avec lui la gouttière reste vide sur un fichier qui ne parse pas. Un éditeur dont la gouttière est vide parce que le serveur attend qu'on lui demande ressemble exactement à un éditeur qui n'a rien à signaler, et c'est le seul échec contre lequel cette famille a appris à se prémunir. Le profil nomme donc la paire mûre — typescript-language-server avec TypeScript 6, la dernière version qui livre tsserver.js — et le dit en une ligne, là où un utilisateur la lit. Le jour où la bibliothèque saura tirer les diagnostics, tsc --lsp --stdio, obtenu d'un seul npm install -g typescript, sera la meilleure réponse, et le changement tient en deux champs du profil.

Pourquoi les tests pilotent le vrai éditeur

internal/jslang/editor_test.go construit un Turbo JS entier sur un terminal simulé — app.New(screen, "turbo-classic", jslang.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.

C'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 .js sort coloré par ce scanner (une expression régulière en est la preuve) et qu'un fichier .ts ne l'est pas, que package.json est du JSON, et que le menu Agent propose d'écrire le fichier de départ. Un bogue où main oublierait d'enregistrer JavaScript passerait tous les tests de turbo-core.

Le même fichier pilote un vrai typescript-language-server de bout en bout, neuf fois. Il écrit un projet avec un package.json, ouvre son fichier, démarre le serveur, puis :

  • tape une déclaration de fonction qui n'existe que dans le tampon, puis tape ses premières lettres dans une autre fonction et demande une complétion — le serveur propose toutes les globales pour n'importe quel fichier, une complétion contenant console ne prouverait donc rien ; une qui contient une fonction absente du disque prouve que le tampon a été envoyé ;
  • demande la définition d'un appel, les références à une fonction déclarée une fois et appelée deux fois, les implémentations d'une classe avec deux sous-classes, et la définition de type d'une variable contenant une instance — quatre questions, une seule forme de réponse, et deux d'entre elles reviennent en liste ;
  • demande le survol d'un appel, qui revient avec le commentaire JSDoc écrit au-dessus de la déclaration ;
  • demande les symboles du fichier et un symbole dans tout le projet par son nom ;
  • 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.

diagram_test.go tient docs/diagrams/packages.drawio à go list, si bien que le diagramme lié plus bas ne peut pas décrire un paquet qui n'existe pas. reference_test.go tient chaque ligne de la référence des langages au scanner.

Alternatives rejetées

Forker Turbo Golo. La façon évidente d'obtenir un sixième éditeur, et la raison pour laquelle la bibliothèque existe à la place : six copies de onze mille lignes divergent en un mois, et chaque correction doit être faite six fois par quelqu'un qui se souvient qu'il y en a six.

Garder le scanner JavaScript de la bibliothèque. Cela aurait fait de celui-ci le seul éditeur de la famille sans scanner à lui, et de son utilisateur le seul sans expressions régulières colorées. Pesé plus haut.

Un système de plugins. Turbo JS 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.

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.

Embarquer Node. Certains éditeurs livrent le runtime pour lequel ils éditent. Node pèse cent mégaoctets et l'utilisateur l'a déjà ; l'éditeur est un binaire de six mégaoctets qui lui parle.

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
118
# Architecture — explication

## De quoi s'agit-il ?

Turbo JS, c'est une commande, un profil et deux scanners. 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, les fenêtres d'agent — c'est [turbo-core](https://rickub.com/turbo-editors/turbo-core), la bibliothèque sur laquelle tous les éditeurs Turbo sont construits.

Cette page parle de cette séparation : ce qui est ici, ce qui est là-bas, et pourquoi la frontière passe où elle passe.

## Ce que contient ce dépôt

```
main.go                  les options, le terminal et le câblage
internal/jslang          tout ce qui fait de cet éditeur Turbo JS
  jslang.go              le profil : nom, menu, serveur, où npm met un binaire global
  scan.go                le répartiteur du scanner JavaScript, les commentaires, les expressions régulières, ce qui franchit une ligne
  literals.go            les template literals et les nombres
  words.go               les mots-clés, les constantes, les globales, les conventions de nommage
  json.go                le scanner JSON
  templates.go           quatre déclarations //go:embed
  *.toml.tmpl            les quatre fichiers de départ d'un projet, embarqués
```

Douze cents lignes environ en comptant les commentaires, dont quelque six cents pour les deux scanners — moins de sept cents lignes de code au compte de qlty. 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.

## Ce que fait `main`

Six choses, dans cet ordre :

1. Il lit les options.
2. Il appelle `jslang.Register()`, qui apprend à la bibliothèque le JavaScript de cet éditeur — en remplaçant le scanner qu'elle livre pour ce langage — et le JSON.
3. Il construit `jslang.Profile()` — la valeur qui dit que cet éditeur est Turbo JS.
4. Il lit `.turbo-js/settings.toml` dans le répertoire courant, s'il existe.
5. Il ouvre le terminal et confie l'écran, le nom du thème et le profil à `app.New`.
6. Il démarre `typescript-language-server` dans la racine du projet — le répertoire le plus proche, au niveau du fichier édité ou au-dessus, qui contient un `package.json` — 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 JavaScript.

## Le profil est la couture

```go
profile.Profile{
	Name:        "Turbo JS",
	Slug:        "turbo-js",
	Language:    "JavaScript",
	ToolsMenu:   "~J~avaScript",
	RootMarkers: []string{"package.json"},
	Server:      profile.Server{Command: "typescript-language-server", Args: []string{"--stdio"}, },
	Templates:   profile.Templates{Settings: , Snippets: , Tools: , Agents: },
}
```

Tout ce qui serait sinon un `"turbo-js"`, un `"node"` ou un `".js"` 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.

`Slug` porte plus qu'il n'y paraît. Le binaire s'appelle `turbo-js`, le répertoire de projet `.turbo-js`, la configuration personnelle vit dans `~/.config/turbo-js`, et les variables d'environnement qui la remplacent sont `TURBO_JS_THEME_DIR` et `TURBO_JS_SNIPPET_DIR` — toutes dérivées de ce seul mot.

`RootMarkers` contient un seul fichier, `package.json`. C'est la frontière d'un projet Node — son nom, ses dépendances, ses scripts — et dans un espace de travail qui contient plusieurs paquets, le plus proche en remontant est le paquet en cours d'édition, c'est-à-dire la racine depuis laquelle le serveur doit résoudre les imports. Le `ProjectRoot` de la bibliothèque fait la remontée ; le profil dit seulement quoi chercher. Un répertoire sans aucun `package.json` au-dessus de lui reçoit le répertoire courant, et `typescript-language-server` déduit un projet des fichiers qu'on lui montre.

## Pourquoi le scanner JavaScript est ici alors que la bibliothèque en a un

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 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, et une page web ou le bloc de code d'un README est en JavaScript.

JavaScript est donc le seul langage de cette famille que la bibliothèque colore *et* pour lequel un éditeur existe. Le scanner de la bibliothèque est écrit pour le README de l'éditeur Rust : il connaît les mots-clés, les chaînes, les commentaires et les nombres, et il refuse délibérément les expressions régulières, parce que distinguer `/x/g` d'une division exige le token précédent, et qu'une mauvaise supposition à cet endroit colore le reste d'une ligne comme une chaîne — un mauvais marché pour un langage qu'on ne rencontre qu'en passant. Un éditeur *pour* JavaScript fait le marché inverse : les expressions régulières sont une ligne sur deux dans un programme Node, et la supposition peut être bornée. Il doit aussi à son utilisateur les globales de Node, la ligne shebang, le nom après `function`, les noms privés et les décorateurs.

La bibliothèque permet exactement cela. `syntax.Register` remplace un langage enregistré sous le même nom, et l'enregistrement le plus tardif l'emporte parce qu'il est l'énoncé le plus spécifique. Turbo JS s'enregistre sous le nom propre de la bibliothèque, `javascript`, si bien qu'un fichier de snippets disant `languages = ["javascript"]` et un bloc ```js dans une fenêtre d'agent atteignent tous deux ce scanner, et rien dans la bibliothèque n'a changé. La [page sur la coloration](colouring-and-completion.md) dit ce que le remplacement ajoute et ce qu'il refuse encore.

## Pourquoi JSON est ici aussi

JSON n'est pas l'un des huit de la bibliothèque, et un projet Node ne peut pas s'éditer sans lui : `package.json` est le manifeste et le marqueur de racine, `package-lock.json` est à côté, `tsconfig.json` et `.eslintrc.json` sont des voisins courants. L'éditeur qui est pour Node enregistre donc un scanner JSON, trente lignes, qui distingue une clé d'une valeur et tolère les commentaires que `tsconfig.json` contient.

Cela aurait pu aller dans la bibliothèque à la place — un éditeur Go rencontre aussi un `package.json`, dans un dépôt qui a un front-end. Cela n'a pas été fait, pour la raison qui tient les scanners Go et Rust hors de la bibliothèque : elle grossirait d'un langage chaque fois que quelqu'un en veut un, et « qu'est-ce que cet éditeur enregistre ? » cesserait d'être la première question à poser sur un nouveau venu. Si turbo-core apprend JSON un jour, cet enregistrement l'emportera toujours par l'ordre, et celui d'ici pourra alors être retiré.

## Pourquoi le menu de l'outillage s'appelle `~J~avaScript` et non `~N~ode` ou `npm`

La touche chaude était la partie facile. Dix lettres sont prises par les menus fixes — F, E, S, R, C, O, W, N, H et, depuis le menu Agent, A — ce qui exclut le S, le C et le R de JavaScript, et le N de Node, mais pas le J ; la touche chaude 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~`.

Le 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 npm : le fichier de départ lance déjà `node`, `npm` et `npx`, 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é **npm** qui contient `node main.js` est déjà un petit mensonge, et un qui contient `docker compose up` en est un gros. `JavaScript` est le langage, et le langage est ce pour quoi cet éditeur existe.

## Pourquoi le serveur de langage n'est pas celui de TypeScript 7

Il y a deux serveurs pour JavaScript aujourd'hui. `typescript-language-server` enveloppe `tsserver`, le moteur sur lequel le support JavaScript de tous les éditeurs tourne depuis dix ans ; TypeScript 7 — le portage natif — livre un serveur de langage à lui dans son compilateur, `tsc --lsp --stdio`, qui n'exige rien d'installé que `typescript`.

Les deux ont été mesurés contre les mêmes neuf tests de bout en bout de ce dépôt. Le natif répond aux huit requêtes, quatre fois plus vite. Il ne publie **aucun diagnostic** : il les propose en mode *pull*, par `textDocument/diagnostic`, que turbo-core ne demande pas, si bien qu'avec lui la gouttière reste vide sur un fichier qui ne parse pas. Un éditeur dont la gouttière est vide parce que le serveur attend qu'on lui demande ressemble exactement à un éditeur qui n'a rien à signaler, et c'est le seul échec contre lequel cette famille a appris à se prémunir. Le profil nomme donc la paire mûre — `typescript-language-server` avec TypeScript 6, la dernière version qui livre `tsserver.js` — et le dit en une ligne, là où un utilisateur la lit. Le jour où la bibliothèque saura tirer les diagnostics, `tsc --lsp --stdio`, obtenu d'un seul `npm install -g typescript`, sera la meilleure réponse, et le changement tient en deux champs du profil.

## Pourquoi les tests pilotent le vrai éditeur

`internal/jslang/editor_test.go` construit un Turbo JS entier sur un terminal simulé — `app.New(screen, "turbo-classic", jslang.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.

C'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 `.js` sort coloré par *ce* scanner (une expression régulière en est la preuve) et qu'un fichier `.ts` ne l'est pas, que `package.json` est du JSON, et que le menu Agent propose d'écrire le fichier de départ. Un bogue où `main` oublierait d'enregistrer JavaScript passerait tous les tests de turbo-core.

Le même fichier pilote un **vrai `typescript-language-server`** de bout en bout, neuf fois. Il écrit un projet avec un `package.json`, ouvre son fichier, démarre le serveur, puis :

- **tape une déclaration de fonction qui n'existe que dans le tampon**, puis tape ses premières lettres dans une autre fonction et demande une complétion — le serveur propose toutes les globales pour n'importe quel fichier, une complétion contenant `console` ne prouverait donc rien ; une qui contient une fonction absente du disque prouve que le tampon a été envoyé ;
- demande la **définition** d'un appel, les **références** à une fonction déclarée une fois et appelée deux fois, les **implémentations** d'une classe avec deux sous-classes, et la **définition de type** d'une variable contenant une instance — quatre questions, une seule forme de réponse, et deux d'entre elles reviennent en liste ;
- demande le **survol** d'un appel, qui revient avec le commentaire JSDoc écrit au-dessus de la déclaration ;
- demande les **symboles du fichier** et un symbole **dans tout le projet** par son nom ;
- 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.

`diagram_test.go` tient `docs/diagrams/packages.drawio` à `go list`, si bien que le diagramme lié plus bas ne peut pas décrire un paquet qui n'existe pas. `reference_test.go` tient chaque ligne de la [référence des langages](../reference/languages.md) au scanner.

## Alternatives rejetées

**Forker Turbo Golo.** La façon évidente d'obtenir un sixième éditeur, et la raison pour laquelle la bibliothèque existe à la place : six copies de onze mille lignes divergent en un mois, et chaque correction doit être faite six fois par quelqu'un qui se souvient qu'il y en a six.

**Garder le scanner JavaScript de la bibliothèque.** Cela aurait fait de celui-ci le seul éditeur de la famille sans scanner à lui, et de son utilisateur le seul sans expressions régulières colorées. Pesé plus haut.

**Un système de plugins.** Turbo JS 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.

**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.

**Embarquer Node.** Certains éditeurs livrent le runtime pour lequel ils éditent. Node pèse cent mégaoctets et l'utilisateur l'a déjà ; l'éditeur est un binaire de six mégaoctets qui lui parle.

## Liens avec le reste

- 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/en/reference/packages.md)
- Le diagramme des paquets, vérifié contre `go list` : [`docs/diagrams/packages.drawio`](../../diagrams/packages.drawio)
- Comment la coloration fonctionne ici : [Coloration et complétion](colouring-and-completion.md)
- Pourquoi le menu des outils est une donnée : [Outils JavaScript](javascript-tools.md)
- Les décisions qui ont survécu au refactoring : [Décisions de conception](design-decisions.md)