- En raison des limites de l’éditeur Howl (développement arrêté, recherche lente, incompatibilité SSH, absence de prise en charge du terminal), l’auteur a développé lui-même un nouvel éditeur de texte en TUI
- Il a essayé 13 éditeurs, dont Helix, VS Code, Vim, Neovim et Emacs, mais aucun ne répondait au ressenti de manipulation (Fingerspitzengefühl) qu’il recherchait
- Au départ, il n’a implémenté qu’un minimum de fonctionnalités adaptées à ses besoins personnels, en repoussant à plus tard les performances, Unicode et le support multilingue, puis a étendu progressivement l’outil
- Pendant le développement, il a implémenté lui-même un moteur d’expressions régulières, un navigateur de fichiers, le rendu basé sur une TUI et l’intégration d’un buffer de terminal
- Il a appliqué de nombreuses techniques d’optimisation des performances dans la recherche globale du projet, la coloration syntaxique, la gestion du cache et la répartition des tâches multithread
- Au final, il explique avoir créé un outil parfaitement adapté à son propre workflow, retrouvant à la fois sa productivité et le plaisir de programmer
Limites des éditeurs existants et recherche d’alternatives
- Les problèmes de l’éditeur Howl, qu’il utilisait depuis environ 10 ans, ont été le déclencheur de ce développement
- Son développement était arrêté depuis des années, et bien qu’il maintenait lui-même un fork, l’éditeur étant écrit en MoonScript, il était difficile d’y apporter des modifications en profondeur
- Les performances de recherche de fichiers à l’échelle du projet étaient insuffisantes, ce qui cassait son flux de travail
- Comme c’était un éditeur GUI, il était impossible de l’utiliser à distance via une connexion SSH
- Il n’avait pas de terminal intégré, ce qui empêchait les interactions en direct avec des commandes externes, et la plupart des codes d’échappement ANSI n’étaient pas pris en charge
- Il a essayé 13 éditeurs, dont Helix, VS Code, Sublime Text, Vim, Zed, Neovim, Emacs, Geany, Micro, Lite XL, Lapce, GNOME Builder et Kakoune
- Chacun avait ses qualités, mais aucun ne procurait le ressenti de manipulation (Fingerspitzengefühl) recherché
- Helix est celui qu’il a utilisé le plus longtemps, avant de s’en lasser au bout d’un mois
Stratégie de développement initiale
- Au démarrage, il a limité au maximum le périmètre
- Il a exclu toute fonctionnalité pensée pour d’autres utilisateurs et a codé en dur tous les réglages
- Il a repoussé l’optimisation des performances et a commencé avec un buffer basé sur
String
- Il n’a pas visé une prise en charge complète des graphèmes Unicode, considérant qu’il suffisait que le symbole
£ occupe une seule colonne
- Pour la coloration syntaxique, il n’a pris en charge qu’un petit nombre de langages qu’il utilise principalement, les autres étant gérés par un surlignage générique basé sur les délimiteurs
- Lors de la deuxième tentative, il a d’abord construit un petit framework TUI, mais avec le temps il en a retiré la majeure partie pour passer à une approche plus directe et plus fine
Mise en pratique du dogfooding
- Une fois l’éditeur arrivé à un seuil minimal de fonctionnalités permettant d’ouvrir, modifier et enregistrer un seul fichier, il a adopté trois pratiques
- Utiliser son propre éditeur à la place de
nano, afin de s’y contraindre pour modifier des fichiers système ou prendre des notes
- Noter dans le
README.md du projet chaque fonctionnalité manquante, bug, comportement étrange ou limite rencontrés
- Corriger immédiatement les problèmes suffisamment agaçants
- Grâce à ces trois pratiques, sa charge de travail est passée d’une heure par mois à plusieurs heures par semaine
- Sur un total d’environ 10 000 lignes de code, presque tout a été écrit au cours des 6 derniers mois
Manipulation du curseur
- La manipulation du curseur est un domaine difficile à implémenter
- Des combinaisons de touches comme
ctrl + shift + left paraissent évidentes pour l’utilisateur, mais leur logique est complexe à implémenter
- Le conseil clé est d’implémenter les entrées de haut niveau comme des combinaisons d’opérations primitives
- Exemple : retour arrière par mot → déplacement du curseur par mot + sélection d’une plage + suppression
- Pour implémenter undo/redo, il faut regrouper ces 3 actions en un seul ensemble afin d’obtenir un résultat intuitif
- Cela lui a permis de comprendre pourquoi les éditeurs modaux exposent directement ces opérations primitives à l’utilisateur
Navigateur de fichiers
- Le navigateur de fichiers de Howl était la raison décisive qui l’empêchait de passer à un autre éditeur
- Son filtre flou mis à jour instantanément était excellent, au point que 1 ou 2 frappes suffisaient souvent pour trouver le fichier voulu
- Si le fichier n’existait pas, il pouvait être créé directement en ligne
- En saisissant
~/, le navigateur basculait automatiquement vers le répertoire personnel
- Il affichait un aperçu du fichier à ouvrir dans la fenêtre principale d’édition
- Il reproche aux autres éditeurs de résoudre l’ouverture des fichiers par des moyens comme la dépendance à la souris, les boîtes de dialogue GTK par défaut ou la supposition de noms de fichiers
- Dans sa propre implémentation, trois critères simples suffisent, plutôt qu’une méthode complexe comme la Levenshtein distance
- Si le nom commence par la chaîne de filtre
- S’il contient la chaîne de filtre
- La date de modification/accès la plus récente
- Il autorise les correspondances insensibles à la casse, mais donne un léger bonus lorsque la casse correspond exactement
- Même dans des projets contenant des dizaines de milliers de fichiers, après 2 frappes il trouve le fichier voulu dans les 2 premiers résultats avec une probabilité d’environ 95 %
Moteur d’expressions régulières
- Les expressions régulières sont utilisées à trois endroits : recherche globale du projet, coloration syntaxique et recherche dans le buffer
- Il explique pourquoi il a choisi de l’implémenter lui-même plutôt que d’utiliser la crate
regex-automata existante
- Il devait gérer des cas limites sensibles au contexte, comme la syntaxe des raw strings de Rust
- Le projet lui-même était aussi un exercice visant à construire et comprendre sa propre stack
- L’implémentation initiale utilisait la crate de parsing
chumsky pour analyser la syntaxe regex, puis parcourait l’AST caractère par caractère, ce qui était lent
- Il a ensuite effectué des optimisations progressives
- Optimiseur en un seul passage : transformation des groupes de correspondance de caractères répétés en un seul nœud
String, permettant des recherches exactes de chaînes
- Extraction de préfixes communs : par exemple, dans
hel[(lo)p], repérage du préfixe commun hel pour ne lancer la correspondance qu’à cet endroit → gros gain de performances dans la recherche globale du projet
- Réécriture du walker d’AST en VM à threaded code reposant sur des appels dynamiques Rust
- Transformation de cette VM en style CPS (Continuation-Passing Style), où chaque instruction de VM effectue un tail-call vers l’instruction suivante pour profiter des optimisations du compilateur
- Encapsulation des appels dynamiques lents de Rust sans lookup de vtable, réduisant le code généré de nombreuses instructions regex à quelques instructions machine
- Implémentation du plus grand nombre possible d’instructions regex au niveau octet plutôt qu’au niveau des points de code Unicode ; grâce à la conception d’UTF-8, les optimisations ASCII restent efficaces même avec des points de code multioctets
- Il a aussi tenté une compilation en chaîne de jump LUT, mais les benchmarks n’ont montré qu’un gain de 20 à 30 % par rapport au threaded code, au prix d’une forte perte de flexibilité, donc il ne l’a pas retenue
- Résultat final : sur la coloration syntaxique Rust la plus complexe, il peut surligner entièrement un fichier de bindings auto-généré de 50 000 lignes en moins de 10 millisecondes à froid
Cache de coloration syntaxique
- Au départ, chaque modification déclenchait une recoloration complète du fichier, ce qui dégradait les performances sur les gros fichiers
- Il a implémenté un cache de coloration des tokens à la demande
- Les tokens sont colorés par chunks d’une taille approximativement identique
- Lorsqu’une modification (damage) survient dans le buffer, seuls les chunks qui chevauchent cette position ou la suivent sont invalidés
- Même dans le pire cas (édition au milieu d’un gros fichier), l’état de coloration avant le point de modification est conservé, et ce qui se trouve plus bas à l’écran n’a pas besoin d’être traité tant qu’aucune information de coloration n’est demandée
- Comme il s’agit d’une approche pilotée par la demande, cela fonctionne aussi correctement avec plusieurs panneaux affichant différentes parties du même buffer
Recherche globale dans le projet
- Le processus de recherche comporte 4 étapes
- Déterminer la racine du projet en remontant depuis le répertoire courant jusqu’à trouver un répertoire
.git/
- Parcourir récursivement tous les répertoires de la racine du projet et faire correspondre le motif de recherche au contenu des fichiers
- Pour chaque correspondance positive, extraire un extrait du fichier et appliquer la coloration syntaxique pour l’aperçu des résultats
- Classer les résultats selon la distance de navigation depuis le chemin courant (les fichiers les plus proches étant les mieux classés)
- Des règles de filtrage par défaut sont appliquées pour éviter les répertoires de build, etc.
- Le traitement est multithread, avec une répartition du travail entre threads basée sur un mécanisme simple de work-stealing
- Il a résolu le problème de détection de terminaison dans une structure particulière où tous les threads sont à la fois consommateurs et producteurs
- Un thread en attente incrémente un compteur atomique, et lorsque ce compteur atteint le nombre de workers et que la file de tâches est vide, tout le traitement s’arrête
- Grâce aux optimisations regex et à la vitesse des SSD modernes, une recherche de motif simple dans une grosse base de code comme Veloren s’exécute quasiment instantanément
- Sur les flamegraphs, l’application est majoritairement IO-bound
- Le fait de pouvoir chercher dans une grosse base de code à la vitesse de la pensée depuis l’éditeur contribue fortement à sa productivité
Buffer d’émulateur de terminal
- Dans un éditeur à panneaux, il est très pratique de pouvoir utiliser un panneau comme fenêtre de terminal
- Il avait envisagé d’implémenter lui-même un parseur ANSI, mais la prise en charge de fonctions modernes de rendu terminal comme OSC52 ou les extensions clavier Kitty est trop vaste
- Il a donc utilisé la crate
alacritty_terminal afin de réemployer le parseur de séquences d’échappement et la logique de gestion d’état du terminal de l’émulateur Alacritty
- Au final, cela permet de remplacer les fonctions essentielles de
screen/tmux, tout en offrant une prise en charge plus riche des séquences d’échappement
Optimisation du rendu
- Même en TUI, la bande passante reste importante lors de connexions mobiles à distance
- Double buffering : l’éditeur conserve deux copies internes de l’écran du terminal
- Lors du rafraîchissement, il compare l’image précédente à la nouvelle et n’émet des séquences d’échappement ANSI que pour les cellules modifiées
- Les séquences de déplacement du curseur, de changement de mode de style, etc., ne sont également émises que lorsqu’elles sont réellement nécessaires
- Dans la plupart des émulateurs de terminal (sauf Ghostty), afficher un gros fichier avec
cat dans le panneau terminal de l’éditeur puis fermer l’éditeur est plus rapide que faire directement cat dans le terminal hôte
- Parce que
alacritty_terminal bloque le coût de traitement des octets stdout vers le terminal hôte
Conclusion : créez votre propre outil
- L’éditeur qu’il a créé est devenu un outil parfaitement adapté à son flux de travail
- Il s’oppose à l’idée reçue selon laquelle créer son propre éditeur ou ses propres outils serait une souffrance inutile
- Il met en avant quatre avantages
- Personnalisation parfaite : l’outil fait exactement ce qu’il veut, ni plus ni moins
- Apprentissage de nombreuses techniques : expressions régulières, ANSI, pseudo-terminal (pty), design TUI, détails d’UTF-8, etc., avec une compréhension approfondie de compétences largement utiles
- Gain de productivité à long terme : en comprenant parfaitement son outil et en y intégrant des fonctions adaptées à son workflow personnel, il réduit les frictions avec ses outils
- Plaisir pur : résoudre des problèmes autonomes et en sentir le résultat au bout des doigts a ravivé son amour de la programmation, au point de coder en souriant et de rire tout seul pour la première fois depuis des années
- Même si ce n’est pas un éditeur de texte, il recommande de créer son propre outil, et insiste sur l’importance de prendre plaisir au défi lui-même plutôt que de déléguer les parties difficiles à une boîte à statistiques (IA, etc.)
Aucun commentaire pour le moment.