Whitespace settings in Vim

Getting whitespace to behave in Vim using settings and plugins

Published on January 12, 2016

For any good programmer, consistent whitespace is a measure of code-quality. Vim has a lot of built-in settings and features for handling whitespace, and this article will go over those with the examples the Vim help pages lack.

For the purposes of this article, tabs are represented as blocks of the character: .


This section pertains to Vim’s built-in settings flags. You can use :help 'somesettingname' with single quotes from within in Vim to see the full details, so the most useful information I can give is an example of how the setting affects editing. (Why the Vim help pages don’t include this? I don’t know.)

To turn a setting on or off, use set settingname or set nosettingname or (set settingname=0 to turn off numeric settings).

Filetype-local settings

Use setlocal in an autocommand or ftplugin instead of set to make the setting specific to the buffer. Example of using an autocommand to set shiftwidth on JavaScript files only:

autocmd FileType javascript setlocal shiftwidth=2

Note that autocommands are executed in the order they are added, so if a plugin decides to use that same autocommand to change the shiftwidth later, it will override yours. That is why I recommend you using an ftplugin instead.

For the ftplugin version, put the line setlocal shiftwidth=2 into the file ~/.vim/after/ftplugin/javascript.vim, where javascript is the name of the filetype you’re changing settings for.

A good understanding of the order in which Vim runs files and commands will help you debug rogue whitespace settings.

Don’t just copy settings from someone else without understanding what they do.

Everyone in #vim on Freenode

Persistent file-local settings

To make special cases for a specific file, you can either make an autocommand specifically to match the filename, or use Vim’s modeline feature. You need set modeline somewhere in your vimrc to make sure the feature is enabled.

A modeline is a comment (in the current syntax) at the beginning or end of your file that Vim will parse commands from and execute. By default, Vim assumes the command is set. Here’s an example of setting the modeline in a vimrc file:

" vim: set ai sw=4:

The double quote at the beginning indicates a VimL comment. This will set set autoindent and set shiftwidth=4 for the buffer. The use of the short form is optional.

Display options

These options affect how whitespace appears in Vim. If someone else were to open your file, they might see something completely different based on their display options.

list and listchars

The option set list will turn this feature on. This is used to highlight trailing spaces, differentiate tabs from regular spaces, and show a continuation symbol when you have set nowrap and a line is longer than the window.

My options are:

set list
set listchars=""                      " reset
set listchars=tab:→\ 
set listchars+=trail:·

Note there is a trailing space after the backslash on the tab line (for escaping the literal space symbol). So 4-character-wide tabs look like this:

    normal code indented with 4 spaces and no trailing spaces
→░░░→░░░code indented with two tabs and with two trailing spaces··
  normal code indented with 2 spaces and no trailing spaces


A real tab will be this wide. E.g. set tabstop=4:


With set tabstop=8:



The wrap setting affects display lines. When it is off, text appears like so:

This option changes how text is displayed.  It doesn't change the text in the buffer, see 'textwidth' for that.

When it is on, text is displayed as:

This option changes how text is displayed.  It doesn't change the text in the
buffer, see 'textwidth' for that.

When you save the file, the text is saved as one long line — this setting is a display setting only.

Whitespace in lines

These settings affect the actual text content of the file.


This affects what backspace deletes. With the following settings:

set backspace=indent,eol,start

Before backspace:

→░░░→░░░Code that was indented with two tabs

After backspace:

→░░░Code that was indented with two tabs

The indent can be deleted with backspace. Your settings for expandtab, and shiftwidth will determine how many characters are deleted, so if you have a set tabstop=4 and set shiftwidth=2 and you hit backspace, you will delete two spaces from the tab, leaving a two space indent (if expandtab is on).


Use spaces instead of tabs. This is a personal coding preference, and sometimes dictated by your language, but I recommend turning this on (set expandtab) for consistency and overriding it using an autocommand or filetype plugin specific to whatever needs to use real tabs (set noexpandtab).


This affects all of the auto-indenting settings like cindent. When you use >> or << to indent or un-indent a line it will remove this many spaces (or tabs).


    Unindented when sw=2
    Unindented when sw=4


  Unindented when sw=2
Unindented when sw=4


This tells Vim how many spaces or to insert when you hit <Tab> in insert mode. If you use real tabs, it will insert spaces until the number of spaces is equivalent to a tabstop, in which case it will merge that many spaces into a single tab.

Example when softtabstop=2 and for varying values of tabstop:

  Indented once, ts=4 sts=2
→░░░Indented twice, ts=4 sts=2
    Indented twice, ts=8 sts=2

When the NUMBER is negative (e.g. -1 or -2 or whatever), this will use the value of shiftwidth. Since I use a shiftwidth of 2, I keep it at softtabstop=-2 so I know how much it counts for, even though it is using the value from shiftwidth. This may have side effects on 3rd party plugins that don’t account for negative values, but I’d sooner fork the plugin than change my value.


This is for >> and << indenting. Vim will round odd numbers of leading spaces to the nearest shiftwidth.

Example of when shiftround is on, before:

   Three spaces with shiftwidth=4, indent me

After, the line gets indented to 4 spaces since that was the nearest shiftwidth:

    Three spaces with shiftwidth=4, indent me

I have this setting off (set noshiftround) so I can control odd-numbers of indents, particularly for cases like JSDoc blocks, where you have an extra alignment space that you want to preserve:

     * <-- alignment space left of asterisk

Example of bad unindenting when set expandtab and set shiftround and you use << to unindent the JSDoc:

    * <-- alignment space left of asterisk


When you press gj in normal mode, vim takes the line below and moves it to the end of the current line. With this setting on, Vim inserts spaces after punctuation.

Before text. _
After text.


Before text._  After text.

When the cpoptions flag has J, it will become:

Before text. After text._

In all examples, the _ is the cursor position.

You probably want to turn this off so you don’t get extra spaces when coding.



Starting from insert mode with:

    Indented line 1_

Pressing <CR> (Return/Enter) will put you at the next line, same indent level:

    Indented line 1

You probably want this on if you use smartindent.
This setting is modified by smartindent, cindent, indentexpr, and paste.


This setting tells Vim to parse your code a little, and based on curly-braces and keywords defined in the indent syntax for the filetype, it will add an indent going from one line to another.

There are built-in exceptions for lines starting with the character #. For no good reason, in my opinion.


    if (true) {_


    if (true) {

This setting modifies autoindent. It is recommended to use both of them together for consistent behavior.

It is not applied when cindent is on or indentexpr is set.


This is more programmatic than smartindent and uses even more parsing and some Vim options that can be defined in indent plugins. Your milage may vary with this, so turn it on or off as desired. It produces pretty much the same result as smartindent for C, but might not do anything for other languages where the required Vim options like cinkeys aren’t defined.

This setting modifies the formatting of autoindent.
This setting supercedes smartindent.
This setting is not applied when indentexpr is set.

I leave this off in favor of just smartindent since the languages I work in aren’t always as C-style.


This is typically set by indent plugins (including the ones provided by Vim) to give programmatic rules for indenting. Unless you’re writing custom logic to indent code, you don’t need to set it manually. These settings will override smartindent and cindent, so indent plugins have the greatest priority in determining how to indent.

The indent plugin code for pangloss/vim-javascript is a pretty clear example of how indentexpr works — basically it parses lines of code to determine what the next line’s indent should be.


If you have mixed tabs and spaces for some reason, and you’re a strict coder when it comes to whitespace, the mix is probably intentional.

When using autoindent and the copyindent setting is on:

░░░░ ░░░░ code_

Will yield the following on the next line:

░░░░ ░░░░ code
░░░░ ░░░░ another line

When off, you’ll get Vim’s attempt to reconstruct the whitespace:

░░░░ ░░░░ code
░░░░░░░░  another line

This is a good setting to keep on if you like to control your own whitespace.


When this is on:

░░░░ ░░░░ unindentme
░░░░ ░░░░ indentme
░░░░  ░░░░ indentme

And you un-indent your code using << or >>, you get:

░░░░ unindentme
░░░░ ░░░░ ░░░░ indentme
░░░░  ░░░░ ░░░░ indentme

Basically, Vim will keep your original indent pattern if you indent, and remove as little as possible — tabs first, and then spaces. In the last line, you’ll notice Vim detected the tab pattern as <Tab><Space> so it continues to add both every time you indent.

I keep this setting on because I don’t want Vim to change what I probably intentially wrote — which is the only scenario where I would intermix tabs and spaces.

formatoptions and cpoptions

You definitely need to tweak these to your liking, but I think Vim’s help info is sufficient in this case since some visual samples are provided. Check out :help formatoptions, :help fo-table and :help cpoptions.


Vim’s built-in whitespace options may be enough for plain text and C code, but for other languages, Vim supports indent plugins. There are many indent plugins that come bundled with vim, and to automatically enable them use this line in your vimrc:

filetype indent on

You may have filetype plugin indent on, which also loads the rest of the filetype related plugins, so that’s fine. If you’re using vim-plug to manage plugins, it will automatically turn the setting on after plugins have loaded, so don’t include the line in your vimrc or things will load twice!


I recommend using the EditorConfig standard for anyone working in open-source or collaborating with others.

EditorConfig lets you define whitespace standards in an .editorconfig file (but it’s not a linting plugin!).
The rules are grouped by file extension , and it will set the expandtab, shiftwidth, tabstop, softtabstop, and other settings for you after you open a file with a definition in the .editorconfig file you provide.

Many non-Vim text editors and IDEs support EditorConfig, too. This is especially nice because you can keep the file along with your project and other EditorConfig users will automatically be conforming to your whitespace standards.

There are three EditorConfig plugins for Vim. The official one, editorconfig/editorconfig-vim requires Vim to be compiled with Python support (or takes some configuration to use a binary), so check in command mode (:has('python') or :has('python3')) or starting vim with the --version flag:

vim --version

An unofficial one, sgur/vim-editorconfig, is written in pure VimScript so it is technically slower, but for something so simple it is unnoticable. I use this one myself.

There is another unofficial EditorConfig vim plugin written in pure VimScript: dahu/EditorConfig — but it is in alpha and the implementation does not include all the EditorConfig features. Instead, it provides a autocmd hooks so you can implement your own interpretation for things it does not implement like trim_trailing_whitespace and insert_final_newline. I’ve checked out the source code (as I have for all plugins I use) and it’s quite well written so if you want to implement the other features (or don’t need them), it is a viable option.

Auto-determine whitespace settings

As an alternative to editorconfig, Tim Pope’s plugin tpope/vim-sleuth will run through the current file or similar files and try to automatically determine what expandtab, shiftwidth, etc. settings were used on them during creation and set those on the current file. Another option is ciaranm/detectindent.

Better indent visibility

The listchars option is helpful, but sometimes it gets hard to look at symbols, or you need a better way to distinguish your current indent level. There are two good plugins that can provide you with this. Check the links to their GitHub repos for screenshots.

  • nathanaelkane/vim-indent-guides changes the background color at each level of indent. I use this one and toggle it on/off as desired using its built-in commands.
  • Yggdroot/indentLine actually inserts symbols at the start of each indent. This may have some performance problems on large files though, so I’d be wary of it.

Better whitespace visibility

The plugin ntpeters/vim-better-whitespace, similar to nathanaelkane/vim-indent-guides, uses syntax groups to match whitespace. This plugin can set up very visible background colors for whitespace, making sure you don’t miss anything.

Line splitting

For a bunch of programming languages, you can put code all on one line. Using native Vim, you’d have to manually press <CR> at points to properly break it up into several lines (sure, you could automate it with macros or regex, but every time is different).

The plugin AndrewRadev/splitjoin.vim adds mappings to automaticaly do this for you (for certain syntaxes only). Check out the plugin’s README to see visual examples, and doc/ on how to configure it.


There are some plugins that will reformat your code for you by adding whitespace to align things together. See each page for examples:

My Vim setup

You can dig through my Vim configuration on GitHub. My whitespace settings and the plugins I use are all in the main vimrc file, and their configurations are interspersed into plugin/, ftplugin/, and after/*/ to cope with the order in which Vim loads files.