Vim/Neovimでgrep結果をいい感じにフィルタリングして一括編集する~quickfixにプラグインを添えて~
はじめに
grepした結果を一括で編集する方法はたくさんあります。
しかしgrepした結果を確認すると「編集したくない対象が紛れ込んでおり、それを除外するために正規表現を考えなおす」なんて経験はありませんか。
今回は雑にgrepした結果を直接編集して絞り込み、絞り込んだ対象を一括編集する方法を紹介します。
色々ありますが、今回はquickfixというVimの組み込み機能をプラグインで拡張して実現する方法を紹介します。
結論
Neovimの場合は以下のどちらかの組み合わせを使いましょう。
Vimの場合はvim-qfreplaceとvim-qfeditを使いましょう。
操作の全体像
操作の流れは以下です。
- grepした結果をquickfixで表示する
- quickfixの結果から除外したいものを消す
- quickfixの結果を編集して一括適用する
今回は私のZennの全ての記事の先頭に「車アイコンによる、」という文言を付けたくなったという想定で説明します。
それでは順番に見ていきましょう。
grepした結果をquickfixで表示する
様々な方法がありますが、プラグインを利用するケースと利用しないケースをひとつずつ紹介します。
:Telescope live_grep
を利用する(Neovimのみ)
Neovimユーザーに大人気のtelescope.nvimを使った方法です。
:Telescope live_grep
を実行し、title
という文字列を検索するとこんな感じです。
この状態で<C-q>
を押してみましょう。
画面下部にあらわれたのがquickfixです。
quickfixでEnterを押すと該当箇所にジャンプできます。
ただquickfixは編集不可なので、このままでは編集できません。
:grep
を使う
Vimには:vimgrep
と外部のgrepプログラムを利用する:grep
があります。
2025年現在、grepと言えばripgrepを使いたいので、:grep
を使います。
:grep title | copen
を実行すると以下のようにgrepの結果が表示され、hit-enter-promptが表示されます。
ここでEnterを押すとquickfixが表示されます。
:grep
でripgrep
を使う
:grep
は'grepprg'
の値をgrepプログラムとして実行します。
その結果を'grepformat'
の値に従って解析します。
つまり'grepprg'
と'grepformat'
をripgrep
用に設定すれば良いです。
Neovimはripgrep
がインストールされていればデフォルトで'grepprg'
と'grepformat'
を設定します。
この時'grepprg'
はrg --vimgrep -uu
と設定されるため、ripgrepのオプションを変えたい場合は'grepprg'
を変更しましょう。
私は-uu
の部分を変えたり、--smartcase
を付けています。
Vimの場合は以下のように自分で設定する必要があります。
if executable('rg')
let &grepprg = 'rg --vimgrep --smart-case --hidden'
set grepformat=%f:%l:%c:%m
endif
ripgrep
のオプションはお好みにあわせて編集してください。
vim-qfreplace
+ vim-qfedit
構成の場合
vim-qfedit
編)
quickfixの結果から除外したいものを消す(quickfixは編集不可能なので、普通は消せません。
しかしvim-qfedit
を使うと不要な行を消すことができます。
さきほどの例ではpackage-lock.jsonの結果が不要なので、試しにdd
で一行消してみましょう。
凄い、消えてます。
それではZennの記事のtitle以外を全て消してしまいましょう。
これでgrepする正規表現などを工夫せず、Zennの記事タイトルの行だけの一覧を得ることができました。楽ちん。
vim-qfreplace
編)
quickfixの結果を一括編集する(それではZennの記事のtitleを一括で編集します。
:Qfreplace
コマンドを実行すると編集バッファが表示されます。
真ん中のバッファがqfreplaceバッファです。
ここを編集して保存してみましょう。
これをpushすれば、私の全ての記事タイトルが「車アイコンによる、」から始まります。
やりませんよ
quicker.nvim
構成の場合(Neovimのみ)
quicker.nvim
編)
quickfixの結果から除外したいものを消す(quicker.nvim
はquickfixの見た目をリッチにします。また通常のバッファと同様に編集可能とし、保存時に実態へ反映させるプラグインです。
oil.nvimの作者が作っているので、思想が似ていますね。
quicker.nvimをインストールするとquickfixは以下のように表情を変えます。
ファイル名、行数、検索結果の開始位置が揃うので見やすいですね。
qfeditと同様に、不要な行を削除していきましょう。
できました。
quicker.nvim
編)
quickfixの結果を一括編集する(直接バッファを書き換えて保存します。
こちらはqfreplaceと違って直接quickfix上で編集します。保存すると適用される点は同一です。
直接編集したいか、安全に別バッファで編集したいかは好みが別れる部分です。
どちらも試してみて、お好みの方をお使いください。
nvim-bqf
でquickfixのジャンプ先のpreviewを表示する
quickfixの結果は、周辺コードと一緒に確認したいことがよくあります。
nvim-bqf
を利用するとpreviewを表示できます。
インストールした様子は以下。
grep結果の妥当性確認の際、実際にジャンプせずに判断できます。
vim.diagnostic.setqflist()
で診断結果をquickfixで表示した際の見通しも良くなります。
ただしデフォルトでいくつかマッピングするので注意が必要です。
特にquicker.nvim
と組み合わせる場合は<C-v>
の矩形選択を良く使うので、私は以下のように無効化しています。
{
'kevinhwang91/nvim-bqf',
ft = 'qf',
config = function()
require('bqf').setup {
auto_enable = true,
func_map = {
vsplit = '',
},
}
end,
},
気を付けること
quicker.nvim
とvim-qfedit
は完全に競合するため、どちらか一方をインストールしましょう。
quicker.nvim
でquickfixを編集した後にvim-qfreplace
を使うとエラーが発生します。
quicker.nvim
を使う場合、quickfixの編集はquicker.nvim
に統一しましょう。
という事で結論に到達しました。
vim-qfedit
でquickfixを編集した場合、ただちにnvim-bqf
のpreviewが更新されます。
quicker.nvim
でquickfixを編集した場合、バッファの保存をするまでnvim-bqf
のpreviewがズレます。
quicker.nvim
は通常のバッファと同様の操作感を提供し、保存操作で編集した内容を適用するという思想のためと考えられます。
nvim-bqf
を併用してquickfixを編集する場合、マッピングが競合する場合は削除するか、他のキーに移動しましょう。
さいごに
昔からのVimmerにとってquickfixはお馴染みの機能ですが、最近使い始めた人にも是非触ってみて欲しいです。
LSの診断結果の閲覧についても以前からtrouble.nvimを使ってリッチに確認できました。
今ではvim.diagnostic.setqflist()
とnvim-bqf
の組み合わせも選択肢に入ると考えています。
専用プラグインの能力も魅力的ですが、quickfixという共通IFを活用することで、プラグイン毎に操作方法を覚える必要が無いことも、また魅力と感じています。
他にvim.lsp.buf.references()
もquickfixを使います。telescope.nvimなどに置き換えている人も是非一度quickfix + nvim-bqf
の使い勝手を試してみて欲しいです。
referencesは同じ一覧を使って、様々な場所にジャンプするシーンがあります。ファジーファインダーのresume機能を使うより、quickfixでジャンプする方が快適な場面もあるのでお試しあれ。
Discussion