ハイパーマッスルエンジニア

Vim、ShellScriptについてよく書く

Exコマンドの補完をfzfでやる

VimのExコマンドって結構長いものであったり、どんなコマンドがあったか全然覚えられない。
TABで一応補完は出てくるが、補完を出した後にインタラクティブに絞り込みたいときって結構あると思う。

fzfでExコマンドのフィルタリングをできるようにした

https://github.com/Rasukarusan/blog-assets/blob/master/vim-ex-completion/demo.gif?raw=true
コマンドラインモードでTABを押すとfzfの絞り込みを開始できる

TABでいきなりfzfを出してもいいし、途中まで入力してから絞り込みを開始してもいい。

環境

  • NeoVim 0.43
  • tmux 3.2rc

ソース

:でコマンドラインモードに入ったらTABでfzfの絞り込みが始まるようになっている。

" =============================================
" Exコマンドの補完をfzfでする
" =============================================
function! CompletionExCmdWithFzf()
    let currentCmdLine = getcmdline()
    let isVisualMode = stridx(currentCmdLine, "'<,'>") != -1 
    let isCall = stridx(currentCmdLine, 'call ') != -1 
    let type = 'command'
    let prefix = ''

    if isCall == 1
      let cmdLines = split(currentCmdLine, ' ')
      let currentCmdLine = len(cmdLines) > 1 ? cmdLines[1] : ''
      let type = 'function'
      let prefix = 'call '
    elseif isVisualMode == 1
      let cmdLines = split(currentCmdLine, '>')
      let currentCmdLine = len(cmdLines) > 1 ? cmdLines[1] : ''
      let type = 'command'
      let prefix = "'<,'>"
    endif

    let result = fzf#run({
      \'source': getcompletion(currentCmdLine, type),
      \ 'tmux': '-p60%,60%',
      \ 'options': '--no-multi --bind tab:down'
      \}
    \)
    if len(result) == 0
      return ''
    endif

    " fzf#runの結果はlist型で返されるので、そのままコマンドラインに返すと^@が末尾に付与される
    " ^@を削除するためjoin()している
    return prefix . join(result, '')
endfunction
cnoremap <TAB> <C-\>eCompletionExCmdWithFzf()<CR>

注意したいのは、tmux popupが使用できる端末に限る。
tmux popupについては別記事で書いたのでそちらを見てもらいたい。

www.rasukarusan.com

いくつかハマったポイントをまとめておく。

コマンドラインモードの入力を取得する

:を打った後につづく文字列を取得するにはgetcmdline()を使う。 ただ、getcmdline()CTRL-\ e {expr}でしか使えない。なのでcnoremap <TAB> <C-\>eCompletionExCmdWithFzf()<CR>という形にしている。

https://vim-jp.org/vimdoc-ja/eval.html#getcmdline()

CTRL-\ e {expr}内でfzfと対話を行う

通常fzfで絞り込みを行いときは下記のようにする。

let result = fzf#run({
  \'source': getcompletion(currentCmdLine, type), 
  \ 'down': '50%'
  \}
\)

ただ、CTRL-\ e {expr}の関数内でfzfを使うと下記のようなエラーが出て使えない。

function CompletionExCmdWithFzf[11]..fzf#run[58]..<SNR>121_execute_term[3]..<SNR>121_split の処理中にエラーが検出されました:
行   31:
E523: ここでは許可されません: botright 33new

全然調べていないので合ってるかわからんが、どうも画面を分割してしまうようなことは駄目っぽい。

ただ、抜け道があってfzfの起動方法をtmux popupにすることで対話が可能になる。

let result = fzf#run({
  \'source': getcompletion(currentCmdLine, type), 
  \ 'tmux': '-p90%,60%'
  \}
\)

tmux popup の思わぬ恩恵がここにある。最高ですね。

終わり

coc.nvimのコマンドとかdeinのコマンドとか長くて打つのがしんどかったけど、これで楽になった。