1. 程式人生 > >Vim 外掛: vim-which-key

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() 帶來的一些不友好體驗。

vim-which-key

使用要求

vim-which-key

對於 vim 的版本和特性基本沒什麼要求,需要注意的一點是不要關閉選項 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>

除了 leaderlocalleader,如果想要提示其他鍵也可以:

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")