從 emacs-which-key 到 vim-which-key
emacs 用戶相信應該對于 emacs-which-key 很熟悉,如果你在一定時間沒有輸入下一個按鍵,它會自動顯示接下來可能的所有快捷鍵映射,這對于常常需要多組合鍵的 emacs 來說很是方便。我在一開始使用 spacemacs 的時候,就被這個功能所吸引。不過一直以來 vim 中都缺少像 emacs-which-key 這樣“形神兼?zhèn)洹钡牟寮?,這一點我在 space-vim 的 README 中也一早有提及。
vim-leader-guide 是 vim 里出現(xiàn)的一個比較接近的插件,它主要借鑒自 guide-key,而 guide-key 是 emacs-which-key 的前身,目前已經不更新了,上一次 commit 還是在 2015 年。emacs-which-key 作為 guide-key 的繼任者對它進行了重寫,并加入了一些新的特性。
因為 vim-leader-guide 之前長時間沒有更新,而且在我看來不夠 fancy,所以我對它進行了一個大的改造,也就是現(xiàn)在的 vim-which-key,主要改進的地方有:
-
大量 UI 細節(jié)上的調整與改進,比如:
- 底部輸出當前輸入的內容
- 高亮群組
- 每列支持按照分隔符對齊
- 必要時更新窗口內容,而不是每一次都關閉再打開一個新窗口
- ......
使用
getchar()而不是input()進行交互,快速響應用戶鍵入的每一個字符。引入針對 vim-which-key 的 timeout 解決由于
getchar()帶來的一些不友好體驗。

使用要求
vim-which-key 對于 vim 的版本和特性基本沒什么要求,需要注意的一點是不要關閉選項 timeout,即不要在 vimrc 中設置 set notimeout。另外可以自行設置 timeout 的時長:
" 默認超時是 1000 ms,如果不想那么長的話,你可以在 vimrc 中設置更短一些
set timeoutlen=500
安裝使用
如果使用 vim-plug:
Plug 'liuchengxu/vim-which-key'
let g:mapleader = "\<Space>"
let g:maplocalleader = ","
nnoremap <silent> <leader> :WhichKey '<Space>'<CR>
nnoremap <silent> <localleader> :WhichKey ','<CR>
這是使用 vim-which-key 的最小配置,它會自動解析用戶自定義的 <leader> 和 <localleader> 相關快捷鍵。但是通常來說,通過自動解析所展示的內容并不能起到 cheatsheet 的作用,所以一般還需要稍加一點自定義配置來實現(xiàn)一個比較好的效果。
自定義配置
要想實現(xiàn)上圖中的效果,只需要再多額外兩步配置。
第一步是用一個 dict 定義你要展示的信息和執(zhí)行的操作,用過 vim-leader-guide 的應該都很熟悉,跟它很像,不同的地方主要有:
- 對于用戶已經定義的快捷鍵,可以只傳入一個字符串描述該快捷鍵
- 支持解析
<C-W>等鍵位
更詳細的樣例可以參考 space-vim 的 leader.vim, 它也是截圖中的配置。
let g:which_key_map = {}
" `name` 是一個特殊字段,如果 dict 里面的元素也是一個 dict,那么表明一個 group,比如 `+file`, 就會高亮和顯示 `+file` 。默認是 `+prefix`.
" =======================================================
" 基于已經存在的快捷鍵映射,直接使用一個字符串說明介紹信息即可
" =======================================================
" You can pass a descriptive text to an existing mapping.
let g:which_key_map.f = { 'name' : '+file' }
nnoremap <silent> <leader>fs :update<CR>
let g:which_key_map.f.s = 'save-file'
nnoremap <silent> <leader>fd :e $MYVIMRC<CR>
let g:which_key_map.f.d = 'open-vimrc'
nnoremap <silent> <leader>oq :copen<CR>
nnoremap <silent> <leader>ol :lopen<CR>
let g:which_key_map.o = {
\ 'name' : '+open',
\ 'q' : 'open-quickfix' ,
\ 'l' : 'open-locationlist',
\ }
" =======================================================
" 不存在相關的快捷鍵映射,需要用一個 list:
" 第一個元素表明執(zhí)行的操作,第二個是該操作的介紹
" =======================================================
" Provide commands(ex-command, <Plug>/<C-W>/<C-d> mapping, etc.) and descriptions for existing mappings
let g:which_key_map.b = {
\ 'name' : '+buffer' ,
\ '1' : ['b1' , 'buffer 1'] ,
\ '2' : ['b2' , 'buffer 2'] ,
\ 'd' : ['bd' , 'delete-buffer'] ,
\ 'f' : ['bfirst' , 'first-buffer'] ,
\ 'h' : ['Startify' , 'home-buffer'] ,
\ 'l' : ['blast' , 'last-buffer'] ,
\ 'n' : ['bnext' , 'next-buffer'] ,
\ 'p' : ['bprevious' , 'previous-buffer'] ,
\ '?' : ['Buffers' , 'fzf-buffer'] ,
\ }
let g:which_key_map.l = {
\ 'name' : '+lsp' ,
\ 'f' : ['LanguageClient#textDocument_formatting()' , 'formatting'] ,
\ 'h' : ['LanguageClient#textDocument_hover()' , 'hover'] ,
\ 'r' : ['LanguageClient#textDocument_references()' , 'references'] ,
\ 'R' : ['LanguageClient#textDocument_rename()' , 'rename'] ,
\ 's' : ['LanguageClient#textDocument_documentSymbol()' , 'document-symbol'] ,
\ 'S' : ['LanguageClient#workspace_symbol()' , 'workspace-symbol'] ,
\ 'g' : {
\ 'name': '+goto',
\ 'd' : ['LanguageClient#textDocument_definition()' , 'definition'] ,
\ 't' : ['LanguageClient#textDocument_typeDefinition()' , 'type-definition'] ,
\ 'i' : ['LanguageClient#textDocument_implementation()' , 'implementation'] ,
\ },
\ }
第二步是注冊鍵位與對應的 dict,這一步比較簡單,不要忘記就行。
call which_key#register('<Space>', "g:which_key_map")
nnoremap <silent> <leader> :<c-u>WhichKey '<Space>'<CR>
vnoremap <silent> <leader> :<c-u>WhichKeyVisual '<Space>'<CR>
除了 leader 和 localleader,如果想要提示其他鍵也可以:
nnoremap <silent> ] :<c-u>WhichKey ']'<CR>
nnoremap <silent> [ :<c-u>WhichKey '['<CR>
更多介紹請參看 vim-which-key 的 README 和 doc。
如果在使用 vim-which-key 過程中有任何問題,請到 GitHub 上的 issue 里面提,提 issue時請說明重現(xiàn)步驟并提供可重現(xiàn)的最小 vimrc,比如這樣:
set nocompatible
call plug#begin()
Plug 'liuchengxu/vim-which-key'
call plug#end()
let g:mapleader="\<Space>"
nnoremap <silent> <leader> :<c-u>WhichKey '<Space>'<CR>
nnoremap <silent> <localleader> :<c-u>WhichKey ','<CR>
nnoremap <Leader>a<Tab> :echom "Hello, World"<cr>
nnoremap <Leader>1 :echom "THis is one"<cr>
let g:which_key_map = {}
let g:which_key_map.a = {
\ 'name':"Test",
\ '<Tab>':"Hello world"
\}
let g:which_key_map.1 = "One"
call which_key#register('<Space>', "g:which_key_map")