Vim. ファイラーまがいの簡易オートプレビュー機能を vimgrep と quickfix で実装する

vim のファイラーに勝手にプレビューを表示する機能があったら便利だと思った。いつも必要というわけではないが、時々あったらいいのにと感じる。

たとえば、マークダウンファイルのフロントマターに draft: true があるかどうかを見れば下書きで放置されていたりするページの発見と内容確認が同時に達成される。また、ファイル名だけでは内容を判別しにくい日記のようなものの場合も便利だ。

vim 備え付けのファイラー netrw はいちいち p を押さないとプレビューを表示してくれない。そうじゃなくて、カーソルを移動させるたびに問答無用でドカドカとプレビューを表示してほしい。単純な対応としては、netrw 限定で <C-n>jp, <C-p>kp などと設定しておけば随時プレビューを表示させることはできるが、ものすごくもっさりした動きになる。

ranger などの TUI のファイラーは勝手にプレビューを表示してくれるが、これも遅い。

実際の利用目的を考えると、vim の中にいるときにそれが実現されてこそ、と思う。ただし、そのような機会はそれほど多いわけではないのでリッチなファイラープラグインを導入するのは気が引ける。どのプラグインならこの自動プレビュー機能を使えるのかもきちんと調べていない。

検索対象が事前にある程度わかっている場合、簡単に実現できそうな手段として vimgrep と quickfix の機能を使うのが良さそうだ。特定の条件に当てはまるファイル群の内容をざーっと確認したいという目的にぴったりだと思う。

①: vimgrep (外部grepでも構わない)で、検索

:vimgrep 検索ワード 対象ファイル

例) カレントディレクトリのマークダウンファイルが対象
:vimgrep 'draft: true' ./*.md 

実行すれば見つかったファイル群の1番目が表示される。これはデフォルトで使える機能。

②: quickfix の window (検索で見つかったファイルの一覧) は autocmd の設定で勝手に開くようにする

autocmd QuickFixCmdPost *grep* cwindow

ここまではよく見かける方法なので定番の設定なのだと思う。というかこれだけでやりたいことの9割は実現できてしまっている。これで十分と言えば十分だが、テキトーに操作しながらゆったりとファイルの中身を確認したいのでちょっとだけ工夫する。

この次から、ファイラーまがいの動きを実現するための設定をする。

③: ②で開いた window を左側に開くようにしておく

function! s:qf_window_settings() abort
  wincmd H
  vertical resize 70
endfunction

autocmd Filetype qf call s:qf_window_settings()

quickfix の window はデフォルトでは画面下半分に出てくるので、左側に開くようにしておく。右側にはファイルの中身が表示される。右側にはファイルの中身が表示される。

④: ③の 左側の window の中でカーソル移動 (j,k,gg,Gなど) と右側の表示を連動させる

function! s:cursor_move() abort
  let lnum = line('.')
  execute lnum . 'cc'
  wincmd p
endfunction

autocmd CursorMoved * if &buftype ==# 'quickfix' | call s:cursor_move() | endif

左側の window の中でカーソル移動 (j,k,gg,Gなど) が発生するたびに、右側には対応するファイルの中身が表示されるようにする。

本来、一覧から表示するファイルを選択するときは Enter を押さないと右側の中身のほうは変化しない。また、どちらの window にいようが、 :[count]cnext, :[count]cprevious, :[nr]cc などのコマンドを打てばファイルへの移動自体はできる。しかしこのコマンドの入力が数が増えてくるとなかなかめんどくさいということでキーマップが割り当てられることが多い。

しかし今回はキーマップを設定しなくてもいいようにしたいので、カーソルが移動すれば勝手に表示も変わるように autocmd を設定する。

quickfix の window はデフォルトでは編集不可能なのでそのままにしておく。そうすればカーソルが移動した先の行の番号 lnum は、そのまま quickfix リストのN番目に対応する関係が崩れない。

一旦完成

複雑なことには対処できないので、なるべく触らなくて良いところは触らないようにしている。

結局ただのちょっとしたラッパーだ。

" vimgrep と quickfix で擬似的にファイラーのプレビューのような使い方をする
function! s:qf_update_preview() abort
  let lnum = line('.')
  execute lnum . 'cc'
  wincmd p
endfunction

function! s:preview_disable() abort
  augroup _preview
    autocmd!
  augroup END
endfunction

function! s:qf_setup_window() abort
  if &filetype !=# 'qf'
    return
  endif
  wincmd H
  vertical resize 70

  augroup _preview
    autocmd! 
    autocmd CursorMoved <buffer> call s:qf_update_window()
    autocmd BufWipeout, BufDelete <buffer> call s:preview_disable()
  augroup END
endfunction

function! s:preview(word, ...) abort
  " 可変長引数 ... をそのままファイルパターンとして結合
  let files = join(a:000, ' ')

  silent execute 'vimgrep ' . a:word . ' ' . files
  " silent execute 'grep ' . a:word . ' ' . files

  cwindow
  wincmd p
  call s:qf_setup_window()
  redraw!
endfunction

command! -nargs=+ Preview call s:preview(<f-args>)

" カレントディレクトリ以下のマークダウンファイルの中で下書き状態のものを探して一覧表示、内容をプレビュー
nnoremap <F8> :<C-u>Preview 'draft: true' **/*.md<CR>