爆速Fuzzy FinderプラグインSnacks.pickerの使い方

snacks.nvimにあるFuzzy Finderプラグインpickerの設定方法や使い方を紹介します。

pickerの例

Snacksとは?

snacks.nvimは便利プラグインが詰め込まれたプラグイン集です。イメージとしてはmini.nvimに近いです。

この記事で紹介する「picker」の他、「ダッシュボード」「スクラッチバッファ」「lazygitとの連携」など20以上の機能があります。

全部知りたい人はREADMEを見るのがオススメです。

pickerとは?

SnacksのpickerはFuzzy Finderプラグインです。
近いプラグインを挙げるとtelescope.nvimなどがあります。毎月Fuzzy Finderプラグインの産声が聞こえるのは気のせいでしょうか。

pickerはビルトインで使える絞り込みが多いです。2025年2月時点では40以上あります。

「複数選択」「横に開く(vsplit)」「quickfixで開く」「レイアウトを変更する」など、さまざまな操作ができるキーマップも簡単に設定できます。

Snacksのpickerは非同期にファイル検索・マッチ処理をしてくれるため、めっちゃ速い&スムーズです。

fzfのSearch syntaxもサポートされてます。

導入方法

リポジトリ名だけコピペしたい人向け
folke/snacks.nvim

各々が使っているプラグインマネージャーでインストールします。

インストール

lazy.nvimだと次のように書いてインストールできます。

lazy.nvimの例
return {
"folke/snacks.nvim",
priority = 1000,
lazy = false,
---@type snacks.Config
opts = {},
}

遅延読み込みせずに優先度をあげて早めに読み込むことが推奨されています。

遅延読み込み大好き人間にとっては心配かもしれませんが、snacks本体のsetupはautocmdの定義ぐらいで、全部の機能を読み込んでしまうわけではありません。

依存プラグイン

依存プラグインはありません。

アイコンを表示させたい人はmini.iconsnvim-web-deviconsのどちらかを入れておきましょう。

最低限の設定

最低限のプラグインの設定はsetupを呼び出すだけで完了です。

require('snacks').setup()

lazy.nvimの場合、setupの中身はoptsに書くためこの記述は不要です。この書き方について知りたい人は別の記事「lazy.nvimの使い方から起動を爆速にする方法までを解説」を読んでください。

pickerの基本的な使い方

さくっと使い方を紹介します。カスタマイズは後で説明します。

snacksはrequireの他、グローバル変数Snacksから呼び出すこともできます。

下記2つは同じ処理
require("snacks.picker").buffers()
Snacks.picker.buffers()

この記事では面倒なので次のようにpicker.〇〇()と書きます。

local picker = require("snacks.picker")
picker.buffers()

ピッカーのキーマップの一覧

ピッカーを開くと挿入モードになっています。

まずはEscでノーマルモードにしてから?を入力し、キーマップのヘルプを開いてみましょう。

ピッカーのキーマップ

画面の下に、pickerで使えるデフォルトのキーマップが表示されます。

あるいは:help snacks-picker-configでデフォルトの設定を見てみましょう(長いけども)。

バッファ一覧

picker.buffers()

バッファの一覧では、見た目がそのまま表示されます。

pickerの例

winbarやsigncolumn、diagnosticsなどがそのまま表示され、まるでスクショみたいです。

ノーマルモードに切り替えてddを入力するとバッファを削除できます

ファイル系

みんな大好きファイルピッカーもあります。

filesの例
picker.git_files()
picker.files()
picker.recent()

files()ではfdrgfindのコマンドを自動で見つけて使ってくれるようです。

recentは最近開いたファイルです。

smart

picker.smart()

smartを実行すると、buffersrecentfilesの結果を重複を削除して一覧にしてくれます。

バッファならプレビューがbuffersのときと同じようになります。

エクスプローラー

ファイルの一覧をツリーでみたいならexplorerを使います。

picker.explorer()
explorerの例

ファイルの移動や削除もできるようです。詳しくは?を入力するか:help snacks-picker-sources-explorerでデフォルトのキーマップを見てみましょう。

grep

grepもあります。

picker.grep()

実装を見てみると、内部で叩かれるコマンドはripgrepのようです。

LSP系

LSP関係のピッカーも用意されています。よく使いそうなのは次の2つでしょうか。

picker.lsp_references()
picker.lsp_definitions()

Vim・Neovim関係

これもたくさんあるのでよく使いそうなやつを。

keymaps

picker.keymaps()

定義されているキーマップの一覧です。

キーマップの一覧

luaの関数で定義した場合、そのファイルがプレビューされて分かりやすいです。

highlights

picker.highlights()

highlightsはハイライトして表示されます。

highlightsの例

colorschemes

picker.colorschemes()

colorschemesはカーソル行のカラースキームがすぐに適用されるため、何度もピッカーを開かなくていいですね。

colorschemesの例

TODOコメント検索

todo-comments.nvimを使っている場合、TODOコメントを検索可能です。

picker.todo_comments()
todo_commentsの例

ピッカーのピッカー

ビルトインのピッカーが40以上あるため、全部にキーマップを設定するのは大変ですし覚えられません。
使用頻度が少ないものはキーマップをセットせず、ピッカーのピッカーから呼びましょう。

picker.pickers()

ピッカーの一覧が表示され、選んだピッカー実行されます。

ピッカーの一覧

直前のピッカーを呼び出す

直前に閉じたピッカーをもう一度表示するにはresumeを使います。

picker.resume()

Snacks.pickerのカスタマイズ例

ピッカーの紹介はこれぐらいにして、ここからは実際に筆者が使っているカスタマイズの例を解説します。

レイアウトを変えたい

レイアウトの種類を変える場合はlayout.presetに書きます。

require("snacks").setup({
picker = {
layout = {
cycle = true,
--- Use the default layout or vertical if the window is too narrow
preset = function()
return vim.o.columns >= 120 and "default" or "vertical"
end,
},
}
})

ピッカー毎に変更したいならpicker.〇〇の引数に渡してあげましょう。これはレイアウトに限らず、キーマップなど他の設定でも同様です。

次はfilesのレイアウトをverticalにする例です。

picker.files({
layout = { preset = "vertical", layout = { width = 0.9 } }
})

レイアウトの値を調整したいだけなら次のようにlayouts(複数形)の方に書きます。

require("snacks").setup({
picker = {
layouts = {
default = { layout = { width = 0.95 } },
},
}
})

上記は、デフォルトのレイアウトの幅を変更しています。

キーマップを変えたい

デフォルトのキーマップを変えるなら、picker.win.input.keysに書きます。

筆者の設定
require("snacks").setup({
picker = {
win = {
input = {
keys = {
-- select allを無効化
["<c-a>"] = false,
["<c-b>"] = false,
["<c-d>"] = { "preview_scroll_down", mode = { "i", "n" } },
["<c-f>"] = false,
["<c-u>"] = { "preview_scroll_up", mode = { "i", "n" } },
["<c-s>"] = false,
["<c-x>"] = { "edit_split", mode = { "i", "n" } },
},
},
},
}
})

無効化するならfalseを指定します。

パスの省略表記を変更したい

デフォルトではファイル名が40文字以上だと、パスの一部が省略されます。これを変更したい場合はtruncateを変えます。

require("snacks").setup({
picker = {
formatters = { file = { truncate = 200 } },
}
})

visualモードなら選択テキストを検索ワードにしたい

「visualモードでピッカーを起動したら選択テキストを検索ワードにする方法」を紹介します。

まずは選択テキストを取得する関数を定義。

local function get_text()
local visual = picker.util.visual()
return visual and visual.text or ""
end

この関数はpickerを立ち上げる前に呼びます

filesgit_filesならpatternに渡します。

local text = get_text()
picker.files({ pattern = text })
picker.git_files({ pattern = text })

grepならon_showで挿入します。

local text = get_text()
picker.grep({
on_show = function()
vim.api.nvim_put({ text }, "c", true, true)
end,
})

git_filesで未追跡ファイルも表示

デフォルトのpicker.git_files()は、ステージングに上げてないファイルは非表示です。

未追跡でも表示したい場合はuntrackedを指定します。

picker.git_files({ untracked = true })

dotfilesのpicker

dotfilesの一覧を出したいなら次のようにcwdを変更します。

picker.git_files({ cwd = '~/dotfiles' })

Gitを使っているか気にしないファイル検索

いちいち「カレントディレクトリがGit管理下なのか」を意識するのは面倒です。

そこで、カレントディレクトリがGitを使っていればgit_files、使っていないならfilesを呼んでくれるキーマップを定義します。

local function project_files()
local text = get_text()
local root = require("snacks.git").get_root()
if root == nil then
picker.files({ pattern = text })
return
end
picker.git_files({
untracked = true,
pattern = text,
})
end

先ほど定義したget_textをここでも使っています。

モノレポ対応

前述のproject_filesをモノレポでも使いやすくします。

次のようなリポジトリを例にします。

./awesome
├── .git
├── packages
│ ├── project-1 # カレントディレクトリ
│ └── project-2

「カレントディレクトリ(project-1)だけを検索対象にしたい」「リポジトリ全体を検索対象にしたい」という2つのケースが出てくると思います。

これに対応するのが次の定義です。

---@param use_git_root boolean git_rootを使うかどうか。モノレポの個別プロジェクトならfalse
local function project_files(use_git_root)
local text = get_text()
local root = require("snacks.git").get_root()
if root == nil then
picker.files({ pattern = text })
return
end
if use_git_root then
picker.git_files({
untracked = true,
pattern = text,
})
else
picker.git_files({
untracked = true,
pattern = text,
cwd = vim.uv.cwd(),
})
end
end

project_files(false)ならカレントディレクトリだけを検索対象にします。

筆者は次のようにlazy.nvimのkeysで定義して使ってます。

{
"<space>ff", function() project_files(false) end, mode = { "n", "x" },
desc = "モノレポでプロジェクト毎"
},
{
"<space>fF", function() project_files(true) end, mode = { "n", "x" },
desc = "モノレポでプロジェクト全体"
},

通常は<space>ffで検索し、モノレポ構成でリポジトリ全体を見たいときに<space>fFを使います。

オリジナルのピッカーを作る

ここからはオリジナルのピッカーの作り方を解説します。

同階層のファイルだけを検索

同階層のファイルだけを検索するピッカーを作りました。Angularのような同階層のファイル間を行き来するプロジェクトで重宝しています。

今まではpicker.〇〇を使っていましたが、今度はpicker()に直接引数を渡します

先に実装の全体を載せます。次の項目から小分けにして解説します。

picker({
finder = "proc",
cmd = "find",
args = { vim.fn.expand("%:h"), "-type", "f", "-not", "-name", vim.fn.expand("%:t") },
---@param item snacks.picker.finder.Item
transform = function(item)
item.file = item.text
end,
})

finderをprocにする

finderprocにすると外部コマンドを呼べます。cmdにコマンド名、引数はargsにテーブルとして渡します。

picker({
finder = "proc",
cmd = "find",
args = { vim.fn.expand("%:h"), "-type", "f", "-not", "-name", vim.fn.expand("%:t") },
-- ...
})

-not -name ……を引数に渡すことで、現在カーソルがあるファイルは一覧から省けます。

transformとは

コマンドの実行結果をピッカーで扱えるように変換する処理transformに書きます。

transform = function(item)
item.file = item.text
end,

findの出力結果はただのファイル名のみであるため、item.file = item.textを指定します。

実行されるコマンド例
find src/pages/foo/foo-buz -maxdepth 1 -type f -not -name 'foo-buz.html'
出力結果の例
src/pages/foo/foo-buz/foo-buz.scss
src/pages/foo/foo-buz/foo-buz.ts
src/pages/foo/foo-buz/foo-buz.module.ts

ファイル以外のピッカーを作る

ファイル以外を扱うピッカーは、findersnacks.picker.Item[]型を返す関数を書きます。

ここでは選んだアイテムを通知するだけの簡単なピッカーを例に挙げます。

カスタマイズしたピッカーの例

先に実装の全体を載せます。次の項目から小分けにして解説します。

picker({
finder = function()
local my_list = {
{ quote = "選ばれるのは私よ!", thanks = "ありがとうなのだわ!" },
{ quote = "うるさいんですけどぉ〜", thanks = "別にありがとうとか思ってないんだからね!" },
{ quote = "ちっす", thanks = "どうも" },
}
---@type snacks.picker.Item[]
local items = {}
for i, person in ipairs(my_list) do
---@type snacks.picker.Item
local item = {
idx = i,
score = 0,
text = person.quote,
thanks = person.thanks,
}
table.insert(items, item)
end
return items
end,
---@type snacks.picker.Action.spec
confirm = function(the_picker, item)
the_picker:close()
vim.notify(item.thanks)
end,
format = "text",
preview = "none",
layout = { preset = "vscode" },
})

finderでリストを返す

finderの中身を見てみましょう。

finderプロパティの中身
local my_list = {
{ quote = "選ばれるのは私よ!", thanks = "ありがとうなのだわ!" },
{ quote = "うるさいんですけどぉ〜", thanks = "別にありがとうとか思ってないんだからね!", },
{ quote = "ちっす", thanks = "どうも" },
}
---@type snacks.picker.Item[]
local items = {}
for i, person in ipairs(my_list) do
---@type snacks.picker.Item
local item = {
idx = i,
score = 0,
text = person.quote,
thanks = person.thanks,
}
table.insert(items, item)
end
return items

ピッカーに渡すItemはidxscoretextが必須です。thanksのように独自のキーも追加できます。

confirm

confirmには、アイテムを選択したときに実行する処理を書きます。

---@type snacks.picker.Action.spec
confirm = function(the_picker, item)
the_picker:close()
vim.notify(item.thanks)
end,

残りのキー

残りのキーは見た目に関するものです。

{
-- ...
format = "text",
preview = "none",
layout = { preset = "vscode" },
}

text-case.nvimをSnacksに対応させる

text-case.nvimはテキストをPascalCasesnake_caseに変更してくれるプラグインです。
これをpickerで選んで実行できるようにしました。

text-caseの例

長いので折りたたみの中に書きました。

設定例(クリックで開きます)
require("textcase").setup({})
local plugin = require("textcase.plugin.plugin")
local picker = require("snacks.picker")
local constants = require("textcase.shared.constants")
local api = require("textcase").api
local api_list = {
api.to_upper_case,
api.to_lower_case,
api.to_snake_case,
api.to_dash_case,
api.to_title_dash_case,
api.to_constant_case,
api.to_dot_case,
api.to_comma_case,
api.to_phrase_case,
api.to_camel_case,
api.to_pascal_case,
api.to_title_case,
api.to_path_case,
}
---@param mode string
---@return snacks.picker.Item[]
local function create_items(mode)
---@type snacks.picker.Item[]
local items = {}
---@type { prefix: string, type: string }[]
local conversion_dict = {}
if mode ~= "n" then
table.insert(conversion_dict, { prefix = "Convert to ", type = constants.change_type.VISUAL })
else
table.insert(conversion_dict, { prefix = "Convert to ", type = constants.change_type.CURRENT_WORD })
table.insert(conversion_dict, { prefix = "Lsp rename ", type = constants.change_type.LSP_RENAME })
end
local i = 1
for _, conversion in pairs(conversion_dict) do
for _, method in pairs(api_list) do
---@type snacks.picker.Item
local item = {
idx = i,
score = 0,
text = conversion.prefix .. method.desc,
method_name = method.method_name,
type = conversion.type,
}
table.insert(items, item)
i = i + 1
end
end
return items
end
---@type snacks.picker.Action.spec
local function invoke_replacement(the_picker, item)
the_picker:close()
if item.type == constants.change_type.CURRENT_WORD then
plugin.current_word(item.method_name)
elseif item.type == constants.change_type.LSP_RENAME then
plugin.lsp_rename(item.method_name)
elseif item.type == constants.change_type.VISUAL then
plugin.visual(item.method_name)
end
end
vim.keymap.set({ "n", "v" }, "gt", function()
local mode = vim.api.nvim_get_mode().mode
picker({
finder = function()
return create_items(mode)
end,
confirm = invoke_replacement,
format = "text",
preview = "none",
layout = { preset = "vscode" },
})
end, { desc = "Picker: textcase" })
end,

以上、snacks.nvimのpickerの紹介でした。めちゃくちゃ便利!