quickfix を便利に使う設定

Vim Advent Calendar 2012 の 220 日目の記事です。
最近の Vim Advent Calendar は子供が生まれた結婚したりで大変めでたいことが続いていますね。良いことです。みなさんも Vim Advent Calendar で執筆すれば御利益があるかもしれませんよ? あ、ちなみに私はいつも通りです。特に変わったことはありません。

さて、今回は私が quickfix で行っている設定をいくつか晒してみたいと思います。

設定方法

quickfix ウィンドウは filetype=qf が設定されます。なので、filetype qf に対する ftplugin を書けばよいです。つまり、

~/.vim/after/ftplugin/qf.vim

もしくは

~/vimfiles/after/ftplugin/qf.vim

この辺りに設定を書いていきます。

p で preview

preview と言うか、開いた後すぐに quickfix に戻ってくる設定です。連続して次々と各項目が見たいときに便利です。

noremap <buffer> p  <CR>zz<C-w>p

項目数を statusline に表示する

grepの結果や、makeの結果が何件あるのか、デフォルトだとパッと見ではわからないので、statusline に行数を出すことで対応しています。

setlocal statusline+=\ %L

ちなみに make だと後述する理由で行数だと完全ではないのですが、ここでは面倒なのでそこまで対応してません。

上下をループする

一番下に行ったら一番上に、一番上に行ったら一番下に移動します。

noremap <silent> <buffer> <expr> j <SID>jk(v:count1)
noremap <silent> <buffer> <expr> k <SID>jk(-v:count1)

ここで s:jk() と言う関数を呼んでますが、これは次に紹介するものも機能が一緒に入っているので次で一緒に紹介します。

ジャンプ先のない項目をスキップ

一部の :make の結果などで、複数行のエラーメッセージを返すためにジャンプ先の情報がない行が quickfix 内に発生することがあります。
この場合に、ジャンプ先がない行は jk で移動時にスキップします。
1つ前のループと合わせて、s:jk() は以下のようになっています。

function! s:jk(motion)
  let max = line('$')
  let list = getloclist(0)
  if empty(list) || len(list) != max
    let list = getqflist()
  endif
  let cur = line('.') - 1
  let pos = g:V.modulo(cur + a:motion, max)
  let m = 0 < a:motion ? 1 : -1
  while cur != pos && list[pos].bufnr == 0
    let pos = g:V.modulo(pos + m, max)
  endwhile
  return (pos + 1) . 'G'
endfunction

ちなみに、先ほどの件数を表示するところで行数だけでは不完全と言ったのは、このようにメッセージだけの行があるためです。

項目を手軽に削除する

grep などの結果で、中にはパッと見で必要ないものが混ざっていたりすることがあります。そういったものを、通常のバッファと同じように dd やビジュアルモードでの d で消せるようにしています。

nnoremap <silent> <buffer> dd :call <SID>del_entry()<CR>
nnoremap <silent> <buffer> x :call <SID>del_entry()<CR>
vnoremap <silent> <buffer> d :call <SID>del_entry()<CR>
vnoremap <silent> <buffer> x :call <SID>del_entry()<CR>

function! s:del_entry() range
  let qf = getqflist()
  let history = get(w:, 'qf_history', [])
  call add(history, copy(qf))
  let w:qf_history = history
  unlet! qf[a:firstline - 1 : a:lastline - 1]
  call setqflist(qf, 'r')
  execute a:firstline
endfunction

削除したものを復元する

削除したあと、やっぱり戻したいこともあるかと思います。そんなときのために undo できるようにしてあります。勘のいい人は上の例で消す前の値を w:qf_history に保存しているのに気付いたでしょう。

nnoremap <silent> <buffer> u :<C-u>call <SID>undo_entry()<CR>

function! s:undo_entry()
  let history = get(w:, 'qf_history', [])
  if !empty(history)
    call setqflist(remove(history, -1), 'r')
  endif
endfunction

ちなみに redo は面倒なので設定してません。

まとめ

以上の設定をまとめたのが以下になります。

noremap <silent> <buffer> <expr> j <SID>jk(v:count1)
noremap <silent> <buffer> <expr> k <SID>jk(-v:count1)

noremap <buffer> p  <CR>zz<C-w>p

function! s:jk(motion)
  let max = line('$')
  let list = getloclist(0)
  if empty(list) || len(list) != max
    let list = getqflist()
  endif
  let cur = line('.') - 1
  let pos = g:V.modulo(cur + a:motion, max)
  let m = 0 < a:motion ? 1 : -1
  while cur != pos && list[pos].bufnr == 0
    let pos = g:V.modulo(pos + m, max)
  endwhile
  return (pos + 1) . 'G'
endfunction

setlocal statusline+=\ %L

nnoremap <silent> <buffer> dd :call <SID>del_entry()<CR>
nnoremap <silent> <buffer> x :call <SID>del_entry()<CR>
vnoremap <silent> <buffer> d :call <SID>del_entry()<CR>
vnoremap <silent> <buffer> x :call <SID>del_entry()<CR>
nnoremap <silent> <buffer> u :<C-u>call <SID>undo_entry()<CR>

if exists('*s:undo_entry')
  finish
endif

function! s:undo_entry()
  let history = get(w:, 'qf_history', [])
  if !empty(history)
    call setqflist(remove(history, -1), 'r')
  endif
endfunction

function! s:del_entry() range
  let qf = getqflist()
  let history = get(w:, 'qf_history', [])
  call add(history, copy(qf))
  let w:qf_history = history
  unlet! qf[a:firstline - 1 : a:lastline - 1]
  call setqflist(qf, 'r')
  execute a:firstline
endfunction

こんな感じで、気になった部分は割と簡単に改善できるのが Vim の良いところです。
普段から Vim を触っていれば、もっとこんな風にできたら良いのに!と思うことはよくあると思います。そういうのは放置しないでどんどん改善していきましょう!