You can’t have everything. Where would you put it?
– Steven Wright
Attack of the 5,000-line vimrc
Vim is an endlessly configurable and extensible editor, with a culture of users
sharing configuration for their
files. These files tend to expand over time. New users start by setting only a
few global defaults for options like
and then add custom mappings, functions, filetype-specific logic, and
third-party plugins, often under an ever-shifting mantle of plugin managers.
Their vimrc files grow not only larger, but more intricate and complex.
If you have one of these longer files, you’re in good company. Damian Conway, a Vim guru specializing in Perl, published a vimrc with 1,855 lines, and at the time of writing, Steve Losh’s is a whopping 3,160 lines. I’m sure you can find vimrc files that are even longer—perhaps yours already is.
The issue with very long vimrc files isn’t the sheer amount of
configuration—after all, all of that power is there for a reason. However, if
you’ve been programming for a while, you’ll know from experience that it’s best
to avoid very large files with code that does many disparate things, because it
makes code hard to find, manage, and understand. Vim configuration is no
exception. Instead of a single large configuration file, there’s a case to be
made for having a set of smaller, well-organized files. Those files go in
Creating a directory hierarchy in
~/.vim to replace your large vimrc keeps
your configuration manageable. It improves efficiency by loading code only when
needed. It becomes clearer from a file’s position in the hierarchy what its
purpose is. It also makes it easier to package configuration for others to
Your own personal
Most of the benefit of an organized
~/.vim directory comes from leaning on
Vim’s built-in behavior, which gives you a lot of control over how
configuration files are loaded.
Let’s start by looking at the structure of the runtime files that come with Vim
itself. You can find the path for this directory in the
If you’re using the version of Vim that was packaged with your operating
system, it will very likely be something like
Let’s take a look at the contents of that directory:
$ ls /usr/share/vim/vim81
autoload/ bugreport.vim colors/ compiler/
defaults.vim delmenu.vim doc/ evim.vim
filetype.vim ftoff.vim ftplugin/ ftplugin.vim
ftplugof.vim gvimrc_example.vim indent/ indent.vim
indoff.vim keymap/ lang/ macros/
menu.vim mswin.vim optwin.vim pack/
plugin/ print/ rgb.txt scripts.vim
spell/ synmenu.vim syntax/ tools/
A quick-and-dirty count in the shell shows us there are 1,674 files in this directory tree:
$ find /usr/share/vim/vim81 -type f | wc -l
Of those, 1,335 are
$ find /usr/share/vim/vim81 -type f -name \*.vim | wc -l
All of these are just plain Vim script files, like your vimrc. Their location within this directory determines when they are loaded. Only a few of them are loaded on Vim startup. That’s well over a thousand files ready to be loaded only when relevant. We should take a hint from Bram on that!
If we look at the value of the
'runtimepath' option in Vim, we can see
a few other paths:
The very first entry of
~/.vim, and that’s where you can
build a structure mimicking that of
$VIMRUNTIME. This is your personal Vim
If you’ve worked with Vim script for a while, you probably know how to use the
:source command to read it from a file. To load a separate file with
something like mapping definitions in it, you might have a line like this in
Vim has another command named
:runtime for loading files that works
with the file layout of the
'runtimepath' directories we just inspected. Used
without an exclamation mark, it reads Vim script commands from the first path
it finds in any of its
'runtimepath' directories. With an exclamation mark
added, it reads all of them. In both cases, we can include filename pattern
matching with globs:
Note that we don’t include the leading
~/.vim path in these patterns.
The double asterisk in the last example here represents a set of
directory path elements, which can be up to 100 levels deep. This means that a
~/.vim/foo/bar/baz/quux/maps.vim would still be found and loaded.
:runtime doesn’t raise errors if it can’t find any matching
files. This avoids boilerplate checks for file existence if a file’s absence is
not an error condition.
Much of Vim’s startup process is just thin wrappers around
So are some of its other commands, including
:filetype. We can leverage
this to run our own code before, instead of, or after Vim’s bundled runtime
code, in order to disable, replace, modify, or extend it.
plugin, drop out
You can start the process of breaking up your vimrc file by looking for blocks
of code that have expanded beyond simple configuration, and can be grouped
together meaningfully. We can extract these into self-contained files in the
As an example, if you read others’ vimrc files, you will often see approaches to solving the problem of conveniently removing trailing whitespace at line endings. Here’s one approach, in a function from the Vim Tips wiki:
if !&binary && &filetype != 'diff'
This kind of function is usually followed by a mapping to call it:
nnoremap <Leader>x :<C-U>call StripTrailingWhitespace()<CR>
This function doesn’t need to be loaded every time vimrc is sourced. Once
defined, it can just sit there, ready for calling when appropriate. In fact,
Vim throws an error if a vimrc with this function is reloaded. We could fix
that by declaring the function with
function!, but there’s another way:
instead of putting the function definition in
~/.vimrc, we can drop it into a
.vim file in
~/.vim/plugin. We’ll use
Once this file is created, we can restart Vim, and then confirm our plugin has
been loaded by checking its path is in the output of
Note that the
<Leader>x mapping left in the vimrc still works, despite being
set before the function it calls was defined.
What’s a plugin, anyway?
Why should we put the script in
~/.vim/plugin? You might object that our
example isn’t a real plugin; it’s just a single function. However, that’s not
a meaningful distinction to Vim. At startup, it sources any and all
files in the
plugin subdirectory of each of the directories in
'runtimepath'. It makes no difference what those files actually contain. A
set of related abbreviations? Custom commands? Code dependent on one particular
machine or operating system? Sure, why not?
*.vim files are loaded from the
recursively, you can organize them in subdirectories if you want to:
The names of the subdirectories aren’t significant; Vim will search them all.
Remember how we mentioned Vim’s thinly-veiled
:runtime wrappers? This is one
of them. A clue here is in the command that
:help load-plugins suggests
as an analogue to what Vim does internally at this step:
Local script scope
Putting blocks of code like this in distinct files in
~/.vim/plugin has some
other advantages. One of these is Vim’s script-variable scoping for
functions and variables that are only needed within the script:
let s:myvar = 'value'
This applies a unique prefix to all of your function names and variable names at the time the file is sourced. That means you don’t have to worry about trampling on any other variables defined elsewhere in your configuration.
There are some caveats here for defining mappings; make sure you read
script-variable carefully to make sure you understand how to use
Short-circuiting and load guards
Another advantage of separate script files is the ability to short-circuit
a script, to prevent it from loading if it’s not appropriate to do so. This is
done by checking at the start of the script whether the rest of it should be
loaded, and skipping it with
:finish if it shouldn’t.
We can use this to check options like
'compatible', the Vim version
number, the availability of a feature, or whether the plugin has already been
\ || v:version < 700
\ || !has('folding')
\ || exists('g:loaded_myplugin')
let g:loaded_myplugin = 1
This way, you don’t have to wrap all your feature-dependent code in clumsy
The question of mappings
Should you include a mapping that uses a defined function in the plugin itself? It’s up to you, but the author likes to think of vimrc files as where user-level preferences go, and plugins where the code they call should go. Mapping choices are personal, and fall into the former category.
If you want to keep some abstraction between what the plugin does and how it’s
called, you can use
<Plug> prefix mappings to expose an interface from
the plugin file:
\ :<C-U>call <SID>StripTrailingWhitespace()<CR>
You can then put your choice of mapping for that target in your vimrc:
nmap <Leader>x <Plug>StripTrailingWhitespace
If someone else wants to use your plugin, this makes choosing their own
mappings for it more straightforward. There’s more general advice about good
mapping practices in writing fully-fledged plugin files in
Not really my
Another pattern in big vimrc files is setting options only for buffers of a
certain filetype. For example, this line of code is intended to set the
'spell' option, to highlight possible spelling errors in the text, but
autocmd FileType mail setlocal spell
The first thing to note here is that this should be surrounded in a
augroup, so that reloading it doesn’t make multiple
definitions for the same hook:
autocmd FileType mail setlocal spell
This is annoying, but there’s a way to avoid this boilerplate.
The second thing to note about our
autocmd is that it’s set every time vimrc
is loaded, regardless of whether a mail file is actually edited in that
session. It therefore makes more sense to put this into a filetype plugin
or ftplugin, so that it’s only loaded when relevant.
autocmd hooks that set a buffer’s filetype are defined in
$VIMRUNTIME/filetype.vim. They apply heuristics to guess and then set the
type of a buffer, and then Vim runs any appropriate filetype plugins
afterwards: files in
'runtimepath' directories named
will be sourced.
This means there’s no need for the
autocmd hooks around our
setting. We already have hooks for changes of filetype available to us, and we
can just put this single line in
~/.vim/ftplugin/mail.vim to use them:
With this done, upon editing a new
This is better, but we can improve it further.
Loading filetype configuration afterwards
Rather than putting our
'spell' setting in
~/.vim/ftplugin/mail.vim, we can
put it in
~/.vim/after/ftplugin/mail.vim—note the extra directory named
after in the path.
Files in the
after runtime directory are loaded after the analogous
runtime files included in Vim. Using this path, we can ensure that our option
is set after the
has been sourced. This is how to override something a filetype plugin does if
you don’t like it.
Breaking up filetype plugins
If you need to make this even more granular, you can also put files in subdirectories named after the filetype:
The filetype followed by an underscore and then a script name works, too:
You may have guessed by now that filetype switching is yet another
wrapper. Switching to a filetype of
:runtime! ftplugin/mail.vim ftplugin/mail_*.vim ftplugin/mail/*.vim
Undoing filetype settings
If the filetype of a buffer changes, we should reverse any local
configuration we applied. We can do this with the
variable, which contains a list of pipe-separated (
|) commands. When a
buffer’s filetype changes, the commands undo the buffer-specific settings for
the previous filetype, ready for the new filetype’s plugins to be loaded.
After each filetype plugin setting we make, we should append corresponding
commands to reverse that change to
b:undo_ftplugin. For our
example, we’d do this:
let b:undo_ftplugin .= '|setlocal spell<'
spell< syntax used here, with a trailing left angle bracket, specifies
that the local value of
'spell' should be restored to match the global value
'spell' when the
After setting a filetype, we can check the
b:undo_ftplugin variable’s value
b:undo_ftplugin setl modeline< tw< fo< comments<|setlocal spell<
The difference with indent
Filetype-specific code related to indentation goes in a different location
Those files are sourced if you include the word
indent in your
:filetype call. You should use this layout for files that change
'indentexpr' settings, for example.
You can put indent settings in your filetype plugin if you want to, but
remember that we’re trying to find the right place for things. Doing it this
way keeps your indentation settings separate from all other filetype-specific
settings. This gives users an easy way to load only what they want, using
appropriate arguments to their vimrc’s
As a final note for filetype-dependent logic, if you have any hooks to set a
buffer’s filetype in the first place, based on its filename or contents, those
go in the
ftdetect directory. You might put this in
~/.vim/ftdetect/irssilog, for example:
autocmd BufNewFile,BufRead */irc/*.log setfiletype irssilog
Putting the hooks in
~/.vim/ftdetect means they are sourced as part of the
augroup defined in
filetype.vim. This is another context
in which you don’t need to surround
autocmd definitions in a self-clearing
augroup, because it’s already been done for you.
Be water, my friend
All of the above is just the beginning. We haven’t even touched on
lazy-loading functions for speed with definitions in
:compiler definitions for setting
~/.vim/compiler. These are yet more examples of Vim
functionality that wraps around
While Vim gives you a lot of flexibility in configuring and customizing, there is definitely a Way of Vim for the timely loading of relevant configuration, and if you learn a little about how it works, you’ll fight with your editor that much less. If this seems stringent to you, think back to when you first learned Vim. Do you remember how strange using the HJKL keys for movement seemed, before it made sense? Do you remember how you wanted to stay in insert mode all the time, before normal mode made sense?
Working within the Vim runtime file structure instead of ignoring it or
fighting with it makes your
~/.vim directory into a refined toolbox, with a
place for everything, and everything in its place. It’s well worth the effort!
If you’d like to see an example of how this layout can end up looking when you
make it work for you, the author’s personal
~/.vim directory is available