Code-Style in Neovim: Tabs vs. Spaces
In der Standardeinstellung wird in Neovim mit nicht weniger als 8 Leerzeichen eingerückt. Dabei sind eine Reihe von Optionen involviert, deren individuellen Effekte nicht unbedingt einfach zu bestimmen sind. In diesem Artikel wird es darum gehen, wie sie einander beeinflussen.
Die wichtigste Entscheidung besteht darin, ob überhaupt Leerzeichen verwendet werden sollen. Alternativ werden Tabzeichen eingefügt. Für eine saubere Codebasis ist es unbedingt erforderlich, dass Projektstandards den Mitarbeitern in dieser Frage eine Vorgabe geben.
Das genaue Verhalten der weiteren Optionen hängt von dieser Haupteinstellung ab. Wir werden verschiedene Konfigurationsmöglichkeiten besprechen. Zuvor werden in ein paar Worten die beiden Seiten der “zeitlosen” Debatte skizziert.
Was spricht für Tabs?
Es wirkt wie eine substanzlose Auseinandersetzung ohne praktische Konsequenzen. Unterschiede in der Dateigröße sind heute kaum noch nennenswert. Selbst bei den erforderlichen Tastatureingaben ähneln sich die beiden Fälle stark.
Neovim simuliert wie andere Editoren in den relevanten Kontexten das Verhalten von Tabs. Um eine Einrückungsebene zu entfernen reicht ein Druck auf die Backspace-Taste, egal ob am Zeilenbeginn ein Tabzeichen oder mehrere Leerzeichen stehen.
Wenn mit Leerzeichen eingerückt wird, stellt sich die Frage, wie viele dazu verwendet werden sollen. Hier besteht der Trade-off zwischen Lesbarkeit und vergeudetem Platz. Coding-Standards geben einen Richtwert, der für alle Projektmitarbeiter passen soll.
Der Hauptunterschied bei der Verwendung von Tabzeichen besteht darin, dass Editoren sie flexibel darstellen können. Die Anzahl der angezeigten Leerstellen ist so nicht mehr in der Textdatei gespeichert, sondern sie ergibt sich aus den Editoreinstellungen.
So können Mitarbeiter selbst entscheiden, was sie visuell anspricht oder wie sie den Platz für ihren Workflow nutzen möchten. Dieser Unterschied kann durchaus bedeutsam sein, etwa bei mehreren Fenstern nebeneinander auf demselben Bildschirm oder bei großer Terminalschrift.
Das ist zugleich der Nachteil von Tabs: Das ASCII-Zeichen wird in verschiedenen Kontexten verschieden interpretiert. Auch wenn es Einstellungsmöglichkeiten dazu gibt, werden viele Anwender ihren Editor gar nicht für die Verwendung von Tabs konfiguriert haben.
So können Abstände riesig erscheinen. Und es kann gewöhnungsbedürftig sein, dass man den Beginn von Zeilen nicht mehr mit dem Cursor erreichen kann. Gegebenenfalls ist nicht ohne Weiteres ersichtlich, dass ein Tabzeichen dafür verantwortlich ist.
Um welche Abstände geht es?
Einleitend wurde gesagt, dass in Neovim Leerzeichen zum Einrücken verwendet werden. Auch die Hilfe spricht in ihrer Erklärung von tabstop
, softtabstop
und shiftwidth
von der “Anzahl von Leerzeichen” (number of spaces).
Tatsächlich werden die Abstände beim Einrücken und bei Verwendung der Tab-Taste standardmäßig mit einem Tabzeichen erzeugt. Ob die Abstände alternativ von Leerzeichen ausgefüllt werden, hängt von expandtab
ab. Per Default ist der Schalter deaktiviert.
Wenn die Hilfe an diesen Stellen von spaces spricht, so sind eigentlich visuelle Leerstellen gemeint. Sie sind so breit wie Leerzeichen und können von beliebigen Zeichen im Buffer ausgefüllt werden.
Die Dokumentation spricht bei diesen Einheiten zur Messung der Fenstergröße für gewöhnlich von
Columns. Die exakte Cursorposition in dem damit definierten Raster wird unten rechts angezeigt. Mit :echo winwidth(0)
wird ausgegeben, wie breit das gerade geöffnete Fenster ist.
In Terminals werden für gewöhnlich Monospace-Schriften verwendet. Damit weisen alle Buchstaben und Sonderzeichen (einschließlich dem Leerzeichen) die gleiche Breite auf. Sie passen somit exakt in die visuellen Slots, die als Columns abgesteckt werden.
Um welche Funktionalität geht es?
Die Tabs vs. Leerzeichen-Debatte betrifft zwei verschiedene Fälle:
- wenn die Tab-Taste gedrückt wird
- wenn eine Zeile eingerückt wird, entweder automatisch oder manuell (wie mit
>
)
Genau genommen ist der zweite Punkt ein Spezialfall vom ersten. Die Unterscheidung ist dennoch wichtig, weil verschiedene Optionen sie verschieden behandeln.
Es geht nicht generell darum, Symbole untereinander anzuordnen. Beispielsweise sollen Operatoren in mehrzeiligen Ausdrücken nach einigen Style-Guides vertikal eine Linie bilden. Dabei können nur Leerzeichen verwendet werden.
Der Unterschied liegt darin, dass sich die Operatoren nicht auf dem tabellarischen Raster anordnen lassen, das den gesamten Textbuffer umspannt. Um sich den Gegenstand der Debatte zu veranschaulichen, können einige historische Bemerkungen helfen.
Die Tabulatortaste gab es bereits auf Schreibmaschinen. Wurde sie gedrückt, rutschte der Schlitten (carriage) vor bis zum nächsten Tabulator (tab stop). Sie wurden am oberen Rand eingestellt, um Eingaben in linksbündigen tabellarischen Spalten zu ermöglichen.
Die Bewegung hängt davon ab, an welcher Stelle sich der Schlitten gerade befindet und wie weit der nächste gesetzte Tabulator entfernt ist. Die Abstände zwischen den Tabulatoren konnten flexibel eingestellt werden, wir unterstellen im Folgenden jedoch Spalten von einheitlicher Breite.
Implementierung
Es ist in erster Linie dieses Verhalten, das moderne Textanwendungen bei Druck der Tab-Taste einer Computertastatur reproduzieren. Statt einer physischen Bewegung springt der Cursor. Die Sprungweite lässt sich in den angesprochenen Columns messen.
Bei der Verwendung von Leerzeichen ist die Implementierung ziemlich einfach. Angenommen die Tabulatoren wurden auf einen Abstand von 8 Columns eingestellt. Wird auf einer leeren Zeile im Insert-Modus <Tab>
gedrückt, springt der Cursor bis Column 9 und erreicht damit die maximale Sprungweite.
Wurden bereits zwei Zeichen eingegeben, werden beim <Tab>
nur noch 6 Leerzeichen benötigt, um den Tabulator an Position 8 zu erreichen. Die zusätzlichen Zeichen sind Teil vom Textbuffer und werden in die geöffnete Textdatei geschrieben.
Im Falle von Tabzeichen spielt die Cursorposition für die Datei im Dateisystem keine Rolle. Doch die korrekte Darstellung der geöffneten Datei muss vom Editor berechnet werden. Bei gleichen Einstellungen muss der Abstand aussehen, als ob 6 Leerzeichen eingefügt worden wären.
Die relevanten Optionen
Nun zu den Optionen, mit denen sich verschiedene Standpunkte in der Debatte in Neovim realisieren lassen. Wie zu erwarten ist die Verwendung von Leerzeichen vergleichsweise einfach. Die Konfiguration für die Verwendung von Tabzeichen involviert einige mögliche Komplikationen.
Verwendung von Leerzeichen
Die meisten Coding-Standards empfehlen die Verwendung von Leerzeichen. Um die Darstellung auf diese Weise in der Textdatei festzuhalten, wird in Neovim die expandtab
-Option eingeschaltet.
Bei dieser Einstellung kontrollieren zwei weitere Optionen die beiden oben erwähnten Fälle:
tabstop
bestimmt, wie viele Leerzeichen mit<Tab>
maximal eingefügt werden.shiftwidth
betrifft die Anzahl von Leerzeichen, die beim Einrücken verwendet werden.
Für Sprachen der C-Familie könnte die Konfiguration beispielsweise folgendermaßen aussehen:
vim.opt.expandtab = true
vim.opt.shiftwidth = 4
vim.opt.tabstop = 4
Für softtabstop
belassen wir es beim Standardwert. Der Zweck dieser Option wird im nächsten Unterabschnitt Thema sein.
Verwendung von Tabzeichen
Wie bereits angesprochen ist expandtab
standardmäßig deaktiviert. Wird <Tab>
gedrückt, wird somit ein Tabzeichen in den Buffer eingefügt. Wie dieses Zeichen in Neovim angezeigt wird, hängt von der tabstop
-Option ab.
Standardmäßig ist shiftwidth
auf 8
gesetzt. Die Option entscheidet darüber, wie viele Leerzeichen beim Einrücken verwendet werden. Dabei ist es egal, ob es automatisch passiert oder ob dazu die >
-Taste verwendet wird.
Vor allem ist es aber egal, ob expandtab
gesetzt wurde – es bleibt in jedem Fall bei Leerzeichen. Wir hätten damit keine eindeutige Antwort in der Tabs vs. Spaces-Debatte. Soll in beiden Fällen ein Tabzeichen eingefügt werden, muss shiftwidth
auf 0
gesetzt werden.
Wie eben gesagt entscheidet tabstop
darüber, wie das Tabzeichen angezeigt wird. Wird es zum Einrücken verwendet, ist der Fall einfach. Es repräsentiert die maximale Sprungweite und der Abstand wäre damit so breit wie 8 Leerzeichen.
In anderen Kontexten unternimmt der Editor für die korrekte Darstellung komplexere Berechnungen. Im Wesentlichen wird gezählt, wie viele Zeichen nach dem letzten Tabulator (tabstop) stehen. Das Tabzeichen wird in seiner Darstellung dann umso schmaler, je weniger Zeichen bis zum nächsten Tabulator fehlen.
softtabstop
verkompliziert die Situation weiter. Wird sie abweichend von den Standardeinstellungen auf einen positiven Wert gesetzt, entscheidet sie über die maximale Sprungweite. Die eigentlich dafür verantwortliche Option tabstop
spielt dann eine andere Rolle.
Praktisch gesprochen kann bei dieser Einstellung mit <Tab>
zugleich eine Kombination von Tab- und Leerzeichen eingefügt werden. Die Hilfe
empfiehlt, für bestmögliche Kompatibilität den Standardwert für tabstop
eingestellt zu lassen.
Zusätzliche Leerzeichen werden möglich, wenn shiftwidth
auf einen Wert größer als 8
gesetzt wird. Ein Beispiel mit tabstop=8
und softtabstop=10
verdeutlicht diesen Fall:
<Tab>
führt zu Beginn einer Zeile nun dazu, dass ein visueller Abstand von 10 Columns eingefügt wird.- 8 dieser Columns können rein visuell durch ein Tabzeichen ausgefüllt werden.
- Für die verbleibenden 2 werden Leerzeichen nötig.
Best Practices
Für eine Kombination von Tabs und Leerzeichen sprechen historischen Gründe, die heute schwer nachzuvollziehen sind. Zur Vermeidung unnötiger Komplikationen vergibt man besser einheitliche Werte für tabstop
und softtabstop
.
-- Function to configure tab/space behavior
-- @param use_tabs bool: Whether to use tabs (true) or spaces (false)
-- @param width number: The width of indentation/distance between tab stops
function configure_indentation(use_tabs, width)
if use_tabs then
vim.opt.expandtab = false
vim.opt.tabstop = width -- For visual appearance of tab characters
vim.opt.softtabstop = width -- For max jump distance of manual tabs
vim.opt.shiftwidth = 0 -- Use tabs for indentation
else
vim.opt.expandtab = true
vim.opt.tabstop = width -- For max jump distance of manual tabs
vim.opt.shiftwidth = width -- For indentation
end
end
Viele Projekte nutzen eine geteilte EditorConfig. Neovim erkennt automatisch, ob sich eine solche Datei im Projektverzeichnis befindet. In diesem Fall überschreiben die darin festgelegten Einstellungen gegebenenfalls individuelle Editoreinstellungen.
Diese Art der projektbezogenen Konfiguration ist besonders hilfreich bei Sprachen, für die es verschiedene Standards gibt. So etwa bei PHP: WordPress empfiehlt die Verwendung von Tabs, während Laravel die Verwendung von Leerzeichen nahelegt. Statt PHP global zu konfigurieren, ist eine .editorconfig
vorzuziehen.
Konvertierung
Zu einer uneinheitlichen Verwendung von Leerzeichen und Tabs in der gleichen Datei kommt es nicht nur durch inkonsistente Einstellungen des Editors. Weitaus üblicher ist der Fall, in denen eine Datei mit abweichendem Standard geöffnet wird.
Wurden Leerzeichen entgegen dem aktuellen Tab-Standard verwendet, können die entsprechenden Stellen mit :retab
in Tabzeichen umgewandelt werden. Wie bei softtabstop
kann es dabei zu einer Kombination von Tab- und Leerzeichen kommen.
Dabei ist mit Vorsicht vorzugehen, da auch Whitespace innerhalb von String-Literalen umgewandelt wird. Aus diesem Grund sollte man die anzupassenden Stellen visuell markieren und erst dann die Konvertierung durchführen.
Tatsächlich funktioniert der Befehl auch in die andere Richtung, vielleicht anders als der Name suggeriert. Wurde expandtab
gesetzt, werden Tabs in Leerzeichen umgewandelt. tabstop!
erzwingt die Konvertierung zu Tabs.
Einfacher macht man es sich natürlich, wenn ein Autoformatter ausgeführt wird. Sie haben für gewöhnlich eine Tabs vs. Spaces-Einstellung, um so auf einen Schlag das gesamte Dokument dem gewählten Standard entsprechend zu formatieren.