Vim 外掛: vim-which-key
emacs 使用者相信應該對於 emacs-which-key 很熟悉,如果你在一定時間沒有輸入下一個按鍵,它會自動顯示接下來可能的所有快捷鍵對映,這對於常常需要多組合鍵的 emacs 來說很是方便。我在一開始使用 spacemacs 的時候,就被這個功能所吸引。不過一直以來 vim 中都缺少像 emacs-which-key 這樣“形神兼備”的外掛,這一點我在 space-vim 的 README 中也一早有提及。
vim-leader-guide 是 vim 裡出現的一個比較接近的外掛,它主要借鑑自 guide-key,而 guide-key 是 emacs-which-key 的前身,目前已經不更新了,上一次 commit 還是在 2015 年。emacs-which-key 作為 guide-key 的繼任者對它進行了重寫,並加入了一些新的特性。
因為 vim-leader-guide 之前長時間沒有更新,而且在我看來不夠 fancy,所以我對它進行了一個大的改造,也就是現在的 vim-which-key,主要改進的地方有:
-
大量 UI 細節上的調整與改進,比如:
- 底部輸出當前輸入的內容
- 高亮群組
- 每列支援按照分隔符對齊
- 必要時更新視窗內容,而不是每一次都關閉再開啟一個新視窗
- …
-
使用
getchar()
而不是input()
進行互動,快速響應使用者鍵入的每一個字元。 -
引入針對 vim-which-key 的 timeout 解決由於
getchar()
帶來的一些不友好體驗。
使用要求
timeout
,即不要在 vimrc 中設定 set notimeout
。另外可以自行設定 timeout 的時長:
" 預設超時是 1000 ms,如果不想那麼長的話,你可以在 vimrc 中設定更短一些
set timeoutlen=500
安裝使用
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 的作用,所以一般還需要稍加一點自定義配置來實現一個比較好的效果。
自定義配置
要想實現上圖中的效果,只需要再多額外兩步配置。
第一步是用一個 dict 定義你要展示的資訊和執行的操作,用過 vim-leader-guide 的應該都很熟悉,跟它很像,不同的地方主要有:
- 對於使用者已經定義的快捷鍵,可以只傳入一個字串描述該快捷鍵
- 支援解析
<C-W>
等鍵位
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:
" 第一個元素表明執行的操作,第二個是該操作的介紹
" =======================================================
" 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 過程中有任何問題,請到 GitHub 上的 issue 裡面提,提 issue時請說明重現步驟並提供可重現的最小 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")