How to Popup Vim/Neovim Keybindings using vim-which-key

Efficiency in using Vim comes from mastering its numerous keybindings, but remembering them all can be challenging. This is where vim-which-key proves invaluable. Acting as a guide, it presents a popup that lists available keybindings as you navigate through your tasks. Upon pressing a designated key, often <leader>, which is very often mapped to space, a clear and concise popup emerges, showcasing all relevant mappings. What’s more, this plugin allows you to go beyond just displaying bindings – you can intelligently organize them into categories and even assign custom names to these groups, tailoring your Vim experience to suit your workflow seamlessly. With vim-which-key, unlocking Vim’s potential becomes more intuitive and engaging.

📔 Note: The following guide has been tested on Neovim. For Vim, I used the same configuration a long time ago. So, it should work there too.

Install vim-which-key

First of all, install the vim-which-key plugin using your favorite plugin manager.

For example, to install using vim-plug*, use the following command in your init.vim/.vimrc:

Plug 'liuchengxu/vim-which-key'

*Head on over to this article for a detailed and easy guide on managing plugins using vim-plug.

For other plugin managers, follow their documentation.

The Configuration File for vim-which-key

I like to split my configuration into various files and then source them into the init.vim:

You can do the same by following the detailed instructions here.

First, create the file $HOME/.config/nvim/keys/which-key.vim and then source it in your init.vim:

source $HOME/.config/nvim/keys/which-key.vim

Now, put all the configuration related to vim-which-key into this file.

Timeout for vim-which-key’s Popup Menu

Here we set the Vim variable which_key_timeout. The unit is milliseconds. This variable instructs vim-which-key to pop up when there are no further keystrokes within this time. Suppose you have remembered the keybindings and press them very quickly without needing the popup menu. In that case, there will be no need for the popup to appear. Set the time accordingly.

let g:which_key_timeout = 100

The which_key_timeout is almost the same as timeoutlen, and therefore, there’s no need to redefine it.

Map the Leader Key to vim-which-key

First, you need to map the leader key to space. If you have followed my guide on Neovim configuration, you have already done that. If you have not done so, use the following command:

let g:mapleader = "\<Space>"

Next, register the leader key with which_key by adding the following line:

call which_key#register('<Space>', "g:which_key_map")

For normal mode, use this mapping to activate the popup menu when pressing the leader key:

nnoremap <silent> <leader> :silent <c-u> :silent WhichKey '<Space>'<CR>

Likewise, for visual mode, use the following mapping to open the popup menu:

vnoremap <silent> <leader> :silent <c-u> :silent WhichKeyVisual '<Space>'<CR>

These steps map the leader key to which_key. Now, when you press the leader key, you will see vim-which-key’s popup menu.

vim-which-key popup as a Floating Window and its Size

Set the Vim variable which_key_use_floating_win to 1 if you want the popup menu to be a floating window. However, personally, I prefer the non-floating popup window. Therefore, I have set it to 0.

let g:which_key_use_floating_win = 0

The variable which_key_max_size is used to define the height/width of the popup menu. I have set it to 0, which signifies an unlimited size.

let g:which_key_max_size = 0

These two variables can also be utilized to adjust the size of the floating window.

Define a Separator between Keybindings and Commands

The separator is employed to distinguish your key from its associated command. Set the variable which_key_sep to or another character of your preference.

let g:which_key_sep = '→'
vim-which-key Separator (→) between Keybindings and Commands
Figure 1: vim-which-key Separator (→) between Keybindings and Commands

Hide Multiple Status Lines when vim-which-key Launches

When you first launch vim-which-key’s popup menu by pressing the space button, you might notice the presence of more than one status line (horizontal bar) at the bottom of the screen.

Multiple status lines in vim-which-key
Figure 2: Multiple status lines in vim-which-key

This can be problematic on smaller screens. To address this issue, you can remove unnecessary status lines using the following code snippets:

autocmd! FileType which_key
autocmd FileType which_key set laststatus=0 noshowmode noruler
  \| autocmd BufLeave <buffer> set laststatus=2 noshowmode ruler

Notice in Figure 1, there is only one status line.

Setting vim-which-key’s Popup Position

To achieve this, you should configure the Vim variable which_key_position with either botright or topleft. Similarly, if you set the variable which_key_vertical to 1, your popup menu will be displayed vertically.

To explore all possible values and their meanings, refer to Vim’s built-in help system. For instance, to learn about which_key_position, execute the Vim command :help g:which_key_max_size.

" let g:which_key_position = 'botright'
let g:which_key_position = 'topleft'
" let g:which_key_vertical = 1
vim-which-key's popup located at top
Figure: vim-which-key’s popup located at top

Defining Keybindings and Their Descriptions to be Shown in the Popup Menu

This is the main configuration section where keybindings are defined within a dictionary. Begin by creating an empty dictionary:

let g:which_key_map = {}

Define a shortcut key and then create a corresponding Vim command along with its description to be displayed in the popup. Use the following format:

let g:which_key_map['keybinding'] = [ ':vim_command',  'description' ]
let g:which_key_map['keybinding'] = [ 'vim_key',       'description' ]

📔 Note: Vim commands start with a colon : (e.g., :w), but not Vim keys (e.g., gx, v, etc.). The spaces in the list elements (i.e., []) are optional and are included to make the configuration look well-organized.

Here are some sample keybindings that I’ve created for you to explore and experience how they appear:

let g:which_key_map['w'] = [ ':w', 'write the file' ]
let g:which_key_map['W'] = [ ':w !sudo tee %', 'write root files with sudo' ]
let g:which_key_map['q'] = [ ':q', 'quit Neovim' ]
let g:which_key_map['Q'] = [ ':q!', 'quit Neovim without saving' ]
map <Leader>x :x<CR>
Example of vim-which-key
Figure: Example of vim-which-key

📔 Note: The last keybinding will also be displayed in the vim-which-key’s popup menu. This is a distinctive feature of vim-which-key. It presents all keybindings initiated with the leader keys, even if they are not defined in the which_key_map dictionary. However, in this case, you will see the actual Vim command as there is no corresponding description provided. Consequently, any mappings created by you or other plugins will also be exhibited in the popup, given that they commence with the leader key. You can override them by redefining the mappings in this configuration file.

📔 Note: writing root files with Neovim requires you to setup askpass helper. Set it up using this guide.

Categorizing the Keybindings

As previously mentioned, you can group specific sets of keybindings under a category and assign a special name to that category.

Format:

let g:which_key_map.primary_key = {
    \ 'name': 'description for the primary_key',
    \ 'secondary_key1': ['command1', 'description 1'],
    \ 'secondary_key2': ['command2', 'description 2'],
    \}

Here’s how you can achieve this with an example:

let g:which_key_map.a = {
    \ 'name': '+action',
    \ 'n': [':set relativenumber!', 'toggle number column'],
    \ 'w': [':set wrap!', 'toggle line wraps'],
    \}
map <Leader>aj :echo "Just checking"<CR>

📔 Note 1: The dictionary which_key_map.a contains keys like name, n, and w, with corresponding values like '+action', [':set relativenumber!', 'toggle number column'], and [':set wrap!', 'toggle line wraps']. The first element in the list is the command and the second element is its description. The backslash is used to indicate continuation onto new lines.

📔 Note 2: The last mapping serves to demonstrate how vim-which-key categorizes sequential Vim keybindings following the leader key.

Add the above snippet to your configuration file and observe its functionality. When you open vim-which-key’s popup by pressing the leader key and then press a, a secondary popup will appear, providing choices for the keys n and w.

categorisation of keybindings in vim-which-key
Figure: Categorisation of keybindings in vim-which-key
choosing a in vim-which-key creates another popup
Figure: choosing a in vim-which-key creates another popup

My vim-which-key Configuration File

Here is my configuration file:

" Timeout
let g:which_key_timeout = 100

" map leader key
call which_key#register('<Space>', "g:which_key_map")
nnoremap <silent> <leader> :silent <c-u> :silent WhichKey '<Space>'<CR>
vnoremap <silent> <leader> :silent <c-u> :silent WhichKeyVisual '<Space>'<CR>

" vim-which-key look
let g:which_key_sep = '→'
let g:which_key_use_floating_win = 0
let g:which_key_max_size = 0
autocmd! FileType which_key
autocmd FileType which_key set laststatus=0 noshowmode noruler
  \| autocmd BufLeave <buffer> set laststatus=2 noshowmode ruler

" defining keybindings
let g:which_key_map = {}
let g:which_key_map['/'] = [ 'gc' , 'comment' ]
let g:which_key_map['.'] = [ ':e $MYVIMRC' , 'open init' ]
let g:which_key_map['='] = [ '<C-W>=' , 'balance windows' ]
let g:which_key_map['h'] = [ '<C-W>s' , 'split below']
let g:which_key_map['p'] = [ ':Files' , 'search files' ]
let g:which_key_map['v'] = [ '<C-W>v' , 'split right']

let g:which_key_map.a = {
      \ 'name' : '+actions' ,
      \ 'h' : [':let @/ = ""' , 'remove search highlight'],
      \ 'S' : [':s/\%V\(.*\)\%V/"\1"/' , 'surround'],
      \ 'o' : [':set spell!' , 'orthography'],
      \ }

let g:which_key_map.b = {
      \ 'name' : '+buffer' ,
      \ 'f' : ['bfirst' , 'first-buffer'],
      \ 'l' : ['blast' , 'last buffer'],
      \ 'n' : ['bnext' , 'next-buffer'],
      \ 'p' : ['bprevious' , 'previous-buffer'],
      \ '?' : ['Buffers' , 'fzf-buffer'],
      \ }

let g:which_key_map.f = {
      \ 'name' : '+fuzzy-find' ,
      \ '/' : [':History/' , 'history'],
      \ ';' : [':Commands' , 'commands'],
      \ 'B' : [':Buffers' , 'opened buffers'],
      \ 'f' : [':Files' , 'files'],
      \ 'h' : [':History' , 'file history'],
      \ 'H' : [':History:' , 'command history'],
      \ 'l' : [':Lines' , 'loaded buffers'] ,
      \ 'b' : [':BLines' , 'current buffer'],
      \ 'M' : [':Maps' , 'normal maps'] ,
      \ 'p' : [':Helptags' , 'help tags'] ,
      \ 'c' : [':Colors' , 'color schemes'],
      \ 'g' : [':Rg' , 'text Rg'],
      \ 'w' : [':Windows' , 'search windows'],
      \ 'z' : [':FZF ~' , 'files in home'],
      \ 't' : [':Telescope builtin' , 'telescope'],
      \ }

let g:which_key_map.t = {
      \ 'name' : '+terminal' ,
      \ ';' : [':FloatermNew --wintype=normal --height=6' , 'terminal'],
      \ 'f' : [':FloatermNew fzf' , 'fzf'],
      \ 'p' : [':FloatermNew python' , 'python'],
      \ 'r' : [':FloatermNew ranger' , 'ranger'],
      \ 't' : [':FloatermToggle' , 'toggle'],
      \ }

let g:which_key_map.g = {
    \ 'name' : '+go_to' ,
    \ 'x' : ['gx' , 'go to link under the cursor and use BROWSER'],
    \ 'e' : ['ge' , 'go to link under the cursor and use vim'],
    \ }

let g:which_key_map.c = {
    \ 'name' : '+Columnize-Selected' ,
    \}

let g:which_key_map.d = {
    \ 'name' : '+diagnostic' ,
    \}

let g:which_key_map.u = {
    \ 'name' : '+ultisnips' ,
    \}

let g:which_key_map.l = {
    \ 'name' : '+lsp' ,
    \}

let g:which_key_map.l.w = {
    \ 'name' : '+workspace' ,
    \}

It is helpful in the following ways:

  1. Copy the keybindings you like.
  2. Copy the whole script if you are following my Neovim guide. Look at all the articles over here. This way you would have fewer problems since everything is tested.

Way Ahead

There is a Lua wrapper for this plugin. Using this wrapper, you can configure vim-which-key in the Lua language. Sometimes, there are some issues associated with using vim-which-key in Neovim. Using the wrapper fixes this. However, I never faced this issue. Look at AckslD/nvim-whichkey-setup.lua to learn more.

You can also use comma along with space to create another popup. But personally, I don’t use that since that will make my configuration unnecessarily complicated. The space popup will be sufficient. Nonetheless, you can set that in a similar fashion. Also, look at the official vim-which-key configuration guide on how to do so.

That’s all, folks. Thanks. If you have any comments or suggestions, put them in the comment section below.

Leave a Comment

Your email address will not be published. Required fields are marked *