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

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

📦 Turbo Python 6fc62ea · on v1.0.1 · k33g · 11h ago
architecture.md · 97 lines · 8.3 KBmarkdown
Blame HistoryOpen raw

Architecture — explication

De quoi s'agit-il ?

Turbo Python 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.

Cette page parle de cette coupure : ce qui est ici, ce qui est là-bas, et pourquoi la ligne tombe où elle tombe.

Ce qu'il y a dans ce dépôt

main.go                  les drapeaux, le terminal, et le câblage
internal/pythonlang      la totalité de ce qui fait Turbo Python
  pythonlang.go          le profil : nom, menu, serveur, marqueurs de racine, où pylsp se cache
  scan.go                l'aiguillage de l'analyseur, les commentaires, les décorateurs
  literals.go            les seize orthographes d'un littéral chaîne
  words.go               nombres, mots-clés, primitives, les conventions de nommage
  templates.go           trois déclarations //go:embed
  *.toml.tmpl            les trois fichiers de départ qu'un projet reçoit, embarqués

Environ huit cents lignes, dont six cents pour l'analyseur. 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 s'en servent sans les modifier.

Ce que fait main

Six choses, dans cet ordre :

  1. Analyse les drapeaux.
  2. Appelle pythonlang.Register(), qui apprend à la bibliothèque à colorer les fichiers .py.
  3. Construit pythonlang.Profile() — la valeur qui dit que cet éditeur est Turbo Python.
  4. Lit .turbo-python/settings.toml dans le répertoire courant, s'il y en a un.
  5. Ouvre le terminal et passe l'écran, le nom du thème et le profil à app.New.
  6. Démarre pylsp à la racine du projet, 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 Python.

Le profil est la couture

profile.Profile{
	Name:        "Turbo Python",
	Slug:        "turbo-python",
	Language:    "Python",
	ToolsMenu:   "~P~ython",
	RootMarkers: []string{"pyproject.toml", "setup.py", "setup.cfg"},
	Server:      profile.Server{Command: "pylsp", …},
	Templates:   profile.Templates{Settings: …, Snippets: …, Tools: …},
}

Tout ce qui serait sinon un "turbo-python", un "pylsp" ou un "pyproject.toml" 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-python, le répertoire de projet est .turbo-python, la configuration de l'utilisateur vit dans ~/.config/turbo-python, et les variables d'environnement qui la remplacent sont TURBO_PYTHON_THEME_DIR et TURBO_PYTHON_SNIPPET_DIR — toutes dérivées de ce seul mot.

Pourquoi l'analyseur 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.

Python n'en fait pas partie, et Go et Rust non plus. 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 .py s'ouvre en texte brut dans Turbo Rust.

L'inverse était possible. Mettre les trois analyseurs dans la bibliothèque permettrait à chaque éditeur de colorer n'importe lequel des langages, sans coût en dépendances — un analyseur Python n'est que du Go ordinaire. Cela a été rejeté parce que la bibliothèque gagnerait alors 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 qu'on pose sur un nouvel éditeur.

Pourquoi le menu de la chaîne d'outils est ~P~ython et non ~U~v

La touche rapide était la partie facile. Neuf lettres sont prises par les menus fixes — F, E, S, R, C, O, W, N et H — et P 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~.

Le nom, lui, était la vraie décision, et elle a tourné comme celle de Turbo Rust. Le menu contient ce que le projet a mis dans son fichier d'outils, et ce n'est pas toujours uv : le premier fichier d'outils que quiconque écrit déborde de la chaîne d'outils du langage, parce que les commandes d'un projet incluent des conteneurs, des bases de données et une cible de Makefile que quelqu'un a ajoutée en 2019. Un menu appelé uv contenant docker compose up est un mensonge sur ce qu'est le menu, exactement de la façon dont la documentation de la bibliothèque met en garde. Python est le langage, et le langage est ce pour quoi cet éditeur existe.

Pourquoi les tests pilotent le vrai éditeur

internal/pythonlang/editor_test.go construit un Turbo Python entier sur un terminal simulé — app.New(screen, "turbo-classic", pythonlang.Profile()) — ouvre un fichier et vérifie la coloration, la barre de menus et les touches rapides. 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 marche ; 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 .py ressort coloré. Un bug où main aurait oublié d'enregistrer Python passerait tous les tests de turbo-core.

Le même fichier pilote un vrai pylsp de bout en bout, quatre fois. Il écrit un projet, ouvre un fichier, démarre le serveur, puis :

  • tape un texte qui n'existe que dans le tampon et demande une complétion — un texte déjà présent sur le disque ne prouverait rien, puisque le serveur répond depuis le disque pour tout ce qu'on ne lui a pas dit être ouvert ;
  • demande les références d'un nom utilisé à trois endroits, c'est-à-dire la forme de réponse qui était autrefois tronquée à un seul élément ;
  • demande les symboles du fichier, encore une autre forme de réponse ;
  • ouvre un fichier qui ne s'analyse pas et attend qu'un diagnostic arrive de lui-même — la seule fonction dont l'échec ressemble exactement à la réussite, puisqu'un éditeur qui n'a aucune erreur à montrer et un éditeur incapable de trouver l'erreur ont la même gouttière vide.

Un cinquième test épingle ce que pylsp ne peut pas faire : il n'annonce ni implementation ni workspace/symbol, la documentation le dit, et le test échoue si un futur pylsp se met à répondre — la page est alors relue plutôt que de vieillir en silence.

Alternatives rejetées

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

Un système de greffons. Turbo Python est un programme Go qui importe une bibliothèque. Il n'y a ni chargement dynamique ni ABI. En ajouter un voudrait dire figer l'API de tous les paquets de turbo-core plutôt que de la poignée 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 l'analyseur inexprimable, et un éditeur à moitié configurable — tout sauf la coloration — est pire que l'une ou l'autre réponse entière.

Comment cela se relie au 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
# Architecture — explication

## De quoi s'agit-il ?

Turbo Python 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](https://rickub.com/turbo-editors/turbo-core), la bibliothèque sur laquelle repose chaque éditeur Turbo.

Cette page parle de cette coupure : ce qui est ici, ce qui est là-bas, et pourquoi la ligne tombe où elle tombe.

## Ce qu'il y a dans ce dépôt

```
main.go                  les drapeaux, le terminal, et le câblage
internal/pythonlang      la totalité de ce qui fait Turbo Python
  pythonlang.go          le profil : nom, menu, serveur, marqueurs de racine, où pylsp se cache
  scan.go                l'aiguillage de l'analyseur, les commentaires, les décorateurs
  literals.go            les seize orthographes d'un littéral chaîne
  words.go               nombres, mots-clés, primitives, les conventions de nommage
  templates.go           trois déclarations //go:embed
  *.toml.tmpl            les trois fichiers de départ qu'un projet reçoit, embarqués
```

Environ huit cents lignes, dont six cents pour l'analyseur. 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 s'en servent sans les modifier.

## Ce que fait `main`

Six choses, dans cet ordre :

1. Analyse les drapeaux.
2. Appelle `pythonlang.Register()`, qui apprend à la bibliothèque à colorer les fichiers `.py`.
3. Construit `pythonlang.Profile()` — la valeur qui dit que cet éditeur est Turbo Python.
4. Lit `.turbo-python/settings.toml` dans le répertoire courant, s'il y en a un.
5. Ouvre le terminal et passe l'écran, le nom du thème et le profil à `app.New`.
6. Démarre pylsp à la racine du projet, 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 Python.

## Le profil est la couture

```go
profile.Profile{
	Name:        "Turbo Python",
	Slug:        "turbo-python",
	Language:    "Python",
	ToolsMenu:   "~P~ython",
	RootMarkers: []string{"pyproject.toml", "setup.py", "setup.cfg"},
	Server:      profile.Server{Command: "pylsp", },
	Templates:   profile.Templates{Settings: , Snippets: , Tools: },
}
```

Tout ce qui serait sinon un `"turbo-python"`, un `"pylsp"` ou un `"pyproject.toml"` 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-python`, le répertoire de projet est `.turbo-python`, la configuration de l'utilisateur vit dans `~/.config/turbo-python`, et les variables d'environnement qui la remplacent sont `TURBO_PYTHON_THEME_DIR` et `TURBO_PYTHON_SNIPPET_DIR` — toutes dérivées de ce seul mot.

## Pourquoi l'analyseur 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.

Python n'en fait pas partie, et Go et Rust non plus. 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 `.py` s'ouvre en texte brut dans Turbo Rust.

L'inverse était possible. Mettre les trois analyseurs dans la bibliothèque permettrait à chaque éditeur de colorer n'importe lequel des langages, sans coût en dépendances — un analyseur Python n'est que du Go ordinaire. Cela a été rejeté parce que la bibliothèque gagnerait alors 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 qu'on pose sur un nouvel éditeur.

## Pourquoi le menu de la chaîne d'outils est `~P~ython` et non `~U~v`

La touche rapide était la partie facile. Neuf lettres sont prises par les menus fixes — F, E, S, R, C, O, W, N et H — et `P` 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~`.

Le nom, lui, était la vraie décision, et elle a tourné comme celle de Turbo Rust. Le menu contient ce que le projet a mis dans son fichier d'outils, et ce n'est pas toujours uv : le premier fichier d'outils que quiconque écrit déborde de la chaîne d'outils du langage, parce que les commandes d'un projet incluent des conteneurs, des bases de données et une cible de `Makefile` que quelqu'un a ajoutée en 2019. Un menu appelé uv contenant `docker compose up` est un mensonge sur ce qu'est le menu, exactement de la façon dont la documentation de la bibliothèque met en garde. `Python` est le langage, et le langage est ce pour quoi cet éditeur existe.

## Pourquoi les tests pilotent le vrai éditeur

`internal/pythonlang/editor_test.go` construit un Turbo Python entier sur un terminal simulé — `app.New(screen, "turbo-classic", pythonlang.Profile())` — ouvre un fichier et vérifie la coloration, la barre de menus et les touches rapides. 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 marche ; 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 `.py` ressort coloré. Un bug où `main` aurait oublié d'enregistrer Python passerait tous les tests de turbo-core.

Le même fichier pilote un **vrai pylsp** de bout en bout, quatre fois. Il écrit un projet, ouvre un fichier, démarre le serveur, puis :

- **tape un texte qui n'existe que dans le tampon** et demande une complétion — un texte déjà présent sur le disque ne prouverait rien, puisque le serveur répond depuis le disque pour tout ce qu'on ne lui a pas dit être ouvert ;
- demande les **références** d'un nom utilisé à trois endroits, c'est-à-dire la forme de réponse qui était autrefois tronquée à un seul élément ;
- demande les **symboles du fichier**, encore une autre forme de réponse ;
- ouvre un fichier qui **ne s'analyse pas** et attend qu'un diagnostic arrive de lui-même — la seule fonction dont l'échec ressemble exactement à la réussite, puisqu'un éditeur qui n'a aucune erreur à montrer et un éditeur incapable de trouver l'erreur ont la même gouttière vide.

Un cinquième test épingle ce que pylsp *ne peut pas* faire : il n'annonce ni `implementation` ni `workspace/symbol`, la documentation le dit, et le test échoue si un futur pylsp se met à répondre — la page est alors relue plutôt que de vieillir en silence.

## Alternatives rejetées

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

**Un système de greffons.** Turbo Python est un programme Go qui importe une bibliothèque. Il n'y a ni chargement dynamique ni ABI. En ajouter un voudrait dire figer l'API de tous les paquets de turbo-core plutôt que de la poignée 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 l'analyseur inexprimable, et un éditeur à moitié configurable — tout sauf la coloration — est pire que l'une ou l'autre réponse entière.

## Comment cela se relie au 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/fr/reference/packages.md)
- Comment marche la coloration ici : [Coloration et complétion](colouring-and-completion.md)
- Pourquoi le menu d'outils est une donnée : [Outils Python](python-tools.md)
- Les décisions qui ont survécu au refactoring : [Décisions de conception](design-decisions.md)