Vim autocommands

I prepared these notes as part of the meetup event (Vimprovement 5), but they might be useful even if you haven’t joined.

Autocommands are the way to execute any vim actions (like setting an option or mapping a key) in response to various events. Events recognised by vim include writing or reading a file, detecting a filetype, and a lot more.

Autocommands can be set by plugins, or by yourself in your vimrc. The most common category of user-defined autocommands is setting options for particular filetype.

You need to wrap your autocommands in the augroup sandwich:

augroup GROUP_NAME
   autocmd!
   <here go your autocommands>
 augroup END

This makes sure that your vimrc can be safely sourced multiple times. Without augroup and autocmd! part, your autocommands would not be cleared, and effectively duplicated, which can cause problems.

Here’s how to set different options for different filetypes:

augroup GROUP_NAME
   autocmd!
   autocmd FileType python set local shiftwidth=2
   autocmd FileType c set local shiftwidth=8
 augroup END

The general syntax is:
autocmd <event> <pattern> <command>

To see all the events recognized by vim, run :h autocommand-events. There’s over one hundred of them!

<pattern> may have different meaning for different events. For reading- and writing-related events, it’s usually a filename. Another example is FileType event; with it, <pattern> is a filetype, like javascript. If an event does not give any meaning to <pattern>, you still need to put something between <event> and <command> – use * in that case.

Here are some more exotic examples of autocommands, with commands explaining what they are doing:

augroup VIMPROVEMENT
  autocmd!
  " Reload my vimrc everytime I save it.
  autocmd BufWritePost ~/.vimrc source % | redraw | echo "Reloaded vimrc!"
  " Open temporary, readonly buffer with text extracted from a pdf file.
  autocmd BufReadPost *.pdf
        \ edit <afile>:r |
        \ set buftype=nofile |
        \ set bufhidden=delete |
        \ set readonly |
        \ %!pdftotext <afile> -
  " Open temporary, readonly buffer with un-minified JavaScript.
  autocmd BufReadPost *.min.js
        \ edit <afile>:r:r |
        \ set buftype=nofile |
        \ set bufhidden=delete |
        \ set readonly |
        \ %!unminify <afile> -
  " Project-specific settings - run prettier on JavaScript files, but only in that directory (recursively)
  autocmd BufWritePost /Users/mdr/proj/vimprovement/5_2020_12_14/project/*.js silent !npx prettier --write 
  autocmd BufWritePost /Users/mdr/proj/vimprovement/5_2020_12_14/project/*.js redraw!
  " Save buffer when idle (only when there are changes)
  autocmd CursorHold * update
augroup END

The PDF trick requires pdfutils command to be available (brew install poppler to get it, if you’re on MacOS with Homebrew).
The JavaScript trick requires the unminify command to be available (npm i -g unminify).

If you want to check out all my autocommands, see this gist.

Other things, not mentioned yet (you can learn about them in :h autocommands):

  • <buffer>
  • Using multiple events and multiple patterns in a single autocmd
  • Built-in gzip functionality as an example of sophisticated use of autocommands

Examples shared by participants

Here are autocommand examples shared in the chat during the event. Thank you!

From Nikhil – setting mappings for Python files:

autocmd FileType python nnoremap <silent> <leader>r :w !python3.8<CR>
autocmd FileType python vnoremap <silent> <leader>r :w !python3.8<CR>
autocmd FileType python nnoremap <silent> <leader>i :!bpython -q -i %<CR>

From Lucas – example of alternative to autocmd for filetype-specific configuration:

$ cat ftplugin/python.vim 
setlocal formatprg=black\ --quiet\ -
nnoremap <buffer> <F5> :up\|!python "%"<CR>
nnoremap <buffer> <F7> mfgggqG`fzz

From Andres – toggle relativenumber option when entering and leaving insert mode:

au InsertEnter * :set norelativenumber
au InsertLeave * :set relativenumber

From Andres – session setup when entering and leaving vim:

 function! MakeSession()
     let b:sessiondir = $HOME . "/.vim/sessions" . getcwd()
     if (filewritable(b:sessiondir) != 2)
         exe 'silent !mkdir -p ' b:sessiondir
         redraw!
     endif
     let b:filename = b:sessiondir . '/session.vim'
     exe "mksession! " . b:filename
 endfunction

 function! LoadSession()
     let b:sessiondir = $HOME . "/.vim/sessions" . getcwd()
     let b:sessionfile = b:sessiondir . "/session.vim"
     if (filereadable(b:sessionfile))
         exe 'source ' b:sessionfile
     else
         echo "No session loaded."
     endif
 endfunction

au VimEnter * nested if argc() == 0 | :call LoadSession() | endif
au VimLeave * if argc() == 0 | :call MakeSession() | endif

From Nick – running media files with default programs:

(OpenSystemFile would be a operating-system specific function that runs a default program associated with a filetype)

augroup FILE_OPEN_GROUP
    autocmd!
    autocmd BufEnter *.jpg call OpenSystemFile()
    autocmd BufEnter *.png call OpenSystemFile()
    autocmd BufEnter *.gif call OpenSystemFile()
    autocmd BufEnter *.pdf call OpenSystemFile()
augroup END

From Andres – highlight yank:

au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=300}

From Nazar – automatically rebalance windows on vim resize:

autocmd VimResized * :wincmd =

From Martin – project-specific options:

au BufNewFile,BufRead /private/var/*/gopass* setlocal noswapfile nobackup noundofile