Neovimに検索機能を3つ入れたのに、Space sGしか使っていなかった

VS Codeだけでコードを読んでいた頃、特にやりづらかったのが検索でした。結果を待ち、対象ファイルを開き、定義や呼び出し先を確認して、また検索へ戻る。この往復に引っかかりを感じていました。

Neovimへ移ってからは<Space>sGで検索しています。rgを使っているつもりはなかったのですが、いつものキーの裏でripgrepが動いていました。私の環境ではレスポンスに不満はありません。しかし設定を調べ直すと、Snacks Picker、Telescope Live Grep Args、Grug-farの3つが入っていました。それでも、実際に使っていたのは<Space>sGだけです。

一つの検索画面ですべてを済ませるのではなく、普段の検索、複数のripgrep引数を組み立てる検索、置換で入口を分けることにしました。まず使うのはSnacksの<Space>sg、引用・除外・検索先をまとめて指定したい時はTelescopeの<Space>sG、複数ファイルを置換する時はGrug-farの<Space>srです。小文字のsgはSpace、s、g。大文字のsGはSpace、s、Shift+gです。

設定を増やす前に、すでにあるキーを確認した

現在の設定には、次の検索キーがありました。<leader>はSpaceキーです。

キー 役割 実体
<leader>ff ファイル名から探す Snacks Picker
<leader>sg プロジェクト全体を文字列検索 Snacks Picker + ripgrep
<leader>sG 引数付きで文字列検索 Telescope Live Grep Args + ripgrep
<leader>sw カーソル下の単語・選択範囲を検索 Snacks Picker + ripgrep
<leader>ss 現在のファイル内のシンボル(対応するLSP接続中) LSP + Snacks Picker
<leader>sS ワークスペース全体のシンボル(対応するLSP接続中) LSP + Snacks Picker
<leader>sr 検索結果を見ながら置換 Grug-far + ripgrep

SnacksのPicker公式ドキュメントには、ファイル、grep、バッファ、LSPシンボルなどの検索元が並んでいます。私のLazyVim設定では標準の検索入口がSnacksになっていました。

一方、<leader>sGには自分でTelescope Live Grep Argsを追加しています。標準の検索と自分の追加設定が似た名前で並び、役割を決めないまま片方だけ使っていました。

キー表は私のLazyVim設定です。<leader>sGと検索中のAltキーは独自設定。標準の<leader>sGは作業ディレクトリのgrepで、Telescopeが開くとは限りません。

grepと置換に必要なrgは:!rg --versionで確認できます。0件なら検索先と絞り込みも見ます。通常はignore対象や隠しファイルを外すので、0件でも文字列がないとは限りません。

以下の画面は、UI検証用プロジェクトenterprise-ui-labのTypeScriptコードをWSL2上のNeovimで開いたものです。プロジェクトのルートから開いて検索の使い方を撮影し、置換だけは一時コピーで試しています。

ファイル名を覚えているなら、ノーマルモードでSpace、f、fを押し、検索欄へOrdersTableと入れます。Ctrl+j / Ctrl+kで候補を選び、Enterで開く。やめる時はEscです。Snacksは入力モードからEscで候補選択へ移り、もう一度Escで閉じます。

Space ffでOrdersTableを入力し、3つのtsxファイルを左の候補と右のプレビューで確認する
ファイル名の検索。左で選んだOrdersTable.tsxの内容が右に出ます。

普段はSnacksの<leader>sgを使う

関数名、画面の文言、エラーメッセージなどを探す時は<leader>sgを最初の入口にします。プロジェクトのルートからripgrepで検索し、候補とプレビューを同じ画面で確認できます。

例えば、金額の表示処理を調べるならformatAmountと入れます。ファイル名ではなく、その文字列を含む行が候補になります。まだ正確な関数名が分からない時は、画面の文言から入口を探す使い方もできます。

今の設定では、Snacksの検索画面にAlt+tとAlt+gも追加しています。LazyVimではSnacksがすでに設定されているため、require("snacks").setup()をもう一度呼ぶのではなく、lua/plugins/に置くfolke/snacks.nvimのspecへoptsを追加します。

Altキーで検索語の後ろへ条件を足す設定です。検索画面を受け取る処理はactionsへ置き、入力欄のキーから名前で呼び出します。ライブ検索欄の更新にはinput:set(nil, 検索文字列)を使い、検索語と引数の間に--を入れます。

return {
  {
    "folke/snacks.nvim",
    opts = {
      picker = {
        sources = {
          grep = {
            actions = {
              insert_type = function(picker)
                local text = picker.input:get()
                local suffix = text:find(" -- ", 1, true) and " -t " or " -- -t "
                picker.input:set(nil, text .. suffix)
              end,
              insert_glob = function(picker)
                local text = picker.input:get()
                local suffix = text:find(" -- ", 1, true) and " -g " or " -- -g "
                picker.input:set(nil, text .. suffix)
              end,
            },
            win = {
              input = {
                keys = {
                  ["<M-t>"] = { "insert_type", mode = "i", desc = "Insert -t args" },
                  ["<M-g>"] = { "insert_glob", mode = "i", desc = "Insert -g args" },
                },
              },
            },
          },
        },
      },
    },
  },
}

検索欄にformatAmountを入れた後、Alt+tを押し、続けてtsと入力するとformatAmount -- -t tsになります。tsはripgrepのファイルタイプ名で、.tsと.tsxの両方が対象です。名前は:!rg --type-listで確認できます。

Snacksの検索欄にformatAmount -- -t tsを入力し、TypeScriptの一致行とコードを表示する
Space sg → formatAmount → Alt+t → ts。右は選んだ一致行の前後です。

さらにAlt+gを押し、**/v3/**と入力すると、条件はformatAmount -- -t ts -g **/v3/**になります。**はディレクトリの階層をまたぐ指定で、ここではパスにv3があるファイルだけを探します。

SnacksでformatAmount -- -t ts -g **/v3/**を入力し、v3フォルダー内の一致行へ絞り込む
Alt+gで条件を追加。検索語を変えずに、v3のコードだけへ絞れます。

普段の検索はSnacksのまま、必要になった時だけ対象を絞ります。Snacksの--は検索語と引数を分ける区切りで、次のTelescopeとは入力が違います。

引数を組み立てたい時だけ<leader>sGへ切り替える

Telescopeでは、ノーマルモードでSpace、s、Shift+gを押し、開いた検索欄へ文字を入力します。rgコマンドをターミナルへ打つ操作ではありません。検索語の後ろに、ファイルタイプや検索先の指定も書ける画面です。公式READMEの入力例を、同じformatAmountで試しました。

Space sGをTelescopeへ割り当てる設定

lua/plugins/のspecで、既存のSnacks定義へkeysを統合します。標準の同じキーもSnacks側にあるため、Telescope側と二重にキーを登録しない形です。TelescopeとLive Grep Argsも必要です。すでに同じプラグインのspecがあれば、追加ファイルへ重複させず既存定義へ統合します。

return {
  {
    "folke/snacks.nvim",
    keys = {
      {
        "<leader>sG",
        function()
          require("telescope").extensions.live_grep_args.live_grep_args()
        end,
        desc = "Live Grep (Telescope Args)",
      },
    },
  },
  {
    "nvim-telescope/telescope.nvim",
    dependencies = { "nvim-telescope/telescope-live-grep-args.nvim" },
    opts = function(_, opts)
      local actions = require("telescope-live-grep-args.actions")
      opts.extensions = opts.extensions or {}
      opts.extensions.live_grep_args = {
        auto_quoting = true,
        mappings = {
          i = {
            ["<C-k>"] = actions.quote_prompt(),
            ["<M-t>"] = actions.quote_prompt({ postfix = " -t " }),
            ["<M-i>"] = actions.quote_prompt({ postfix = " --iglob " }),
          },
        },
      }
    end,
    config = function(_, opts)
      require("telescope").setup(opts)
      require("telescope").load_extension("live_grep_args")
    end,
  },
}

現在の設定では次のキーを使えます。

検索画面内のキー 入力するもの
Ctrl+k(<C-k>) 現在の検索語を引用符で囲む
Alt+t(<M-t>) 検索語を引用符で囲み、-tを追加する
Alt+i(<M-i>) 検索語を引用符で囲み、--iglobを追加する

まずformatAmountと入れ、Alt+tを押してからtsと入力します。検索欄は次の形になります。

"formatAmount" -t ts

最初の"formatAmount"が検索語、その後の-t tsが「TypeScriptだけ」という条件です。引用符で囲むのは、検索語と条件を分けて渡すため。検索欄に引用符そのものも表示されます。

Telescope Live Grep Argsの検索欄へ引用符付きのformatAmountと-t tsを入力した検索結果
Space sG → formatAmount → Alt+t → ts。入力欄にSnacksの区切り — は入れません。

v3だけを探す時は、いったん検索欄を空に戻し、formatAmount → Alt+i → **/v3/**と入れます。

"formatAmount" --iglob **/v3/**

--iglobはファイルのパスをパターンで絞る指定で、パスの大文字・小文字を区別しません。この例ではv3ディレクトリ内の一致行が残ります。

TelescopeでformatAmountを--iglob **/v3/**によりv3フォルダーへ絞り込む
Alt+iでパスの条件を追加。SnacksのAlt+gと、同じ対象へ絞る例です。

フォルダーが分かっているなら、もっと直接指定できます。formatAmount → Ctrl+k → src/libと入力すると、検索欄は次の形です。

"formatAmount" src/lib
Telescopeで引用符付きformatAmountの後ろへsrc/libを入力し、共通関数のフォルダーだけを検索する
Ctrl+kで検索語を引用し、検索先のsrc/libを追加。金額を表示する共通関数へ絞れます。

追加条件をそのまま入力する例も、同じ検索欄です。

検索欄に入れる文字 探す対象
"formatAmount" -t ts --iglob **/v3/** TypeScriptかつv3内
"formatAmount" tests testsフォルダー内
"formatAmount" --no-ignore src/lib src/lib内。ignore指定で外れたファイルも対象にする

--no-ignoreは.gitignoreなどによる除外を無効にします。node_modulesなどまで広げないよう、検索先を付けて使います。隠しファイルは別の条件なので、それも含める時は--hiddenを追加します。

引用符を忘れてformatAmount -t tsと入れると、私の設定では全体が検索語になり、絞り込みの指定として渡りません。auto_quotingが有効なためです。最初の語だけをCtrl+kで引用してから条件を足すか、上の入力例を引用符ごと入れます。条件を含む欄へAltキーを繰り返すより、引用した語の後ろへ必要な条件を手で足すほうが分かりやすいです。

候補は下矢印 / 上矢印で選び、Enterで開きます。Telescopeの入力中は、私のCtrl+kが引用用になっています。Escでノーマルモードへ移ればj / kで選び、そこからもう一度Escで閉じられます。

毎回ここから始めると、検索語よりオプションを考える時間が増えます。<leader>sGは、普通のgrepでは対象が広すぎた時の二手目です。

目の前の単語なら<leader>sw、コードの関係ならLSPを使う

カーソル下の関数名や型名を、設定ファイルやドキュメントも含めて文字列検索したい時は<leader>swを使います。選択範囲をそのまま検索することもできます。

OrdersTable.tsxでformatAmountへカーソルを置き、Space、s、wを押すと、その単語の一致行が出た状態でSnacksが開きます。手入力を省く入口です。

Space swで左上にカーソル下のformatAmountを表示し、その文字列を含む行を一覧表示する
swはカーソル下の単語から検索。grepなので、LSPがなくても使えます。

一方、定義や参照の関係が欲しい時はgrepではなくLSPです。ただし、次のキーは開いているファイルへLSPが接続し、その操作を言語サーバーが提供している時に使えます。言語やサーバーによって使える範囲は同じではありません。

キー 確認するもの 必要なLSP機能
gd 定義 定義ジャンプ
gr 参照元 参照検索
gI interfaceなどの実装 実装検索
gy 型定義 型定義検索
gai 呼び出し元 Call HierarchyのIncoming Calls
gao 呼び出し先 Call HierarchyのOutgoing Calls

gyはg、yと順に押すキーです。私のWSL設定でOrdersTable.tsxを開き、TypeScript用のvtslsが接続すると、gyを含むLSPキーが現れました。テキストだけのバッファでは現れません。gを押した時のwhich-keyも、開いているファイルの状態によって変わります。

LSPが接続したコードでgを押し、which-keyにyのGoto Type DefinitionやdのGoto Definitionを表示する
LSP接続中のgメニュー。yが型定義、dが定義です。LSPのない画面ではこの一覧にはなりません。

見つからない時は、まず調べるコードのファイルを開き、:set filetype?で言語を確認し、<leader>clでそのバッファへのLSP接続を見ます。接続がなければgyを探すより、言語サーバーの起動を先に確認します。接続していても、型定義の結果があるかはサーバーとカーソル位置によります。gaiとgaoはCall Hierarchyに対応する時だけ、そのバッファにキーが作られます。

例えばOrdersTable.tsxの変数orderでgyを押すと、今回の画面ではtypes.tsのOrder型へ移動しました。formatAmountの上なら、gdで関数の定義、grで参照元を調べます。grは文字列の一致とは違い、接続したLSPが返すコード上の参照です。

formatAmountでgrを押し、LSPの参照一覧と選択したコードを表示する
grで参照元を開いた画面。候補を選びEnterで移動し、Ctrl+oで元の位置へ戻ります。

ファイルの中で関数名から移動したい時は<leader>ss、プロジェクト全体の名前から探す時は<leader>sSを使います。それぞれドキュメントシンボルとワークスペースシンボルに対応するLSPが必要です。

ss・sSの画面と使い方

OrdersTable.tsxを開いてSpace、s、s。現在のファイルにある関数・型・変数などが並びます。OrdersTableと入力して絞り、候補を選んでEnterで移動します。

Space ssでOrdersTableを入力し、OrdersTable.tsx内の関数とPropsの型を絞り込む
ssは現在のファイル内。文字列が出るすべての行を探すgrepとは異なります。

Space、s、Shift+sでプロジェクト全体のシンボル検索を開き、formatAmountと入力します。今回のvtslsではsrc/lib/format.tsの関数が候補になりました。

Space sSでformatAmountを入力し、src/lib/format.tsの関数をワークスペースシンボルとして表示する
sSはワークスペース内の名前から探す入口。検索できる範囲はLSPに依存します。

私がよくやるのは、検索で対象ファイルへ飛び、gdで定義を開き、grで使われ方を見る流れです。深く移動した後はCtrl+oで戻ります。

grepは文字が一致する場所を探します。LSPは言語としての定義や参照を探します。同じ関数名を探す場合でも、設定やテストデータまで見たいならgrep、コード上の関係だけを追いたいならLSPです。

置換まで進む時は<leader>srで検索をやり直す

検索して読むだけならSnacksかTelescopeで終わります。複数ファイルを変更したい時は、結果を見つけた後に<leader>srでGrug-farを開きます。

私の設定では、現在開いているファイルに拡張子がある場合、同じ拡張子を対象ファイルの初期値にしています。

次は、既存の<leader>srのキー定義にあるコールバック関数へ組み込む部分の抜粋です。

local ext = vim.fn.expand("%:e")

require("grug-far").open({
  transient = true,
  prefills = {
    filesFilter = ext ~= "" and "*." .. ext or nil,
  },
})

Grug-farの公式READMEでは、ripgrepまたはast-grepによる検索・置換、差分表示、Quickfixへの出力などが案内されています。

例えばOrdersTable.tsxを開いている時に起動すれば、まず*.tsxが対象になります。検索結果と置換後の差分を確認してから反映できるため、変更範囲を把握しやすくなります。

このキーは前の検索語を引き継ぎません。次は一時コピーでformatAmountをformatCurrencyへ置き換える説明用の入力例です。関数名を実際に変えるなら、定義・import・参照の整合も確認する必要があり、この範囲だけでリネームが完成するわけではありません。

  1. Space、s、rで開き、SearchにformatAmount、ReplaceにformatCurrencyを入れる。
  2. Files Filterの*.tsxを確認し、Pathsにsrc/features/orders/v3を入れて範囲を絞る。各入力欄は矢印キーや、ノーマルモードのj / kで移動し、iで入力する。
  3. ファイル名と差分を見る。ここはプレビューで、まだファイルは変わらない。
  4. ノーマルモードでReplaceを実行する。既定の<localleader>rはSpaceの<leader>とは別。画面上部のキー表示も見る。
Grug-farでformatAmountからformatCurrencyへの置換、*.tsxとv3フォルダーの範囲、変更前後の差分を表示する
Search・Replace・Files Filter・Pathsを別々に入力。下の差分はプレビューで、Replaceを実行するまではファイルを保存しません。

Replaceは対象ファイルへ保存します。直前の版を残し、反映後はgit diffなどで確認します。戻すならその版を使い、既存の変更まで消さないようにします。同じ語を含む別の処理も確認が必要です。

ファイル名を覚えている、内容から探す、カーソル下の単語を探す、検索条件を細かく付ける、複数ファイルを置換する
目的に合わせて選ぶ検索の入口。コードの定義・参照をたどるgd・grは、対応するLSPが必要です。

私がまず試す検索の順番

新しいキーを全部覚えるのはやめました。次の順番だけをチートシートへ残しています。

  1. ファイル名を覚えているなら<leader>ff
  2. 内容から探すなら<leader>sg
  3. 目の前の単語を広げるなら<leader>sw
  4. 条件を細かく付けるなら<leader>sG
  5. 置換まで行うなら<leader>sr
  6. コードの関係はgd、gr、gI
  7. 迷ったら<leader>?でチートシートを開く

これまでは<leader>sGしか使っていませんでした。まずはsg、複数の条件をまとめて渡すならsG、変更するならsr。この三段階で使います。

ターミナルからプロジェクトへ入り、Neovimで検索するまでの全体像はAIにコードを書かせた後、rg・fzf・Yazi・Neovimで読む、LSPの実行経路はNeovimのLSPが動かない原因はプラグインではなくPATHだったへ書いています。

確認した公式情報

確認日: 2026年10月14日

コメント

タイトルとURLをコピーしました