Vim Plugins Series: vim-which-key

This is the first of hopefully many vim posts to come. I use quite a few vim plugins, to improve my vim efficiency and quality of life, both while developing or just writing blog posts in Markdown. Today, I wanted to highlight vim-which-key, which has recently become one of my favorite vim plugins. As you’ll see, it helps to tie in all of my other plugins and solves one of the most challenging parts of adopting a full time vim development environment: remembering keyboard shortcuts.

Which key?

vim-which-key is both a keybinding manager and popup menu for browsing your custom shortcuts.

Here, I invoke the which-key menu with , and then press f, the shortcut labeled search files, to invoke the command :Files from the amazing plugin fzf.vim allowing me to fuzzy search the current directory and all sub directories recursively.

vim-which-key-1

You define a “which key” (I used my vim leader key) and then describe one or more custom shortcut menus. You can then access your shortcuts, just as you’ve defined them immediately without waiting for the menu to display … but if you’re like me and have dozens of plugins with custom functionality that you want to bind to keyboard shortcuts for quick access, then it can be very easy to forget both the keybindings to invoke them or even what the original vim command was in the first place!

vim-which-key solves this with its lovely popup menu: you get a reminder of all that sweet functionality you worked so hard to acquire by installing dozens of plugins. Plus, the method of accessing those functions, i.e., navigating the vim-which-key menu, is by entering in the custom bindings you defined! This way, you learn the custom keybinding over time.

Let’s take a look at my vim-which-key config:

" Map leader to which_key
nnoremap <silent> <leader> :silent WhichKey ','<CR>
vnoremap <silent> <leader> :silent <c-u> :silent WhichKeyVisual ','<CR>

let g:which_key_map =  {}
let g:which_key_sep = ': '
" Set a shorter timeout, default is 1000
set timeoutlen=100

let g:which_key_use_floating_win = 1

" Single mappings
let g:which_key_map['/'] = [ '<Plug>NERDCommenterToggle'        , 'comment' ]
let g:which_key_map['f'] = [ ':Files'                           , 'search files' ]
let g:which_key_map['h'] = [ '<C-W>s'                           , 'split below']
let g:which_key_map['S'] = [ ':Startify'                        , 'start screen' ]
let g:which_key_map['T'] = [ ':Rg'                              , 'search text' ]
let g:which_key_map['E'] = [ ':SSave'                           , 'save session']
let g:which_key_map['L'] = [ ':SLoad'                           , 'load session']
let g:which_key_map['l'] = [ ':Limelight!!'                     , 'limelight']
let g:which_key_map['z'] = [ ':Goyo'                            , 'zen mode']
let g:which_key_map['r'] = [ ':RnvimrToggle'                    , 'ranger' ]
let g:which_key_map['g'] = [ ':FloatermNew lazygit'             , 'git']
let g:which_key_map['d'] = [ ':FloatermNew lazydocker'          , 'docker']
let g:which_key_map['k'] = [ ':FloatermNew k9s'                 , 'k9s']
let g:which_key_map['t'] = [ ':FloatermNew'                     , 'terminal']
let g:which_key_map['v'] = [ '<C-W>v'                           , 'split right']

" s is for search
let g:which_key_map.s = {
      \ 'name' : '+search' ,
      \ '/' : [':History/'                 , 'history'],
      \ ';' : [':Commands'                 , 'commands'],
      \ 'a' : [':Ag'                       , 'text Ag'],
      \ 'b' : [':BLines'                   , 'current buffer'],
      \ 'B' : [':Buffers'                  , 'open buffers'],
      \ 'c' : [':Commits'                  , 'commits'],
      \ 'C' : [':BCommits'                 , 'buffer commits'],
      \ 'f' : [':Files'                    , 'files'],
      \ 'g' : [':GFiles'                   , 'git files'],
      \ 'G' : [':GFiles?'                  , 'modified git files'],
      \ 'h' : [':History'                  , 'file history'],
      \ 'H' : [':History:'                 , 'command history'],
      \ 'l' : [':Lines'                    , 'lines'] ,
      \ 'm' : [':Marks'                    , 'marks'] ,
      \ 'M' : [':Maps'                     , 'normal maps'] ,
      \ 'p' : [':Helptags'                 , 'help tags'] ,
      \ 'P' : [':Tags'                     , 'project tags'],
      \ 's' : [':CocList snippets'         , 'snippets'],
      \ 'S' : [':Colors'                   , 'color schemes'],
      \ 't' : [':Rg'                       , 'Rg text'],
      \ 'T' : [':BTags'                    , 'buffer tags'],
      \ 'w' : [':Windows'                  , 'search windows'],
      \ 'y' : [':Filetypes'                , 'file types'],
      \ 'z' : [':FZF'                      , 'FZF'],
      \ }

" P is for vim-plug
let g:which_key_map.p = {
      \ 'name' : '+plug' ,
      \ 'i' : [':PlugInstall'              , 'install'],
      \ 'u' : [':PlugUpdate'               , 'update'],
      \ 'c' : [':PlugClean'                , 'clean'],
      \ 's' : [':source ~/.config/nvim/init.vim', 'source vimrc'],
      \ }

" Register which key map
call which_key#register(',', "g:which_key_map")

I’ve set up my most commonly used shortcuts in the default main menu, but I’ve also set up a search submenu, as well as one for managing plugins. No doubt I’ll add other menus over time.

Using my vim-whick-key config directly will only work if you have all of the same plugins as I do. Instead, use it as a guide to add your own shortcuts.

Pro-tip: customize the timeoutlen variable to your liking. It specifies how long vim will wait after you press the definied ‘which-key’ before displaying the popup menu. Personally, I prefer a shorter wait so that the menu appears right away.

Installation

It is recommended to use a plugin manager like vim-plug. Install which-key by adding this line to your ~/.vimrc or ~/.config/nvim/init.vim:

Plug 'liuchengxu/vim-which-key'

And then do :PlugInstall in vim.