Pages

2013年10月15日火曜日

Emacsからagで検索してhelmで表示するhelm-ag-rの紹介

MELPAにアップロードだけして全然紹介してなかった ag(silver-searcher) で
検索して表示helmでおこなうhelm-ag-rについて紹介したいと思います。
でもその前に他の ag or helm関係で似ているパッケージの紹介をします。

helm-ag : 僕がhelm-ag-rを作るにあたって参考にさせていただきました。
実行すると"ag –nocolor –nogroup -S -C 2 -U "のような行が
minibuffer にでて検索ワードを入力します。結果をhelmでしぼりこみ検索できます。

ag.el : これはM-x grepコマンドがありますがそれの ag 版という印象です。
ただこれはgrepコマンドでgrep-editやgrep-a-lotなどを使っていると、
そちらと互換がとれてないので僕は使いませんでした。
(これの解決方は下の方に書いてます。)

なぜ車輪の再発明をしたのか?

helm-ag に不満があったわけではなく、自分がつくった elisp で
google-contacts,shell-history, gitのコミット履歴をhelmで表示する elisp
があったのですがそれらが結構似たような構造だったので似てる部分をあつめて
パッケージにしたかったというのがありました。これは Shell script で
grep 検索した結果を helm で表示するものでした。helm-ag-rはgrepだったもの
を ag に置きかえて作りました。
(でもgoogle-contactsは検索するたび問い合わせるのでちょっと遅い。。
まぁmewだと一度メール送ったらアドレスリストに乗るので必要ないですよね?)

特徴

  • helm-ag.el だと最初に検索ワードを入れて ag の結果を表示していましたが、
    helm-ag-rはhelmの絞込み minibuffer 入力時に検索します。(なんていうん
    ですかね?) なのでtypoして間違えた結果がでても、C-kしてすぐに入力しな
    おせます。そのかわり文字入力のたびにagを起動するのでちょっとアレな感
    じです。 (helm-input-idle-delay変数を指定することで対処可能ですが)
    -> 変数で指定するようにしました。詳しくは設定の項をみてください。
  • カスタマイズがshell-scriptを知っていればば簡単にできます。詳細はカス
    タマイズの項を参照してください(いいすぎかも。。)

機能

デフォルトで以下の検索コマンドがあります。

helm-ag-r-current-file現在のファイルから検索
helm-ag-r-from-git-repogit管理のリポジトリから検索
helm-ag-r-shell-history$HISTFILEで指定されているファイルからshell-history を表示する。
helm-ag-r-git-logsgit logから検索
helm-ag-r-google-contacts-listgoogle-contactsからメールアドレスを検索、挿入※

※google-clをインストールしている必要があります。まだ個人で複数メールア
ドレスをもっている場合には対応していません。

インストール

MELPAにアップロードしたので、list-packagesコマンドでhelm-ag-rを探すか
package-installコマンドでインストールできます。
GitHubからインストールしたい場合は ここから できます。

設定

検索時は文字を入力するたびに ag で検索するので、候補が多すぎると感じる
場合は、helm-ag-r-input-idle-delayやhelm-ag-r-requires-pattern変数など
を調整すると良いと思います。helm-ag-r-option-listはagのオプションを指
定します。C-oで次のオプションに移動します。 C-rで逆にオプションを移動
します。

(require 'helm-ag-r)
(setq
helm-ag-r-google-contacts-user "your gmail address"
helm-ag-r-google-contacts-lang "ja_JP.UTF-8"
helm-ag-r-option-list '("-S -U --hidden"
"-S -U -g")
;; 不安定な場合以下の項目を調整すれば
;; よくなるかもしれません
helm-ag-r-requires-pattern 3 ; 文字数以上入力してから検索
helm-ag-r-input-idle-delay 0.5 ; 検索をdelay後からおこなう
helm-ag-r-use-no-highlight t ; ハイライト無効化
helm-ag-r-candidate-limit 1000) ; 候補の上限を設定

使い方

普通のhelm系コマンドと同じように使えるのですが、
helm-ag-r-option-listで指定したオプションをC-oで切替えることができます。

カスタマイズ

shell-scriptを知っていれば簡単にカスタマイズできると書きましたが、実際
にはactionを定義するのに elisp の知識も多少必要になります。
下のはshell-historyをみるための関数定義です。
helm-ag-r-pype 関数の第一引数部分がshell scriptでその結果がhelmの候補として
表示されます。第二引数部分はhelmのソースの定義です。ここで指定したもの
は元のhelm-ag-rのソースを上書きします。下の例ではactionを上書きしていま
す。(helmではsource(s)という用語で何を検索してどのように表示するかなど
の情報をhelm-xxx-c-sourceというリストを指定する慣習?があります。)

(defun helm-ag-r-shell-history ()
"Search shell history(I don't make sure without zsh)"
(interactive)
(helm-ag-r-pype
"tac ~/.zsh_history | sed 's/^: [0-9]*:[0-9];//'"
'((action . (lambda (line)
(case major-mode
(term-mode (term-send-raw-string line))
(t (insert line))))))))

こんな感じで設定します。何か便利な関数ができたらpull requestくれるとよ
ろこびます。

おまけ grep-edit, grep-a-lot を ag でつかう

クリアコードさんの記事 Emacs実践入門 - おすすめEmacs設定2012 から
grepの設定を参考にしています。
grep-edit, grep-a-lot自体の設定は省略しています。(僕の設定は
grep-a-lot , grep-edit で公開しています)
ag がインストールされてない場合はそのまま grep コマンドを利用します。

(require 'grep)
(eval-when-compile (require 'cl))
(require 'thingatpt)

;; Use ag if the command was exist, otherwise use grep
(defvar grep-command-before-query
(if (zerop (shell-command "which ag"))
"ag --nogroup -a -S "
;; Recursive grep by -r
"grep -nH -r -e "))

(defun grep-default-command ()
(lexical-let*
((grep-read-from-point
(let ((grep-command-before-target
(concat grep-command-before-query
(shell-quote-argument (grep-tag-default)))))
(cons (if buffer-file-name
(concat grep-command-before-target
" *."
(file-name-extension buffer-file-name))
(concat grep-command-before-target " ."))
(1+ (length grep-command-before-target))))))
grep-read-from-point))

(setq grep-command (cons (concat grep-command-before-query "\"\" .")
(+ (length grep-command-before-query) 2)))

grep-command-before-queryとかなんだかわかってないんですが(helm-apropos
ででてこない..)設定するとM-x grepコマンドが指定したコマンドに変えられます。
それと ag コマンドを –nogroup オプションにすることで、grepと同じ形式
で出力できるので、ag で検索してるんだけど普通に grep コマンドで検索で
きます。なので、、、grep-a-lotとgrep-editをgrepで使えるならそのまま
使えるというわけです。

終わり

大したものではありませんがよかったら使ってください。


5 件のコメント:

  1. grep-command-before-queryはdefvarなので自分で定義した変数です!
    (なのでapproposで出てこない。)

    返信削除
    返信
    1. コメントありがとうございます。SutoさんがC-M-gでgrepしてたのがかっこよすぎたので、すぐにコピぺしたのですが byte-compile するとwarning:assignment to free variable とでてたので元のコードのsetqからdefvarにしてました。そして変えたの忘れたままよく調べないで記事書いちゃいました。推測ですが grep-command に指定する文字列をわかりやすいように名前つけてくれてたんだなーとコメントもらってから思いました。

      削除
    2. かっこよすぎた!!!

      おぉ、そんなwarningがでるとは。。。気づいていませんでした。defvarするのがいいんですかねぇ。調べて直しておきます!

      > 推測ですが grep-command に指定する文字列をわかりやすいように名前つけてくれてたんだなーとコメントもらってから思いました。

      はい、そのとおりです!わかりやすいように名前をつけていただけです。
      ちゃんと気づいてさすがです!

      削除
    3. (´-`).。oO(自分でdefvarって書いちゃったから、letでgrep-commandを囲っ
      たほうが近くていいなぁなんていえない)

      削除
    4. あっダメだ。grep-default-commandの方でもgrep-command-before-query使ってました。すいません。。。

      削除