Warum wir plattformspezifische Shells auf einen gemeinsamen Rust-Kern gesetzt haben
Wenn man denselben Editor für drei Betriebssysteme herausbringen will, gibt es einen Weg, die UI nur einmal zu entwickeln. Wir sind diesen Weg einmal gegangen, dann wieder umgekehrt und haben einen gemeinsamen Kern mit einem Satz dünner, plattformspezifischer Shells versehen.

Wenn man denselben Editor für Linux, macOS und Windows herausbringen will, muss man die UI dreimal entwickeln.
Anfangs wollten wir diese Kosten vermeiden. Mit einem einzigen plattformübergreifenden Toolkit zeichneten wir auf allen drei Betriebssystemen dieselbe Oberfläche. Dank dieser Entscheidung bestanden wir das erste Performance-Gate und erreichten auch den ersten Meilenstein für die koreanische Texteingabe.
Dann änderten sich die Prioritäten.
Performance ist wichtiger als Design, und Design ist wichtiger als Kosten. Zeit und Kosten werden als Einschränkungen ausgeschlossen.
Als wir die Sache anhand dieser Kriterien neu betrachteten, fiel auch die Antwort anders aus.
Wir haben uns von einer einheitlichen UI verabschiedet
Ein plattformübergreifendes Toolkit hat klare Vorteile. Eine einmal entwickelte Funktion lässt sich auf mehreren Betriebssystemen verwenden.
Es gab jedoch auch einen Preis dafür. Es wurden 40 bis 60 MB mehr Arbeitsspeicher benötigt, und der Start war 150 bis 200 ms langsamer. Die Darstellung der Schrift blieb hinter den nativen Text-Engines der Betriebssysteme zurück, und auch die systemspezifischen visuellen Effekte ließen sich nicht vollständig nutzen.
Solange die Entwicklungskosten wichtig waren, war das eine vernünftige Entscheidung. Doch nachdem wir die Kosten als Einschränkung ausgeschlossen hatten, gab es keinen Grund mehr, diese Nachteile hinzunehmen.
Deshalb entschieden wir uns, den Kern für die Dokumentverarbeitung in Rust zu entwickeln und für jedes Betriebssystem eine eigene Shell für die Oberfläche zu bauen.
Nicht, weil die bisherige Technologie schlecht gewesen wäre. Wenn sich die Prioritäten ändern, können dieselben Optionen zu einer anderen Antwort führen. Wir beschlossen, die Kosten dafür zu tragen, Funktionen dreimal zu entwickeln, und dafür Performance, Schriftqualität und ein systemgerechtes Nutzungserlebnis zurückzugewinnen.
Wir haben jedoch nicht alles verworfen.
Den Satz an Messregeln haben wir unverändert übernommen. Den Zeiger außerhalb des Fensters platzieren, das Fenster exakt anhand seines Namens finden und die Frames synchron zählen. Auch die Entwurfsprinzipien für die koreanische Texteingabe haben wir übernommen. Dem Betriebssystem zeigen wir nur die Zeile, in der sich der Cursor befindet, und Zeichen, die gerade zusammengesetzt werden, werden nicht formatiert. Wenn Zustände auseinanderlaufen, raten wir nicht, sondern gleichen sie erneut ab.
Code kann man verwerfen, bewährte Prinzipien bleiben.
Wir haben die Rolle der Shell klein gehalten
Wenn die plattformspezifischen Shells beginnen, viele Aufgaben zu übernehmen, entwickeln sich die drei Produkte schnell auseinander. Deshalb haben wir klar festgelegt, was eine Shell tun soll und was nicht.
Die Shell erstellt das Fenster und zeichnet die Schrift. Sie leitet Tastatur- und Zeigereingaben an den Kern weiter und wandelt die koreanische Texteingabe in Bearbeitungsbefehle um. Außerdem wendet sie die visuellen Effekte des Betriebssystems an.
Markdown interpretiert sie dagegen nicht. Abgesehen vom Zeilenumbruch legt sie auch kein Layout fest, speichert keine Dateien und trifft keine Lizenzentscheidungen.
Dieses Prinzip haben wir nicht nur in einem Dokument festgehalten. Gelangt Code zur Markdown-Interpretation in eine Shell, wird der Build durch eine automatische Prüfung gestoppt.
Wenn nur eine Shell beginnt, die Syntax zu verarbeiten, können sich die Ergebnisse je nach Betriebssystem allmählich unterscheiden. Solche Probleme sind zudem schwer zu finden. Denn wenn eine Tabelle nur in einer Shell anders aussieht, muss die eigentliche Ursache nicht einmal im Code liegen, der die Tabelle zeichnet.
Wir haben dafür gesorgt, dass nur der Kern langfristig bestehen bleibt
Der Kern ist von keiner UI-Technologie abhängig. Er lässt sich auch ohne Fenster eigenständig im Terminal testen.
Der Kern übernimmt die Dokumentbearbeitung, die Markdown-Interpretation, das Rückgängigmachen, die Suche, die Gliederung, das Speichern von Dateien, den Export und die Lizenzprüfung.

Wenn ein Benutzer eine Taste drückt, leitet die Shell diese Eingabe an den Kern weiter. Der Kern verändert das Dokument und interpretiert nur den Bereich um die geänderte Stelle neu. Nachdem er ausschließlich den auf dem Bildschirm sichtbaren Inhalt berechnet hat, gibt er die veränderten Ergebnisse an die Shell zurück. Die Shell zeichnet nur die betroffenen Zeilen neu und stellt einen Frame fertig.
Wenn dabei das gesamte Dokument erneut gelesen wird, ist das nicht bloß ein Performance-Problem. Dann wurde das ursprünglich festgelegte Budget verletzt.
Auch die Korrektheit prüfen wir nur einmal im Kern. Alle 652 offiziellen CommonMark-Beispiele müssen bestanden werden, und wenn die Zahl der bestandenen Beispiele sinkt, schlägt der Build fehl. Außerdem führen wir Fuzzing-Tests durch, die fortlaufend unerwartete Eingaben zuführen.
Wir prüfen auch, ob die gespeicherte Datei byteweise mit dem Original übereinstimmt. Liegt die Roundtrip-Quote unter 1,0, ist das System nicht langsam, sondern hat eine Datei verloren.
Auch wenn sich die Shell ändert, bleibt der Kern unverändert bestehen. Unter Linux und Windows binden wir den Kern direkt ein, unter macOS verwenden wir Swift-Brückencode. Die macOS-Shell entwickeln wir mit AppKit und TextKit 2, die Windows-Shell mit DirectWrite.
Das Ziel ist, die Dokumente identisch zu halten und zugleich auf jedem Betriebssystem ein systemgerechtes Bediengefühl zu schaffen.
Das Dokument ist gleich, das Nutzungserlebnis ist unterschiedlich
Nachdem wir die Grenze festgelegt hatten, wurde auch deutlich, was vereinheitlicht werden muss.
Vereinheitlicht werden muss das Dokument. Unabhängig davon, unter welchem Betriebssystem es gespeichert wird, muss der Dateiinhalt byteweise identisch sein. Auch die Ausrichtung von Tabellen, die Zeilenenden und der abschließende Zeilenumbruch dürfen sich nicht verändern.
Die Korrektheit des Markdowns wird bereits im Kern geprüft. Sie muss nicht bei jeder neuen Shell erneut bewiesen werden. Auf einer neuen Plattform müssen nur das Nutzungserlebnis und die Performance überprüft werden.
Das unmittelbar spürbare Erlebnis vereinheitlichen wir dagegen nicht.
Die Schrift wird mit der Text-Engine des jeweiligen Betriebssystems gezeichnet. Auch für die koreanische Eingabemethode verwenden wir direkt das Verfahren des Betriebssystems. Für Transparenzeffekte nutzen wir unter macOS und Windows ebenfalls die Systemfunktionen; unter Linux, wo dieselbe Funktion nicht vorhanden ist, zeichnen wir nur die benötigten Teile selbst.
Auch die Position der Fensterknöpfe, die Form der Menüs und die Zusatztasten für Tastenkürzel gleichen wir nicht zwanghaft an. Es ist nicht unser Ziel, auf unterschiedlichen Betriebssystemen dieselben Pixel zu zeigen. Wichtiger ist, dass sich die Anwendung auf jedem Betriebssystem natürlich anfühlt.
Auch die Veröffentlichungskriterien haben wir für jedes Betriebssystem separat festgelegt. Eine Veröffentlichung ist erst möglich, wenn die Performance-Kriterien erfüllt und praktische Tests mit der standardmäßigen koreanischen Eingabemethode abgeschlossen wurden.
Auch eine Zeile für die jeweilige Plattform erscheint in der Benchmark-Tabelle erst, nachdem tatsächlich ein Build verfügbar ist. Denn wenn wir eine nicht gemessene Zeile vorab eintragen, wird auch die Glaubwürdigkeit der übrigen Zeilen in der Tabelle beeinträchtigt.
Diese Entscheidung hat einen klaren Preis
Wir müssen dieselben Funktionen dreimal entwickeln. Auch die koreanische Texteingabe muss dreimal implementiert werden, und wir müssen drei Build-Umgebungen verwalten.
Windows und macOS können auf der derzeit verwendeten Maschine weder gebaut noch debuggt werden. Dafür werden separate Hardware und eine eigene Automatisierungsumgebung benötigt. Deshalb nennen wir für diese beiden Plattformen keine Termine.
Wir haben auch Bedingungen festgelegt, unter denen diese Entscheidung erneut geprüft werden soll. Dass Kosten als Einschränkung ausgeschlossen wurden, ist eine Voraussetzung dieser Entscheidung. Wenn wir die erforderliche Hardware nicht beschaffen können, verschiebt sich lediglich der Zeitplan der betreffenden Plattform. Die Veröffentlichung für Linux wird wie geplant fortgesetzt.
Eine Entscheidung braucht nicht nur Gründe, sondern auch Bedingungen, unter denen sie neu überdacht wird. So muss die Diskussion nicht wieder ganz von vorn beginnen, wenn sich die Umstände ändern.
Was langfristig bestehen bleiben muss, ist der Kern. Die Shells müssen jederzeit verworfen werden können.
Der Plattform haben wir nur das anvertraut, was sich verwerfen lässt.