EN

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.

Artikel vom 18. April 2025.