Make Vim follow XDG Base Directory specification

XDG Base Directory specification, $XDG_CONFIG_HOME etc. Great thing - configs separated from user data and cache, no clutter in home directory. Unfortunately, many programs still don't respect it, including Vim. But what would be our favourite text editor if we wouldn't be able to reconfigure it!

TL;DR #

Into environment/shell config (e.g. in ~/.profile):

if [ -x "$(command -v vim)" ]; then
    [ "$(vim --clean -es +'exec "!echo" has("patch-9.1.0327")' +q)" -eq 0 ] && \
        export VIMINIT="set nocp | source ${XDG_CONFIG_HOME:-$HOME/.config}/vim/vimrc"
fi

At the top of vimrc:

" XDG Base Directory support

if empty($MYVIMRC) | let $MYVIMRC = expand('<sfile>:p') | endif

if empty($XDG_CACHE_HOME)  | let $XDG_CACHE_HOME  = $HOME."/.cache"       | endif
if empty($XDG_CONFIG_HOME) | let $XDG_CONFIG_HOME = $HOME."/.config"      | endif
if empty($XDG_DATA_HOME)   | let $XDG_DATA_HOME   = $HOME."/.local/share" | endif
if empty($XDG_STATE_HOME)  | let $XDG_STATE_HOME  = $HOME."/.local/state" | endif

if !has('nvim')
  set runtimepath^=$XDG_CONFIG_HOME/vim
  set runtimepath+=$XDG_DATA_HOME/vim
  set runtimepath+=$XDG_CONFIG_HOME/vim/after

  set packpath^=$XDG_DATA_HOME/vim,$XDG_CONFIG_HOME/vim
  set packpath+=$XDG_CONFIG_HOME/vim/after,$XDG_DATA_HOME/vim/after

  set backupdir=$XDG_STATE_HOME/vim/backup | call mkdir(&backupdir, 'p', 0700)
  set directory=$XDG_STATE_HOME/vim/swap   | call mkdir(&directory, 'p', 0700)
  set viewdir=$XDG_STATE_HOME/vim/view     | call mkdir(&viewdir,   'p', 0700)
  set undodir=$XDG_STATE_HOME/vim/undo     | call mkdir(&undodir,   'p', 0700)
  set viminfofile=$XDG_STATE_HOME/vim/viminfo
endif

let g:netrw_home = $XDG_DATA_HOME."/vim"
call mkdir($XDG_DATA_HOME."/vim/spell", 'p', 0700)
call mkdir($XDG_STATE_HOME."/vim", 'p', 0700)

Step-by-step #

Relocating vimrc #

Since version 7.3.1178, Vim will search for ~/.vim/vimrc if ~/.vimrc is not found. So let's move the file there.

Let's move our ~/.vim to $XDG_CONFIG_HOME/vim. Now we need to command Vim to read config from this new location prior to ~/.vim.

If you have Vim 9.1.0327 or newer, you are in luck! From that version Vim looks for $XDG_CONFIG_HOME/vim/vimrc on its own, no further hacks required.

We can move on to configuring inside our vimrc.

Configuration in vimrc #

First of all, although not mandatory, let's set $MYVIMRC variable:

if empty($MYVIMRC) | let $MYVIMRC = expand('<sfile>:p') | endif

Let's define fallback locations in case XDG_* variables are not set.

if empty($XDG_CACHE_HOME)  | let $XDG_CACHE_HOME  = $HOME."/.cache"       | endif
if empty($XDG_CONFIG_HOME) | let $XDG_CONFIG_HOME = $HOME."/.config"      | endif
if empty($XDG_DATA_HOME)   | let $XDG_DATA_HOME   = $HOME."/.local/share" | endif
if empty($XDG_STATE_HOME)  | let $XDG_STATE_HOME  = $HOME."/.local/state" | endif

Let's add entries to runtimepath:

set runtimepath^=$XDG_CONFIG_HOME/vim
set runtimepath+=$XDG_DATA_HOME/vim
set runtimepath+=$XDG_CONFIG_HOME/vim/after

$XDG_CONFIG_HOME/vim and $XDG_CONFIG_HOME/vim/after are just equivalents of ~/.vim and ~/.vim/after, but $XDG_DATA_HOME/vim is brand new - there we will keep downloadables (like plugins and spell files), Netrw bookmarks etc.

Let's set directory for Vim8 build-in packages:

set packpath^=$XDG_DATA_HOME/vim
set packpath+=$XDG_DATA_HOME/vim/after

Netrw is just as easy:

let g:netrw_home = $XDG_DATA_HOME."/vim"

What about spellings? Well, this one is more tricky, because it isn't controlled by any option. Instead it searches for spell directory in whole runtime path. If none is found then it falls back to ~/.vim/spell. So let's create one at desired location ourselves!

call mkdir($XDG_DATA_HOME."/vim/spell", 'p', 0700)

So far so good. We are left with state (backup, undo, swap, viminfo, view). Vim doesn't create directories for them (even for defaults), so we will need to do it ourselves - luckily VimL offers mkdir() function:

set backupdir=$XDG_STATE_HOME/vim/backup | call mkdir(&backupdir, 'p', 0700)
set directory=$XDG_STATE_HOME/vim/swap   | call mkdir(&directory, 'p', 0700)
set undodir=$XDG_STATE_HOME/vim/undo     | call mkdir(&undodir,   'p', 0700)
set viewdir=$XDG_STATE_HOME/vim/view     | call mkdir(&viewdir,   'p', 0700)

if !has('nvim') " Neovim has its own location which already complies with XDG specification
  set viminfofile=$XDG_STATE_HOME/vim/viminfo
endif

Congratulations!
Now your Vim is configured with accordance to XDG Base Directory specification.

Sources #