- Wegen der Grenzen des bisherigen Howl-Editors (eingestellte Entwicklung, langsame Suche, keine SSH-Kompatibilität, keine Terminal-Unterstützung) wurde ein neuer TUI-Texteditor selbst entwickelt
- 13 Editoren wie Helix, VS Code, Vim, Neovim und Emacs wurden ausprobiert, aber keiner erfüllte das gewünschte Bediengefühl (Fingerspitzengefühl)
- Anfangs wurden nur minimal notwendige, persönliche Funktionen umgesetzt; Performance, Unicode- und Mehrsprachen-Support wurden nachrangig behandelt und schrittweise ausgebaut
- Im Entwicklungsprozess wurden unter anderem eine eigene Regex-Engine, ein Dateibrowser, TUI-basiertes Rendering und die Integration eines Terminal-Buffers selbst implementiert
- Für projektweite Suche, Syntax-Highlighting, Caching und die Verteilung von Arbeit über mehrere Threads hinweg wurden zahlreiche Techniken zur Performance-Optimierung eingesetzt
- Am Ende entstand ein Werkzeug, das perfekt zum eigenen Workflow passt und laut Autor Produktivität und Freude am Programmieren zurückgebracht hat
Grenzen bestehender Editoren und die Suche nach Alternativen
- Die Probleme des etwa 10 Jahre lang genutzten Howl-Editors waren der Auslöser für die Eigenentwicklung
- Die Entwicklung war seit Jahren eingestellt; ein eigener Fork wurde gepflegt, doch tiefgreifende Änderungen waren schwierig, da er in MoonScript geschrieben ist
- Die Dateisuche über das gesamte Projekt war zu langsam und unterbrach den Arbeitsfluss
- Als GUI-Editor war die Nutzung über SSH für Remote-Arbeit nicht möglich
- Es gab kein integriertes Terminal, daher war bei externen Befehlen keine Live-Interaktion möglich, und die meisten ANSI-Escape-Codes wurden nicht unterstützt
- 13 Editoren wurden ausprobiert, darunter Helix, VS Code, Sublime Text, Vim, Zed, Neovim, Emacs, Geany, Micro, Lite XL, Lapce, GNOME Builder und Kakoune
- Jeder hatte eigene Stärken, erfüllte aber nicht das gewünschte Bediengefühl (Fingerspitzengefühl)
- Helix wurde am längsten genutzt, verlor aber nach einem Monat seinen Reiz
Frühe Entwicklungsstrategie
- Zu Beginn wurde der Umfang bewusst minimal gehalten
- Funktionen für andere Nutzer wurden ausgeschlossen, sämtliche Konfigurationen hartcodiert
- Performance-Optimierung wurde aufgeschoben und zunächst mit einem
String-basierten Buffer gearbeitet
- Vollständige Unterstützung von Unicode-Graphemen wurde weggelassen; es genügte, wenn das Symbol
£ eine einzelne Spalte belegte
- Syntax-Highlighting unterstützte nur wenige häufig genutzte Sprachen; für den Rest wurde generisches delimiter-basiertes Highlighting verwendet
- Im zweiten Anlauf wurde zunächst ein einfaches TUI-Framework gebaut, das später größtenteils wieder entfernt und durch einen direkteren, feineren Ansatz ersetzt wurde
Dogfooding in der Praxis
- Nachdem der Editor den minimalen Funktionsschwellenwert erreicht hatte, um eine einzelne Datei zu öffnen, zu bearbeiten und zu speichern, wurden drei Praktiken eingeführt
- Statt
nano wurde der eigene Editor verwendet und so zwangsweise eingesetzt, etwa zum Bearbeiten von Systemdateien oder für Notizen
- Immer wenn fehlende Funktionen, Bugs, seltsames Verhalten oder Grenzen auffielen, wurden sie in der
README.md des Projekts dokumentiert
- Probleme, die wirklich störten, wurden sofort behoben
- Dadurch stieg der Arbeitsaufwand von etwa einer Stunde im Monat auf mehrere Stunden pro Woche
- Von insgesamt rund 10.000 Zeilen Code wurde fast alles in den letzten sechs Monaten geschrieben
Cursor-Steuerung
- Die Cursor-Steuerung ist ein besonders schwer umzusetzender Bereich
- Tastenkombinationen wie
ctrl + shift + left wirken für Nutzer selbstverständlich, sind in der Logik aber komplex
- Ein zentraler Rat ist, höherwertige Eingaben als Kombination primitiver Operationen zu implementieren
- Beispiel: Backspace auf Wortebene → Cursorbewegung auf Wortebene + Bereichsauswahl + Löschen
- Für Undo/Redo müssen diese drei Schritte zu einer einzigen Gruppe zusammengefasst werden, damit das Ergebnis intuitiv bleibt
- Dadurch wurde verständlich, warum modale Editoren solche primitiven Operationen direkt offenlegen
Dateibrowser
- Der Dateibrowser von Howl war der entscheidende Grund, warum der Wechsel zu anderen Editoren scheiterte
- Sein sofort aktualisierter Fuzzy-Filter war so gut, dass meist schon 1–2 Tastenanschläge genügten, um die gewünschte Datei zu finden
- Wenn die Datei nicht existierte, konnte sie inline erstellt werden
- Bei Eingabe von
~/ wurde automatisch ins Home-Verzeichnis gewechselt
- Es gab eine Vorschau der zu öffnenden Datei im Hauptbearbeitungsfenster
- Kritisiert wurde, dass andere Editoren das Öffnen von Dateien über Mausabhängigkeit, GTK-Standarddialoge oder Dateinamensraten lösen
- Bei der Eigenimplementierung erwiesen sich statt komplexer Verfahren wie Levenshtein distance drei einfache Kriterien als ausreichend
- Ob ein Pfad mit dem Filterausdruck beginnt
- Ob er den Filterausdruck enthält
- Die zuletzt erfolgte Änderungs- bzw. Zugriffszeit
- Groß-/Kleinschreibung wird ignoriert, bei exakter Übereinstimmung aber leicht höher gewichtet
- Selbst in Projekten mit Zehntausenden Dateien liegt die gewünschte Datei nach 2 Tastenanschlägen mit etwa 95 % Wahrscheinlichkeit unter den ersten beiden Treffern
Regex-Engine
- Regex werden an drei Stellen verwendet: projektweite Suche, Syntax-Highlighting und Suchen im Buffer
- Statt der bestehenden Crate
regex-automata wurde eine eigene Implementierung gewählt
- Es mussten kontextsensitive Edge Cases wie Rusts Raw-String-Syntax behandelt werden
- Das Projekt diente auch als Übung, einen eigenen Stack aufzubauen und zu verstehen
- Die erste Implementierung parste die Regex-Syntax mit der Parsing-Crate
chumsky und durchlief den AST bei jedem Zeichen, was langsam war
- Danach folgten schrittweise Optimierungen
- Single-Pass-Optimierer: Wiederkehrende Gruppen von Zeichen-Matches wurden in einen einzelnen
String-Knoten umgewandelt, um exakte String-Suchen auszuführen
- Extraktion gemeinsamer Präfixe: Zum Beispiel wird in
hel[(lo)p] das gemeinsame Präfix hel erkannt und nur dort gematcht → großer Performance-Gewinn bei projektweiter Suche
- Der AST-Walker wurde als threaded-code-VM auf Basis dynamischer Rust-Aufrufe neu implementiert
- Die threaded-code-VM wurde in CPS (Continuation-Passing Style) umgeformt, sodass jede VM-Instruktion die nächste per Tail-Call aufruft und Compiler-Optimierungen nutzen kann
- Langsame dynamische Funktionsaufrufe in Rust wurden ohne vtable-Lookup gekapselt, sodass der Codegen vieler Regex-Instruktionen auf wenige Maschinenbefehle schrumpfte
- So viele Regex-Instruktionen wie möglich wurden auf Byte-Ebene statt Unicode-Codepoints umgesetzt; dank des UTF-8-Designs funktionieren ASCII-Optimierungen auch bei Multi-Byte-Codepoints
- Es wurde auch versucht, in Jump-LUT-Ketten zu kompilieren, aber Benchmarks zeigten nur 20–30 % mehr Geschwindigkeit gegenüber threaded code bei deutlich geringerer Flexibilität, daher wurde der Ansatz verworfen
- Endergebnis: Beim komplexesten Syntax-Highlighting für Rust wird eine automatisch generierte Binding-Datei mit 50.000 Zeilen im kalten Zustand in unter 10 Millisekunden komplett hervorgehoben
Syntax-Highlighting-Cache
- Anfangs wurde bei jeder Änderung die gesamte Datei neu hervorgehoben, was bei großen Dateien zu Performance-Problemen führte
- Implementiert wurde ein On-Demand-Cache für Token-Highlighting
- Tokens werden in Chunks ähnlicher Größe hervorgehoben
- Wenn Änderungen (damage) im Buffer auftreten, werden nur Chunks, die sich mit dieser Position überschneiden oder danach liegen, invalidiert
- Selbst im pessimistischsten Fall (Bearbeitung in der Mitte einer großen Datei) bleibt der Highlighting-Zustand vor dem Schaden erhalten; unterhalb des sichtbaren Bereichs ist keine Verarbeitung nötig, weil dort keine Highlighting-Informationen angefordert werden
- Durch den demand-driven Ansatz funktioniert das auch korrekt mit mehreren Panels, die verschiedene Teile desselben Buffers anzeigen
Projektweite Suche
- Der Suchprozess besteht aus vier Schritten
- Vom aktuellen Verzeichnis aus wird rückwärts nach einem
.git/-Verzeichnis gesucht, um den Projekt-Root zu bestimmen
- Alle Verzeichnisse unterhalb des Projekt-Roots werden rekursiv durchlaufen und das Suchmuster mit Dateiinhalten abgeglichen
- Für jedes positive Match wird ein Dateisnippet extrahiert und zur Ergebnisvorschau Syntax-Highlighting angewendet
- Ergebnisse werden anhand der Navigationsdistanz vom aktuellen Pfad sortiert; nähere Dateien erhalten eine höhere Priorität
- Es gibt standardmäßige Filterregeln, um etwa Build-Verzeichnisse zu vermeiden
- Die Verarbeitung ist multithreaded, mit grundlegender Work-Stealing-Verteilung zwischen Threads
- In einer speziellen Struktur, in der alle Threads zugleich Produzenten und Konsumenten sind, musste das Problem der Termination Detection gelöst werden
- Wartende Threads erhöhen einen atomaren Zähler; erreicht dieser die Zahl der Worker und ist die Aufgabenwarteschlange leer, wird global beendet
- Dank Regex-Optimierungen und moderner SSD-Geschwindigkeit ist eine einfache Mustersuche selbst in großen Codebasen wie Veloren nahezu sofort abgeschlossen
- Im Flamegraph ist der Prozess größtenteils IO-bound
- Große Codebasen im Editor mit Gedankengeschwindigkeit durchsuchen zu können, trägt stark zur Produktivität bei
Terminal-Emulator-Buffer
- In einem panelbasierten Editor ist es sehr praktisch, ein Panel als Terminalfenster zu verwenden
- Ein eigener ANSI-Parser war geplant, doch die Unterstützung moderner Terminalfunktionen wie OSC52 und der Kitty Keyboard Protocol Extensions ist sehr umfangreich
- Deshalb wird die Crate
alacritty_terminal genutzt, um den Escape-Sequence-Parser und die Terminal-Zustandsverwaltung des Alacritty-Terminalemulators wiederzuverwenden
- Dadurch lassen sich Kernfunktionen von
screen/tmux ersetzen und sogar reichhaltigere Escape-Sequence-Unterstützung bereitstellen
Rendering-Optimierung
- Trotz TUI bleibt Bandbreite bei Remote-Verbindungen auf Mobilgeräten wichtig
- Double Buffering: Es werden zwei interne Kopien des Terminalbildschirms vorgehalten
- Beim Neuzeichnen wird mit dem vorherigen Frame verglichen und nur für geänderte Zellen ANSI-Escape-Sequenzen ausgegeben
- Auch Sequenzen für Cursorbewegungen oder Stilwechsel werden nur bei tatsächlichem Bedarf ausgegeben
- In den meisten Terminalemulatoren (außer Ghostty) ist es schneller, eine große Datei im Terminal-Panel des Editors mit
cat auszugeben und danach den Editor zu schließen, als direkt im Host-Terminal cat auszuführen
- Der Grund ist, dass
alacritty_terminal die Kosten der stdout-Byteverarbeitung gegenüber dem Host-Terminal abfängt
Fazit: Baut eure eigenen Werkzeuge
- Der selbst gebaute Editor ist zu einem Werkzeug geworden, das perfekt zum eigenen Workflow passt
- Der Autor widerspricht der verbreiteten Annahme, eigene Editoren oder Tools zu bauen sei sinnloses Leiden
- Vier Vorteile werden genannt
- Perfekte Anpassung: Das Werkzeug tut genau das, was man will – nicht mehr und nicht weniger
- Lernen vielfältiger Technologien: Tiefes Verständnis für Regex, ANSI, Pseudoterminals (pty), TUI-Design, Details von UTF-8 und andere breit nützliche Techniken
- Langfristige Produktivitätssteigerung: Das eigene Werkzeug wird vollständig verstanden und auf den persönlichen Workflow zugeschnitten, wodurch Reibung mit dem Tool sinkt
- Reine Freude: Das Lösen in sich geschlossener Probleme und das direkte Erleben des Ergebnisses an den Fingerspitzen entfachte die Liebe zum Programmieren neu; zum ersten Mal seit Jahren wurde beim Coden wieder breit gelächelt und allein gelacht
- Es muss kein Texteditor sein: Empfohlen wird, eigene Werkzeuge zu bauen, schwierige Teile nicht an statistische Blackboxes wie KI auszulagern und die Herausforderung selbst zu genießen
5 Kommentare
Eigentlich ist an diesem Beitrag vor allem ein Wort am erstaunlichsten.
Fingerspitzengefühl
Finger + Spitzen + Gefühl
Dass es im Deutschen tatsächlich ein Wort gibt, das das Bediengefühl in den Fingerspitzen ausdrückt, ist wirklich … ach …
Finger war also auch Deutsch. Ich dachte, es wäre Englisch ...
Da es zur gleichen Wortfamilie gehört, teilt es viel Grundwortschatz.
Deutsch, bei dem unendlich viele Wortkombinationen möglich sind, haha.
Hacker-News-Kommentare
Hat durchgehend Spaß gemacht zu lesen. Ich empfehle inzwischen auch Freunden, ihren eigenen Texteditor zu bauen
Ich benutze meinen Editor „Left“ nun seit fast 10 Jahren. Am Anfang war er nicht perfekt, aber ich habe Left weiterentwickelt, indem ich Left mit Left bearbeitet habe. Die Freude, morgens ein Werkzeug zu öffnen, das ich mit eigenen Händen gebaut habe, belohnt mich 20-fach für die investierte Zeit
Es gibt den Spruch: „Im Leben sollte man einmal ein Haus bauen, einen Baum pflanzen und einen Editor schreiben.“ Ich habe mit dem Letzten angefangen
Das ist eine Zeile aus Vip, einem Vi-artigen Editor auf Basis von PicoLisp
Ich habe auch von Grund auf einen eigenen Texteditor gebaut. Weil er viele Funktionen hat, habe ich externe Tools wie LSP, tree-sitter und fzf aktiv genutzt.
Er wurde im suckless-Stil so entworfen, dass man ihn einfach durch direkte Code-Änderungen anpassen kann.
In den ersten Wochen war er voller Bugs, aber mit jeder Korrektur wurde er stabiler. Mein Projekt hat kann man sich ansehen
Kann jemand vielleicht eine Textbearbeitungsbibliothek empfehlen?
Eine GUI ist Pflicht, also muss man am Ende auch Font-Renderer und Grafik-Kontext direkt behandeln.
Für eine reine Konsole ist das für mich nicht nutzbar, und wenn man nur die GUI baut, fehlt die Editierfunktionalität, also brauche ich beides.
Überraschenderweise ist es schwer, eine reine API-Bibliothek zu finden, die diese Anforderungen erfüllt.
Meistens ist es entweder ein fertiger Editor oder gleich ein riesiges Framework.
Eigentlich hätte ich einfach gern nur eine grundlegende Editier-Engine, die große Textdateien schnell verarbeiten kann
Mit Tools wie trolley kann man das sogar wie eine native UI auf ghostty-Basis verpacken
Stattdessen ist die Kombination aus SDL und SDL_ttf eine ziemlich gute Wahl. SDL3_ttf verbessert auch die String-Verarbeitung
Ich habe den „kilo“-Editor von antirez noch einmal nachimplementiert.
Der Originalcode und das Tutorial sind sehr gut gemacht, daher war das ein hervorragendes Projekt, um Terminalmodus und die Grundlagen der Sprache C zu lernen
Ich habe Erinnerungen daran, in den 90ern selbst einen Editor für COBOL- und ASM-Dateien gebaut zu haben.
Er hatte Syntax-Highlighting, schnelles Buffering und sogar einen Bildschirmschoner.
Er lief auf einem Pentium 120 und war meiner Erinnerung nach tausendmal schneller als das heutige VSCode
Damals schrieb man alle HTML-Tags in Großbuchstaben
Hier ist der Editor zte, den der Autor dieses Artikels gebaut hat
Der Satz „Widerstehe der Versuchung, die schwierigen Teile in eine Statistik-Box zu schieben“ ist mir wirklich im Gedächtnis geblieben
Ich benutze auch meinen eigenen Editor. Andere Leute interessiert das kaum, aber der Wert, den ich aus einem selbstgebauten Werkzeug ziehe, ist groß
Es gibt auch eine einfache „Browser“-Funktion, mit der man per F5 Links öffnen kann
Josh Barretto ist das Genie, das den Super Mario 64 GBA Port gemacht hat. Seinen Editor würde ich gern ausprobieren