| 📦 Turbo Python 6fc62ea k33g 14h ago | 1 | # Architecture — explication |
| 2 | |
| 3 | ## De quoi s'agit-il ? |
| 4 | |
| 5 | 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. |
| 6 | |
| 7 | Cette page parle de cette coupure : ce qui est ici, ce qui est là-bas, et pourquoi la ligne tombe où elle tombe. |
| 8 | |
| 9 | ## Ce qu'il y a dans ce dépôt |
| 10 | |
| 11 | ``` |
| 12 | main.go les drapeaux, le terminal, et le câblage |
| 13 | internal/pythonlang la totalité de ce qui fait Turbo Python |
| 14 | pythonlang.go le profil : nom, menu, serveur, marqueurs de racine, où pylsp se cache |
| 15 | scan.go l'aiguillage de l'analyseur, les commentaires, les décorateurs |
| 16 | literals.go les seize orthographes d'un littéral chaîne |
| 17 | words.go nombres, mots-clés, primitives, les conventions de nommage |
| 18 | templates.go trois déclarations //go:embed |
| 19 | *.toml.tmpl les trois fichiers de départ qu'un projet reçoit, embarqués |
| 20 | ``` |
| 21 | |
| 22 | 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. |
| 23 | |
| 24 | ## Ce que fait `main` |
| 25 | |
| 26 | Six choses, dans cet ordre : |
| 27 | |
| 28 | 1. Analyse les drapeaux. |
| 29 | 2. Appelle `pythonlang.Register()`, qui apprend à la bibliothèque à colorer les fichiers `.py`. |
| 30 | 3. Construit `pythonlang.Profile()` — la valeur qui dit que cet éditeur est Turbo Python. |
| 31 | 4. Lit `.turbo-python/settings.toml` dans le répertoire courant, s'il y en a un. |
| 32 | 5. Ouvre le terminal et passe l'écran, le nom du thème et le profil à `app.New`. |
| 33 | 6. Démarre pylsp à la racine du projet, et lance la boucle d'événements. |
| 34 | |
| 35 | 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. |
| 36 | |
| 37 | ## Le profil est la couture |
| 38 | |
| 39 | ```go |
| 40 | profile.Profile{ |
| 41 | Name: "Turbo Python", |
| 42 | Slug: "turbo-python", |
| 43 | Language: "Python", |
| 44 | ToolsMenu: "~P~ython", |
| 45 | RootMarkers: []string{"pyproject.toml", "setup.py", "setup.cfg"}, |
| 46 | Server: profile.Server{Command: "pylsp", …}, |
| 47 | Templates: profile.Templates{Settings: …, Snippets: …, Tools: …}, |
| 48 | } |
| 49 | ``` |
| 50 | |
| 51 | 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. |
| 52 | |
| 53 | `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. |
| 54 | |
| 55 | ## Pourquoi l'analyseur est ici et pas dans la bibliothèque |
| 56 | |
| 57 | 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. |
| 58 | |
| 59 | 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. |
| 60 | |
| 61 | 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. |
| 62 | |
| 63 | ## Pourquoi le menu de la chaîne d'outils est `~P~ython` et non `~U~v` |
| 64 | |
| 65 | 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~`. |
| 66 | |
| 67 | 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. |
| 68 | |
| 69 | ## Pourquoi les tests pilotent le vrai éditeur |
| 70 | |
| 71 | `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. |
| 72 | |
| 73 | 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. |
| 74 | |
| 75 | 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 : |
| 76 | |
| 77 | - **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 ; |
| 78 | - 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 ; |
| 79 | - demande les **symboles du fichier**, encore une autre forme de réponse ; |
| 80 | - 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. |
| 81 | |
| 82 | 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. |
| 83 | |
| 84 | ## Alternatives rejetées |
| 85 | |
| 86 | **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. |
| 87 | |
| 88 | **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. |
| 89 | |
| 90 | **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. |
| 91 | |
| 92 | ## Comment cela se relie au reste |
| 93 | |
| 94 | - 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) |
| 95 | - Comment marche la coloration ici : [Coloration et complétion](colouring-and-completion.md) |
| 96 | - Pourquoi le menu d'outils est une donnée : [Outils Python](python-tools.md) |
| 97 | - Les décisions qui ont survécu au refactoring : [Décisions de conception](design-decisions.md) |