✍️

Emacs のディレクトリ内検索・置換

2023/12/24に公開

背景

  • 時代の潮流にのり、少し前から vscode に helm から vertico + consult に移行していた
  • helm-ag の "Save results in buffer" (検索結果をバッファに出力する) と "Edit search results" (検索結果を 1つのバッファで一括編集する) というアクションがとても便利だったのだが、上記だけの構成では同等の機能がなかった。のでやり方を調べた。
    • 検索結果を行って戻ってしたい場合、検索結果をバッファに保持しておいたが手間が少ない
    • 検索結果について一個一個確認しながら置換するような場面では、再検索(リフレッシュ)ができると便利
    • 確認不要で一括置換できるような場面では、検索結果のバッファ上で一括編集したい

目的

  1. 関連機能や関連パッケージについて整理する
  2. 上記の2機能を使えるようにする

検索関連

grep

  • emacs のビルトイン機能
  • M-x grepgrep コマンド(細かいパラメータは省略)を組み立て、結果を *grep* バッファに出力する
  • grep なので再帰的なディレクトリの検索はできない

grep バッファ

  • *grep* バッファでは以下の操作が行える

    • n 次のマッチ行にすすむ
    • p 前のマッチにもどる
    • g 再検索(リフレッシュ)
    • C-u g コマンドを編集して再検索

grep-find (find-grep)

  • emacs のビルトイン機能
  • M-x grep-find でミニバッファ上で find . -exec grep コマンド(細かいパラメータは省略)を組み立て、結果を *grep* バッファに出力する
  • 再帰的な検索ができる

rgrep

  • recursive grep (?)
  • emacs のビルトイン機能
  • M-x rgrep で対象の単語・拡張子・ディレクトリを対話的に入力する
    • ( grep-find の人間にやさしい版

lgrep

  • limited grep (?)
  • emacs のビルトイン機能
  • M-x lgrep で単語・拡張子・ディレクトリを対話的に入力する
    • ( grep の人間にやさしい版

ripgrep

  • ripgrep は ag (The Silver Searcher)と同じような高速で便利な検索ツール

    • .gitignore / .ignore を参照してファイル・ディレクトリを除外できる
  • ripgrep の emacs バインディングは以下

    • ripgrep.el

    • rg.el

      • wgrep (後述) が依存パッケージとして指定されており、 *rg* バッファ上で e をタイプすると wgrep モードに入る

検索結果の編集・一括置換

occur

  • emacs のビルトイン機能
  • M-x occur カレントバッファに対して入力された単語を検索し、結果を *Occur* バッファに表示する
  • M-x multi-occur 対象のバッファを選択し、 空 [RET] で選択終了。その後は occur と同様
  • *Occur* バッファで e をタイプすると Occur Edit モードになり、一括編集が可能になる。 C-c C-c で編集を終了する。

wgrep

  • wgrep.el

  • *grep* バッファを ( occur のように ) 編集可能にする

  • *grep* バッファで C-c C-p をタイプすると wgrep モードに入る

    • C-c C-c or C-x C-s で変更を適用して wgrep モードを終了
    • C-c C-k 変更を破棄して wgrep モードを終了

dired-do-query-replace-regexp

  • emacs のビルトイン機能
  • Dired モードでは Q が dired-do-query-replace-regexp にバインドされている
  • Dired でマークした(もしくはカーソル上の)ファイルまたはディレクトリに対して再帰的に query-replace-regexp を行う
  • 置換実行後はバッファの更新は保存されていないのでそれぞれ保存する ( ibuffer を使うと編集中のバッファを一括保存できる)
  • 内部で xref を利用している

補完UI関連

vertico

  • vertico.el
  • vertico.el - VERTical Interactive COmpletion とあるように、保管候補を縦にインタラクティブに表示するUI

consult

  • consult.el

  • Emacs の completing-read (ミニバッファで入力を受け取って補完候補を提供する) を使った便利コマンド集 [1]

  • 例えば以下のコマンドがあり、それぞれの補完候補を提供する

    • consult-grep grep の結果
    • consult-ripgrep ripgrep の結果
    • consult-buffer 現在開いているバッファの一覧や recentf のファイル
    • consult-recentf-file recentf
    • consult-yank-from-killring killring の履歴
  • consult 単体では標準の *Completion* バッファを表示に用いる

    • (この場合、インクリメンタルな補完でなく TAB を押して補完候補を取得する

embark

  • Embark

  • embark-act : コンテキストに応じたアクション(サブコマンド)を提供する

    • 右クリックメニューのようなイメージ
  • 補完候補を対象としたアクションもある

    • なにかしらキーを embark-act にバインドし、minibuffer で補完候補の表示中に実行する
  • embark-select : ( *Embark Actions* 表示中に SPC ) 候補を選択(マーク)して embark-act-all で一括操作を行う

  • embark-collect : ( *Embark Actions* 表示中に S ) 補完候補をリストにして *Embark Collect:* バッファーを生成する。

  • embark-export : ( *Embark Actions* 表示中に E ) 補完候補の種類に応じたメジャーモードバッファを生成する。

    • ファイルなら Dired バッファ、バッファーなら iBuffer など

    • embark-consult を使うと以下の exporter が追加される

      • consult-line, consult-outline, consult-mark > occur-mode バッファ
      • consult-grep, consult-git-grep, consult-ripgrep の検索結果 > grep-mode バッファ

なにを使うか

とりあえず基本的には .gitignore で除外しているファイルは検索対象外にするだろうということで ripgrep を使う

  • 元々は helm-ag を使っていたので consult-ag も検討したが、consult-ripgrep は consult に元から入っているので

となると選択肢は二つ

  1. consult-ripgrep した検索結果を embark (consult-embark) で export (+ wgrep)
  2. consult-ripgrep と rg.el を併用(リストが欲しいときや一括編集したい場合は rg.el + wgrep)

で、両方試してみた結果、1 だと export 結果で g を押したときにリフレッシュでなくリストを閉じて minibuffer にもどる挙動をしていたので、2 でやってみる。 [2]

あとわりと副次的ではあるが、 rg.el で wgrep したあと g でリフレッシュすると編集中のファイルを保存するか聞かれるので保存も楽。

感想

分からんことだらけだったが割と整理できた。(予備知識的なものも書いているので記事としては入ってき辛いかもしれない)

昔のメモを掘り返していたら grep-find を使っていた痕跡があったので、多分 helm-ag を長く使っている間に grep-find の存在を忘れたものと思われる。(ありがとう helm 、ありがとう helm-ag )

参考

脚注
  1. という説明がわかりやすいのではないかと思っている ↩︎

  2. 設定で変えられるかもしれないが、検索 > export のひと手間かけるよりはコマンドを使い分ける方がシンプルというのもあり ↩︎

Discussion