Tab settings in Vim
Summary:
There are 4 settings that you may be concerned with. Each setting’s value answers a question
tabstop
answers the question: how many columns of whitespace is a\t
char worth? Think of a set of vertical lines running down the length of your paper.shiftwidth
answers the question: how many columns of whitespace a “level of indentation” is worth?softtabstop
answers the question: how many columns of whitespace is atab
keypress or abackspace
keypress worth?- Setting
expandtab
means that you never wanna see a\t
again in your file — rather, tabs keypresses will be expanded into spaces
999 times outta 1000, I’ll want all integer options set to be the same. In abbreviated notation, that means we'll run a command like so: :set ts=2 sw=2 sts=2 et
.
Foreword:
- If you don’t know how settings work in Vim, read this really fast (link)
- You might want to make whitespace visible before reading this. We can use the
list
&listchars
settings for that. Run:set listchars=space:_,tab:>~ list
. You will now see spaces as colored_
characters, and tabs as a single>
followed by as many~
s as needed.
Alright! Let’s make something simple complicated. There are a few fundamental ways to get whitespace into Vim, and they all serve different purposes. Thus, they use refer to different settings in order to do their job right. Here are the ways to get whitespace into your file: the spacebar
key, the tab
key, the shift-left-right commands(<
or >
), and the formatting commands (=
or gq
).
In order to determine how to place whitespace into your file upon receiving one of those commands, Vim refers to the values of the following settings.
- tabstop — How many columns wide is a tab character worth? Does a
\t
take up 4 columns? 2 columns? 8 columns? Changing this value will change how tabs are displayed. Must be greater than 0, as tabs having no width is never desired. - shiftwidth — Referred to for “levels of indentation”, where a level of indentation is shiftwidth columns of whitespace. That is, the shift-left-right commands, the formatting commands, and the behavior of vim with cindent or autoindent set is determined by this setting. A shift command says “give me one more (or one less) level of indentation”. Formatting commands are more complicated (
:help gq
if you’re curious, they basically parse your file and determine how many layers of indentation should be there). So if vim decides that a line needs 3 levels of indentation then it’ll place 3*shiftwidth columns of whitespace. - expandtab — Unlike the other settings, this one is a Boolean, and quite straightforward. It answers the question: should vim place spaces or tab upon receiving a whitespace command or a
tab
keypress. - softtabstop — Referred to for the
tab
key andbackspace
key. How much whitespace should be inserted when thetab
key is pressed? And how much whitespace should be removed when thebackspace
key is pressed?
By default, softtabstop is disabled, meaning that hitting tab
will add whitespace columns until vim gets to a tabstop
(either 1 \t
character or tabstop
spaces, based on if expandtab
is set). And hitting backspace
will remove 1 character. This default behavior is weird when expandtab
is set, the lack of parallelism feels weird: pressing tab
will add tabstop
characters to your file, but backspace
will only remove 1. People normally want backspace
to remove a tab’s worth of whitespace when they have expandtab
set. So if you’re enabling expandtab
then you should enable softtabstop
.
Alright! Not too bad. Let’s just review really quickly:
tabstop
is effectively how many columns of whitespace a\t
is worth.shiftwidth
is how many columns of whitespace a “level of indentation” is worth.- Setting
expandtab
means that you never wanna see a\t
again in your file softtabstop
is how many columns of whitespace atab
keypress or abackspace
keypress is worth.
Well, that made it sound simple. But it gets kinda nuanced:
- By default (
softtabstop=0
,noexpandtab
), atab
keypress will give you a\t
character. And abackspace
keypress will remove 1 character. - If you set
expandtab
, then atab
keypress will give youtabstop
spaces. Abackspace
keypress will remove 1 character. - If you set
softtabstop
, then atab
keypress will give yousoftabstop
spaces. Abackspace
keypress will removesoftabstop
columns of whitespace (or 1 character, if you’re in the middle of a block of text and stuff).
If you understand the abstractions that the designers of vim made, then everything is simple! Here’s how I understand it: enabling softtabstop
says that backspace
and tab
should change of columns of whitespace instead of characters. And orthogonally related, shiftwidth
is all about levels of indentation.
Alright! Let’s look at some examples! Just for fun.
Example 1: We have tabstop=5
, and softtabstop=3
. We are in insert mode on an empty line.
Hitting tab
will give you 3 columns of whitespace. Can we apply expandtab? Nope.
Hitting tab
again will change your total to 6 columns of whitespace. There are now enough columns of whitespace to fit a \t
.
- If
noexpandtab
, as is default, then vim will fill up the 6 columns of whitespace with 1\t
character and 1 space. - If
expandtab
is set, then vim will fill up the 6 columns of whitespace with 6 spaces.
This example was meant to show you that a \t
character is equal to tabstop
columns of whitespace, and that pressing the tab
key is worth softtabstop
columns of whitespace.
Example 2: We have tabstop=8
, softtabstop=0
, shiftwidth=5
, and noexpandtab
. We are in insert mode on an empty line.
softtabstop
is disabled. Hitting tab
will give you tabstop
(8) columns of whitespace. Because noexpandtab
is set as well, Vim inserts 1 \t
character to fill these 8 columns.
Hitting tab again will change your total to 16 columns of whitespace. The file sees \t\t
.
You run :set expandtab tabstop=4
. The two tabs on screen each become worth 4 columns of whitespace, but they don’t turn to spaces yet. You have 8 columns of whitespace. The file’s contents haven’t changed, but what you see on the screen has.
You hit >>
, adding a level of indentation (shiftwidth columns of whitespace) to your file. Well, 8+5 = 13 columns of whitespace. Vim expands the tabs, and you will get 13 spaces.
Had you set noexpandtab
before running the indent command, then you’d still have 13 columns of whitespace. But instead of Vim placing 13 spaces, it would place 3 \t
characters and 1 space.
As you can see, you can do some kinda weird stuff with these settings. To not get confused, there’s only a few things you have to memorize. tabstop
is the maximum columns of whitespace a \t
character can take up, shiftwidth
is for “levels of indentation”, and softtabstop
is for keypresses.
Generally, you’ll want to have all these values be the same, just for consistency’s sake. That way, you can edit and hit the tab
key or >>
or backspace
and affect the same number of columns of whitespace.
And that’s it. Woo! ⚪️👾🎉
End note 1: Check out retab
, it’s a super easy way to change tabs to spaces in a document. Also check out what CTRL-t does in insert mode.
End note 2: Knowing these settings works super will with the command filetype plugin on
. That command says: upon opening a file of type xyz
, source the file ~/.vim/ftplugin/xyz.vim
.
This is awfully convenient if you wish to run setlocal tabstop=2 shiftwidth=2 expandtab
every time you open a *.c
file, and setlocal tabstop=8 shiftwidth=8 noexpandtab
every time you open a Makefile. You can check out what’s in my ~/.vim
folder here. Ask me anything! I’d love to talk to someone that cares about this LOL.