コードを書いているとプロジェクト内のコードを参考にしたかったり、一括で置換したかったりします。そんな時emacs内でgrepを使うのですが、プロジェクトがそれなりに大きくなると非常に遅くなります。gitのプロジェクトであれば、gitであればgit grepを使えば高速に検索できるのでそれを使えば良いと思い、やってみました。
letでgrep-find-commandを書き換える
http://blog.kentarok.org/entry/20100219/1266577631 にletでgrep-find-commandを書き換えた上で、grep-find関数を呼び出すと出来るというふうに書いてあったので、やってみたのですが、なぜかうまくいきませんでした。うーん。
grep-apply-settingを使う
emacsのgrep-commandを変更する - すぎゃーんメモ と同じように、grep-find-commandをgrep-apply-settingコマンドで書き換えるという手もあります。例えば以下のように設定しておいて、M-x grep-findとかするとgit grep出来るようになります。
(grep-apply-setting 'grep-find-command "PAGER='' git grep -I -n -i -e ")
ただ、この方法の場合、globalな変数を置き換えてしまうため、普通にgrepしたいときにできなくなってしまいます。gitのプロジェクト触ってる時にそうじゃないところを検索したいときにできなくなります。また、ディレクトリを気軽に指定できないという問題もありました。
grep関数を使って自由に呼び出してあげる
で、結局自分で定義してみることになりました。grep関数に適切な出力を行うコマンドを与えてあげると、出力結果をgrep-modeで出力してくれるということが分かったので以下のようにしてみました。
まずgitのrootとかを取得する関数。
(defun chomp (str) (replace-regexp-in-string "[\n\r]+$" "" str)) ;;; git (defun git-project-p () (string= (chomp (shell-command-to-string "git rev-parse --is-inside-work-tree")) "true")) (defun git-root-directory () (cond ((git-project-p) (chomp (shell-command-to-string "git rev-parse --show-toplevel"))) (t "")))
git grepの定義。
(defun git-grep (grep-dir command-args) (interactive (let ((root (concat (git-root-directory) "/"))) (list (read-file-name "Directory for git grep: " root root t) (read-shell-command "Run git-grep (like this): " (format "PAGER='' git grep -I -n -i -e %s" "") 'git-grep-history)))) (let ((grep-use-null-device nil) (command (format (concat "cd %s && " "%s") grep-dir command-args))) (grep command)))
これを定義しておいて、M-x git-grepを呼ぶと以下のように最初にgitのプロジェクトルートのディレクトリが表示されて、そこから検索したいディレクトリを指定します。
そのあとgit grepで検索したい文字を入力します。
そうするとgit grepで検索された結果がgrep-modeで表示されます。
これでgit grepをemacs上で行えるようになりました。さらにwgrep.elとかを使うと一括置換とかできるので便利ですね。
今回の技術的tips
read-file-nameを使ってファイル名を読み込む
read-file-nameを使うとファイル名を置換しながら読み込むことが出来ます。使い方は以下のとおり。
(read-file-name prompt &optional directory default existing initial)
promptを指定してあげて、最初に出ているdirectoryを指定して、何も入力がなかった時のdefaultを指定してあげる感じですね。existingをtにしていると、ファイルが存在しているかどうかもチェックします。
今回の場合はgit rootを渡してあげて、存在確認をしています。
(read-file-name "Directory for git grep: " root root t)