Vim and Tab Stops
8 Apr 2022 • Ian Emnace

What you may be used to in other editors is that there is a single behavior you set: tabs or N spaces?

This is not the case with Vim. Vim allows you to define at least three different behaviors that, as we'll see, can actually be mutually exclusive.

P.S. The following is an illustrated guide to the concepts behind tabs and indentation. If you're looking for the TL;DR, feel free to jump to the synopsis.

You May Not Know Tabs

Here's a frame challenge: in each of the two lines below, where will the cursor go when you press the Tab key?

A cursor on an empty line in a word processor
A cursor on a line saying 'A word' in a word processor
Two different lines. Where will the cursor go after a Tab?

Try to remember how different programs will behave: not just your code editor, but different word processors as well, for example.

The answer:

The empty line above, with the cursor moved by a Tab
The 'A word' line above, with the cursor moved by a Tab
The same above lines, after a Tab each.

The cursor, for each line, goes to the same column!

You may very well already know this intuitively. One thing you might gloss over, though, is that the Tab character has a different "length" in these two cases!

Hence, the frame challenge. When talking about tabs and spaces in programming, it's easy to fall into the trap of thinking about "tabs vs N spaces". The two aren't actually completely interchangeable!

The Tab Stop

The reality here is that Tab characters don't inherently have a length. What a Tab character actually does is bring the cursor forward to the next tab stop.

Tab stops can be thought of as markers ("stops") along the page, marking each multiple of a fixed width.

The typical word processor uses a fixed width of half an inch:

Tab stops along a page, marked with text reading '1', '2', '3', and '4'
Half-inch tab stops in Google Docs.

Terminals, and thus terminal-based editors, also have a concept of tab stops. The typical terminal uses a width of 8 character blocks, or 8 columns:

Tab stops in Vim, displayed visually with >-------
8-column tab stops in Vim, displayed using :set list.

While these are the typical lengths of tab stops, most programs allow users to configure tab stop lengths themselves.

Vim's tabstop

This is what Vim's tabstop option is for: it configures the length of the tab stop.
4-column tab stops in Vim, displayed visually with >---
The same line above, but with :set tabstop=4.

With :set tabstop=4, for example, tab stops will now mark every 4 columns along the line.

Notice that we haven't talked about indentation at all yet!

Tab stop length and indentation length can be mutually exclusive. In fact, in certain cases, the settings for both are indeed orthogonal.

Indentation

Indentation length is configured separately in Vim with the shiftwidth option.

Some C code, indented 4 columns from the left
4-column indents with :set shiftwidth=4.

This is used by Vim features that are expressly for indenting, such as:

The Tab key is a special case — we'll get to that later.

Indenting: Tabs or Spaces

While the shiftwidth option tells Vim how many columns to indent a line, it doesn't actually tell Vim how.

As programmers well know, a 4-column indent can be created in two ways:

What governs this choice in Vim is the expandtab option.

With :set expandtab, Vim will use leading Space characters to fulfill the shiftwidth indentation length.

With :set noexpandtab, Vim will do its best to use Tab characters.

There's a minor caveat with :set noexpandtab: if Vim cannot fit Tab characters into the indentation length (such as if shiftwidth is indivisible by tabstop), it uses a mix of Tab and Space characters.

Some C code, indented 6 columns from the left with a tab and 2 spaces
Mixed indentation.

The above example uses an indentation length of 6, but with tab stops of 4. The relevant settings would be:

set tabstop=4 shiftwidth=6 noexpandtab

In such a case, Vim indents with a leading Tab character and 2 Space characters.

The Tab Key

As I've alluded to above, the Tab key (the key on your keyboard, not the character!) is a special case in Vim.

By default, the Tab key simply inserts a Tab character — no surprise there.

The smarttab option

However, Vim recognizes that it's popular among programmers to press the Tab key to perform indentation. In fact, it's likely this preconception that led you to some confusion with Vim's tab settings in the first place.

In light of this, Vim provides another option that can govern Tab key behavior: the smarttab option.

With :set smarttab, the Tab key becomes overloaded with two separate functions:

  1. When the cursor is at the beginning of a line, the Tab key will indent, and will thus follow shiftwidth.
  2. When the cursor is anywhere else, the Tab key will insert a Tab character.

The expandtab and softtabstop options

...except that's not the whole truth.

The expandtab option actually governs Tab characters inserted by the Tab key as well!

With :set expandtab, the Tab key will actually insert Space characters. For example, with

:set tabstop=8 expandtab
the Tab key will insert 8 Space characters.

But what if you want to configure this behavior separately from tabstop? That is where the softtabstop option comes in.

For example, with

:set tabstop=8 softtabstop=4 expandtab
the Tab key will insert 4 Space characters. But Tab characters (\x09 in ASCII) will visually display a tab stop length of 8 columns.

Synopsis

There are three distinct but interrelated behaviors regarding tab stops and indentation:

  1. The tab stop length, configured with tabstop
  2. The indentation length, configured with shiftwidth and expandtab
  3. Tab key behavior, configured with smarttab, expandtab, and softtabstop

Examples

Here are a few examples that correspond more closely with real-world use cases, for illustration.
  1. 4-column Tab indents.

    4-column Tab indents, displayed visually with >---
    4-column Tab indents, displayed using :set list.
    set tabstop=4 shiftwidth=4 noexpandtab

  2. 4-column Space indents.

    4-column Space indents, displayed visually with underscores
    4-column Space indents, displayed using :set list (Spaces are underscores).
    set shiftwidth=4 expandtab

    Popular with Python. In fact, this is automatically configured in the default ftplugin/python.vim.

    The tabstop setting isn't actually needed to achieve 4-column Space indents per se — it's orthogonal.

  3. 2-column Space indents. Tab stops are 8 columns wide.

    2-column Space indents, with 8-column tab stops
    2-column Space indents with 8-column tab stops, all displayed using :set list.
    set tabstop=8 shiftwidth=2 expandtab

    This is my Vim setting of choice. 2-column Space indents are a norm in JavaScript code, so that's what I use for indentation.

    Orthogonally, I use 8-column-wide tab stops because that's what a lot of terminal output optimizes for: mail clients, Git log viewers, etc.