Neovimのファジーファインダー比較 telescope/fzf-lua/snacks.picker
Neovimでファイル名や文字列を探すプラグインは、長いあいだtelescope.nvimがほぼ定番でした。いまはfzf-luaと、snacks.nvimに入っているsnacks.pickerが並び、telescope自身も2025年11月のv0.2.0で中身が変わっています。古い設定例のまま入れると、nvim-treesitterのmain移行と重なってプレビューでエラーが出る組み合わせもあります。
そこで3つを同じ方法でNeovim 0.12.4に入れ、同じリポジトリで同じ操作を打って並べました。比べたのは、ファイル名で探す・全文grep・前回の検索に戻る・結果をquickfixへ送る、の4操作です。加えて、外部コマンドが無いときにどう壊れるか、起動にかかる時間、プレビューのハイライトを見ました。
検証環境:NVIM v0.12.4(macOS、Apple Silicon)。プラグインはすべてvim.pack.add()で入れました。telescope.nvim v0.2.1(比較のため0.1.8とv0.1.9も)とplenary.nvim、fzf-lua(2026-10-05のコミットed0e0ea。fzf-luaは版番号のタグを打っていません)、snacks.nvim v2.31.0の後のコミット882c996、nvim-treesitterはmainブランチ(e289100f)です。探す対象はNeovim本体のソース(2026-10-08時点、gitで管理されたファイル3,935本)。最初にripgrep・fd・fzfが無い状態で試し、そのあとHomebrewでripgrep 15.2.0・fd 10.5.0・fzf 0.74.4を入れて同じ操作をやり直しました。載せたメッセージ・件数・時間はすべて手元の実行結果です。
| 操作 | telescope | fzf-lua | snacks.picker |
|---|---|---|---|
| ファイル名で探す | :Telescope find_files | :FzfLua files | Snacks.picker.files() |
| 全文grep | :Telescope live_grep | :FzfLua live_grep | Snacks.picker.grep() |
| 前回に戻る | :Telescope resume | :FzfLua resume | Snacks.picker.resume() |
| quickfixへ | Ctrl-q | Alt-aのあとAlt-q | Ctrl-q |
目次(9項目)
ファジーファインダーで何をするか
ファジーファインダーは、候補の一覧に対して打った文字で絞り込み、選んだものを開く道具です。候補の元はファイル名、grepの結果、開いているバッファ、ヘルプのタグ、最近開いたファイルなどで、3つのプラグインはどれもこれを一通り揃えています。違いが出るのは、候補を作る外部コマンドへの頼り方、絞り込みをどこで計算するか、一覧の中でのキー操作の3点です。
文字で絞るだけなら、Neovimの標準にも:findやコマンドラインの補完があります。ただ、打つたびに一覧とプレビューが更新される画面は標準にはありません。そこを埋めるのがこの3つです。ディレクトリを木構造でたどる道具は別の系統で、そちらはNeovimのファイラー比較で扱いました。場所が分かっているときはファイラー、名前や中身の一部しか覚えていないときはファインダー、と分けると迷いません。
3つの立ち位置と外部コマンドへの依存
telescope.nvimは絞り込みも画面もLuaで組んであり、plenary.nvimに依存します。fzf-luaは名前のとおりfzf本体を端末バッファの中で動かし、絞り込みをfzfに任せます。snacks.pickerはsnacks.nvimという部品集の1つで、ほかのプラグインに依存せず、絞り込みもLuaで行います。vim.pack.add()に書く行は、telescopeだけが2本、残りは1本です。
vim.pack.add({
'https://github.com/nvim-lua/plenary.nvim', -- telescope が使う
'https://github.com/nvim-telescope/telescope.nvim',
'https://github.com/ibhagwan/fzf-lua', -- fzf 本体は別に要る
'https://github.com/folke/snacks.nvim',
})
どれもファイル一覧とgrepの候補作りは外部コマンド(rg・fd・find)に頼ります。rg・fd・fzfが入っていない状態で、それぞれを開いた結果が次の表です。
| プラグイン | ファイル名で探す | 全文grep |
|---|---|---|
| telescope | 動く(findで代用。名前の頭に./が付く) | エラーで開かない |
| fzf-lua | エラーで開かない | エラーで開かない |
| snacks.picker | 動く(findで代用) | 窓は開くが0件のまま |
telescopeのlive_grepは窓を開く前に止まり、次のメッセージを出します。
[telescope.live_grep]: 'ripgrep', or similar alternative, is a required dependency for the live_grep picker. Visit https://github.com/BurntSushi/ripgrep#installation for installation instructions.
fzf-luaはfzf(または互換のsk)が無いと、:FzfLua filesもlive_grepも同じ1行で終わります。原文のまま載せます。
[Fzf-lua] 'fzf' is not a valid executable, aborting. Please make sure 'fzf' is in installed.
いちばん気づきにくいのはsnacks.pickerのgrepでした。窓が開いて入力もできるのに、件数が0/0のまま増えず、:messagesにFailed to spawn rgが残るだけです。足りないものは:checkhealth snacksのpickerの節にはっきり出ます。
Snacks.picker ~
- ✅ OK setup {enabled}
- ❌ ERROR `vim.ui.select` is not set to `Snacks.picker.select`
- ✅ OK 'git' `git version 2.42.0`
- ❌ ERROR Tool not found: 'rg'
- ❌ ERROR 'rg' is required for `Snacks.picker.grep()`
- ❌ ERROR None of the tools found: 'fd', 'fdfind'
- ✅ OK 'find'
- ✅ OK `Snacks.picker.files()` is available
入れたらまず健康診断を開くのが早道です。名前は:checkhealth telescope・:checkhealth fzf_lua(下線)・:checkhealth snacksです。macOSではfzf-luaの診断が'find'の行をfind: illegal option -- -のERRORにしますが、BSD版のfindが--versionを受け付けないだけで、fdを入れたあとの:FzfLua filesは問題なく動きました。snacksのvim.ui.selectのERRORも、vim.ui.selectを差し替えていないという意味で、ピッカーの動作には関係しませんでした。
同じ4操作を3つで比べる
Homebrewでrg・fd・fzfを入れてから、Neovimのソースのルートで4つの操作を打ちました。grepの語はnvim_buf_set_linesです。quickfixの題は、送ったあとgetqflist({'title': 1})で取りました。
| 項目 | telescope | fzf-lua | snacks.picker |
|---|---|---|---|
| ファイル数 | 3,877 | 3,934 | 3,877 |
| grepの件数 | 703 | 703 | 703 |
| 前回に戻る | 入力欄の語が戻る | 語と選択数まで戻る | 入力欄の語が戻る |
| 全件をquickfixへ | Ctrl-q | Alt-a→Alt-q | Ctrl-q |
| 選んだものだけ | Alt-q | Alt-q | Ctrl-q(選択があればそれだけ) |
| quickfixの題 | Live Grep (nvim_buf_set_lines) | [FzfLua] live_grep: nvim_buf_set_lines | :setqflist() |
ファイル数がfzf-luaだけ57件多いのは、fzf-luaのfilesが隠しファイルを既定で含めるからです(設定のhidden = true)。増えたのは.github/の42本と、.clang-formatや.editorconfigのようなドットで始まるファイルでした。require('fzf-lua').setup({ files = { hidden = false } })にすると3,877件にそろいます。grepの件数は3つとも同じで、どれもrgの出力を並べているので、差が出るのは並べ方と、結果をどう持ち出すかのほうです。
quickfixへの送り方は揃っていません。telescopeとsnacksはCtrl-qで、何も選んでいなければ全件を送ります。fzf-luaのAlt-qは「選んだものを送る」動作なので、全件送るには先にAlt-a(全選択の切り替え)を押します。複数選んだ状態でEnterを押してもquickfixに入ります。検証はtmuxからAltを送って行いました。macOSのTerminal.appやiTerm2では、OptionキーをMetaとして送る設定にしておかないとAltの組み合わせが届きません。
resumeは、閉じた直前のピッカーを開き直します。fzf-luaは選択状態まで残り、703件を全選択して閉じたあとに:FzfLua resumeで開くと、件数表示の(703)がそのまま出ていました。snacksにはExコマンドが無く(exists(':Snacks')は0)、Luaの関数を呼ぶ形なので、キーに割り当てて使います。
vim.keymap.set('n', '<leader>ff', function() Snacks.picker.files() end)
vim.keymap.set('n', '<leader>fg', function() Snacks.picker.grep() end)
vim.keymap.set('n', '<leader>fr', function() Snacks.picker.resume() end)
telescope v0.2で変わったこと
telescope.nvimは長く0.1.xの系統が使われ、導入例もtag = '0.1.8'やbranch = '0.1.x'が普通でした。2025-11-29のv0.2.0でNeovim 0.9のサポートが切られ(最低0.10.4)、0.12で非推奨になったAPIが置き換えられ、ハイライトにnvim-treesitterを使わなくなりました。リリースノートの該当箇所です。
It also updates treesitter functionality to rely purely on Nvim core API, removing nvim-treesitter as a requirement (as long as parsers and queries are available, no matter the source).
Release v0.2.0 · nvim-telescope/telescope.nvim
この変更が効いてくるのは、nvim-treesitterをmainブランチに上げたときです。0.1.8とnvim-treesitter mainを一緒に入れ、:Telescope find_filesでCのファイルにカーソルを当てると、プレビューが空のまま次のエラーが出ました。
vim.schedule callback: ...re/opt/telescope.nvim/lua/telescope/previewers/utils.lua:135: attempt to call field 'ft_to_lang' (a nil value)
stack traceback:
...re/opt/telescope.nvim/lua/telescope/previewers/utils.lua:135: in function 'ts_highlighter'
...re/opt/telescope.nvim/lua/telescope/previewers/utils.lua:119: in function 'highlighter'
0.1.8のソースを読むと、pcall(require, "nvim-treesitter")が通ればtreesitterでの色付けに進み、nvim-treesitter.parsersのft_to_langを呼びます。mainブランチにもnvim-treesitterというモジュール自体はあるので判定は通り、そこで消えた関数を呼んで落ちます。nvim-treesitterのmain移行で消えた関数の1つがここに当たります。
組み合わせごとに、プレビューのバッファでvim.treesitter.highlighter.activeと'syntax'を見た結果です。
| telescope | nvim-treesitter | プレビュー |
|---|---|---|
| 0.1.8 | なし | 正規表現のsyntax=cで色が付く |
| 0.1.8 | main | 上のエラー、プレビューは空 |
| v0.1.9 | main | treesitterで色が付く |
| v0.2.1 | なし | treesitterで色が付く |
| v0.2.1 | main | treesitterで色が付く |
v0.2.1はnvim-treesitterを入れなくてもCのプレビューがtreesitterで色付きました。Neovim 0.12.4がCのパーサーを同梱しているからで、同梱されていない言語はパーサーとクエリを別に用意する必要があります。fzf-luaとsnacks.pickerのプレビューも、nvim-treesitter無しでtreesitterの色付けが付いていました。
気をつけたいのは0.1.xブランチです。リポジトリを見ると、0.1.xブランチの先頭は2024-05-24のコミットa0bbec2で、0.1.8と同じ場所を指していました。branch = '0.1.x'と書いた古い設定は、更新をかけても0.1.8から動きません。一方v0.1.9(2025-11-06。リリースノートでNeovim 0.9に対応する最後の版とされています)は、プレビューがNeovim本体のtreesitterを使う形になっていて、nvim-treesitter mainと組み合わせてもエラーは出ませんでした。Neovim 0.10.4以上ならv0.2系に上げるのが素直です。Neovim 0.12の標準プラグイン管理 vim.packなら、版の範囲で書けます。
vim.pack.add({
'https://github.com/nvim-lua/plenary.nvim',
-- 0.2 系の最新タグを使う
{ src = 'https://github.com/nvim-telescope/telescope.nvim', version = vim.version.range('0.2') },
})
これで入ったのはv0.2.2でした。v0.2.2はタグだけで、GitHubのリリースのページはv0.2.1までしかありません。v0.2.1からの差分はgit_statusの解析とプレビュー切り替えの修正、READMEの直しなど5コミットです。
fzf-luaとsnacks.pickerで引っかかるところ
一覧の中でノーマルモードが使えるかは、3つで分かれました。telescopeとsnacksは、入力欄でEscを押すとノーマルモードに入り、j/kで候補を移ってEnterで開けます。snacksでEsc j j Enterと押すと、3番目の候補のsrc/bit.cが開きました。ノーマルモードでもう一度Escを押すと閉じます。
fzf-luaはEscを押した時点でピッカーが閉じます。中身は端末バッファで動くfzfなので、Ctrl-\ Ctrl-nで端末のノーマルモードには入れます。ただ、そこでjを押して動くのは画面上のカーソルだけで、fzfの選択は動きません。iで戻ってEnterを押すと、開いたのは先頭のsrc/coverity-model.cでした。fzf-luaで候補を移るのはCtrl-j/Ctrl-k(またはカーソルキー)で、Ctrl-jを2回押してEnterならsrc/bit.cが開きます。シェルでfzfを使っている人には同じ指の動きですが、Vimのキーで一覧を歩きたい人には合いません。
snacks.pickerは、READMEの例ではsetup()にpicker = { enabled = true }を渡します。手元ではsetup()を呼ばずにSnacks.picker.files()を呼んでも3,877件が出ました。enabledが効くのはvim.ui.selectの差し替えのような、ピッカー以外の場所に手を入れる部分です。snacks.nvimはダッシュボードや通知など多くの部品を抱えていて、:checkhealth snacksには使っていない部品のsetup {disabled}というWARNINGが並びますが、これは使っていないという表示です。
起動時間と大きなリポジトリでの速さ
init.luaに3つのどれか1つだけを書き、--startuptimeで20回ずつ測って、NVIM STARTEDまでの時間の中央値を取りました。
nvim --headless --startuptime st.txt -c qa
grep 'NVIM STARTED' st.txt
| 設定 | 起動まで(中央値) | init.luaの読み込み |
|---|---|---|
| プラグインなし | 17.8ms | 0.2ms |
telescope(setup()あり) | 27.9ms | 9.0ms |
fzf-lua(setup()あり) | 30.6ms | 12.4ms |
snacks(pickerを有効) | 20.1ms | 2.0ms |
telescope(setup()なし) | 21.3ms | 2.2ms |
fzf-lua(setup()なし) | 20.2ms | 1.8ms |
差の大半は、setup()を呼んだ瞬間に本体のモジュールを読み込むかどうかでした。fzf-luaはrequire('fzf-lua')だけで10.1msかかっています。fzf-luaは速いという評判は絞り込みの速さの話で、起動の軽さとは別物です。telescopeとfzf-luaは、setup()を呼ばなくても:Telescope find_filesと:FzfLua filesが動き、件数も同じでした。既定のままで使うならsetup()の行は省けます。どれを選んでも差は10ms前後なので、起動時間を理由に選ぶほどの差ではありません。
大きなリポジトリでの速さは、Neovimのソース(約3,900ファイル)では3つとも一覧がすぐに出て、体感で差は分かりませんでした。数値にはしていません。この規模では絞り込みの計算をLuaでやるかfzfでやるかの差が表に出ないので、数万ファイル級のリポジトリでの比較はこの記事の範囲外です。
選び方の目安とプラグイン無しの代わり
- telescope:0.1.xで組んだ設定や
telescope-*の拡張をすでに持っているなら、v0.2系に上げて使い続けるのが手間が少ない。Neovim 0.10.4以上が要ります - fzf-lua:シェルでfzfに慣れていて、同じキーと選択の操作をそのまま使いたい人向け。fzf本体が無いと何も動きません
- snacks.picker:プラグインの依存がいちばん少なく、Escのあとj/kで歩けます。Exコマンドが無いので、キーの割り当てまで自分で書きます
プラグインを足したくないなら、ファイル名で探すだけは標準の:findで足ります。findfuncを使えばfdやgit ls-filesの結果を:findの候補にでき、その書き方はプラグイン無しでファイル名からファイルを探す(:find)にまとめました。grepのほうは、Neovim 0.12.4はrgが入っていると'grepprg'がrg --vimgrep -uuになり、:grep nvim_buf_set_linesでも同じ703件がquickfixに入りました。-uuは.gitignoreを無視して隠しファイルも探す指定なので、ビルド結果のディレクトリがあるリポジトリでは件数が膨らみます。
3つを行き来しながら検証していて何度もやったのが、fzf-luaでEscを押してgrepの結果を消すことでした。直前までtelescopeとsnacksで「Escでノーマルモード、jで下」を繰り返していたので、手が同じ動きをします。703件を全選択した直後に閉じてしまったこともありましたが、:FzfLua resumeで選択ごと戻ったので、resumeを最初にキーへ割り当てておく理由が身にしみました。もう1つ意外だったのは0.1.xブランチで、名前からして0.1系の最新を追うものだと思っていたのに、先頭は2024年5月のまま止まっていました。
まとめ
3つとも、ファイル名で探す・grep・resume・quickfixへ送る、の4操作は揃っていて、grepの件数も同じでした。分かれたのは外部コマンドが無いときの壊れ方(fzf-luaはfzfが無いと全部止まり、snacksのgrepは黙って0件)、一覧の中でj/kが使えるか(fzf-luaだけ使えない)、quickfixへ全件送るキー(fzf-luaだけAlt-aが要る)の3点です。
telescopeを使い続けるなら、tag = '0.1.8'やbranch = '0.1.x'のままにしないことです。nvim-treesitterをmainに上げるとプレビューが落ちます。v0.2系ならnvim-treesitterが無くても色が付き、起動の差もsetup()を省けば数msに収まります。