helm-swoop 20160619.953(in MELPA)
Efficiently hopping squeezed lines powered by helm interface

M-x package-install helm-migemo
M-x package-install ace-isearch

概要

<2015-03-23 Mon>isearchと併合しました。

ども、Emacs Advent Calendar 2014 の最後を
務めさせていただきまするびきちです。

昨日はg000001 さんのM-gを活用しよう でした。

僕は『日刊Emacs』というサイトを立ち上げ、
文字通り毎日Emacsについて何か書いています。

未熟者ですが、サイト・メルマガ・書籍によって
僭越ながらもEmacs道の伝道に携わっています。

今後とも広大なるEmacs界の道具として
より一掃あなたの役に立ちたい気持ちでいっぱいです。

さて、今回主役になるEmacs Lispは…
helm-swoopです!

これを一言で言うと、 helm インターフェースを使って
バッファ内・全バッファを 絞り込み検索 するものです。

M-x helm-occurM-x helm-multi-occur の超強化版です。

『かゆいところに手が届く』helm-occur+αです。

helm-swoopを使いこなすことで、
あなたが日常行っている「検索」が
一気に進化すること間違いありません。

インストール

パッケージシステムを初めて使う人は
以下の設定を ~/.emacs.d/init.el の
先頭に加えてください。

(package-initialize)
(setq package-archives
      '(("gnu" . "http://elpa.gnu.org/packages/")
        ("melpa" . "http://melpa.org/packages/")
        ("org" . "http://orgmode.org/elpa/")))

初めてhelm-swoopを使う方は
以下のコマンドを実行します。

M-x package-install helm-swoop

アップグレードする方は、
以下のコマンドでアップグレードしてください。
そのためにはpackage-utilsパッケージが必要です。

M-x package-install package-utils (初めてアップグレードする場合のみ)
M-x package-utils-upgrade-by-name helm-swoop

helmとは

helm とは インクリメンタル絞り込み検索 を通じて
候補選択 をし、何らかのアクションを行うフレームワークです。

少し前にEmacsを熱狂の渦に巻き込んだ anything.el の後継です。

よくある使い方として、
バッファや最近使ったファイルから串刺し検索をして
開いたり内容を挿入したりとかです。

そういうとファイルやバッファだけにしか使えないと思われがちですが、
helmの中核は絞り込み検索にあるので、検索できるものであれば
何にでも適用できます。

この「何にでも」こそが前身anythingの由来です。

helmは(anythingも)既存のEmacsとは抜本的に考え方が違うので
アンチさんが少なからずいらっしゃいます。

しかし、helmアンチさんにも今日紹介するhelm-swoopだけは
ぜひとも使っていただきたいのです。

helmの他の機能は使わなくていいので、
helm-swoopは体験してほしいです。

ただの先入観のせいでこんな素晴らしいパッケージを使っていないのは
とてつもなく勿体ないですから…。

20141224093733.png
Fig1: M-x helm-miniで串刺し検索!

Migemoとhelm-migemoを導入しよう

まずは準備段階で、 Migemo 環境を整えておく必要があります。

というか、本記事を読んでいるくらいの人ならば
おそらくMigemoは入っているとは思います。

Migemoは日本人Emacserにとっては常識です。

万一入っていないというのならば、
http://emacs.rubikitch.com/migemo/
を見て
今すぐ設定してください!

Migemoとはローマ字で日本語検索するという神ツールで、
検索時にわざわざ漢字変換しなくて済みます。

Debian GNU/Linux系列ならば
cmigemo がパッケージになっているので
一撃でインストールできます。

Windowsで初めてインストールする場合は
もしかしたら手こずるかもしれませんが、
ここはふんばり時です。

一度Migemoの味を知ったら、
これ無しでは生きてけなくなるので
苦労は絶対に報われます。

がんばりましょう。

そして、helmでの検索にもMigemoを適用しようというのが
helm-migemo パッケージです。

helm-swoopはhelm-migemoに対応しているので、
日本人ならばhelm-migemoは必須です。

helmでmigemoが効かない不具合も設定で直しています。

http://emacs.rubikitch.com/helm-migemo/

動作確認はhelm-swoopで行うので
helm-migemoはインストール・設定だけでいいです。

M-x helm-swoopを使おう

M-x helm-swoopを起動してください。

すると、バッファ内の非空白行が*Helm Swoop*バッファに見えています。

C-pやC-nで行を前後すると、バッファの対応位置を表示してくれます。

20141224094103.png
Fig2: M-x helm-swoop実行直後

では何か入力してみましょう。

Migemoが有効になっているのなら、ローマ字で入力します。

検索対象バッファでもマッチした部分が
ハイライトされているのが分かりやすいですね。

20141224094146.png
Fig3: defunにマッチする行を絞り込み

スペースをはさんで検索語を入力すると、
絞り込み検索が行われ、
双方にマッチする行が表示されます。

20141224094242.png
Fig4: Migemoでローマ字絞り込み!

「!」で始まる検索語を入力すれば、
それにマッチしない行に絞り込まれます。

20141224094525.png
Fig5: title(Migemo)を含まない行に絞り込み

そしてRETを押すと、その行にジャンプしてhelm-swoopを終了します。

C-x c b (M-x helm-resume)で終了したhelm-swoopを復元します。

ここまででも初めて使う人にとっては感激ものだとは思います。

しかし、この程度のことはM-x helm-occurでも
だいたい出来ていたことです。

色が付いたり検索対象に自動的に移動するなど
より洗練されていますが
機能的にはM-x helm-occurとさほど変わらないです。

helm-swoopは空気を読んでくれるので
regionが指定してあるときはregionを、
そうでないときは現在のシンボルを
初期入力にしてくれます。

カーソル位置のシンボルを検索させる

プログラミング中にものすごく役立つのが、
カーソル位置の シンボル検索 です。

helm-swoopを使えば同じシンボル間をC-p/C-nで渡り歩けます。

僕は今までシンボル間移動を highlight-symbol.el
やっていましたが、今ではhelm-swoopに取って代わっています。

しかし、anything時代からのバグというか仕様として
Migemo化された情報源は正規表現で絞り込めなくなります。

このことが問題になるケースはめったにないのですが、
helm-swoopでシンボル検索するときに問題になります。

M-x helm-swoop-nomigemo を定義して
複数の正規表現での絞り込み検索ができるようにします。

設定は最後にまとめて示します。

M-x allやM-x occur-edit-modeのようにマッチした行をまとめて書き換えよう

helm-swoopの機能はまだまだこんなものではありません。

helm-swoopで絞り込んだ結果を編集して、
それをバッファに反映する機能があります。

M-x allM-x occur-edit-mode のような機能です。

使い方は、上(header-line)に書いてるようにC-c C-eを押します。

そして、編集してC-x C-sでバッファに反映させます。

20141224095406.png
Fig6: fooが含まれる行を絞り込み

20141224095413.png
Fig7: C-c C-eでhelm-swoop-editに移行

20141224095429.png
Fig8: 置換!しかしまだバッファには未反映

20141224095443.png
Fig9: C-x C-sで初めて反映される

helm/anythingの結果をM-x allで編集するall-ext.el
かつて作っていましたが、同じ機能が実装されてしまいました。

all-extいらないんじゃね?

とはいえall-ext.el(というかall.el)は
*All*バッファの編集が即バッファに反映される違いがあります。

即反映される方が好きな方はall-extを、
一度に反映してほしい方はhelm-swoop版を使ってください。

好みの問題なので。

20141224095512.png
Fig10: C-c C-aでhelm-swoopからallが起動!すぐに反映される

究極合体ace-isearch!isearchからhelm-swoopする

実は素のM-x helm-swoopはキーに割り当てる必要すらありません。

というのは、isearchからM-iでhelm-swoopに移行できるからです。

しかも ace-isearch と併用してしまえば、
M-iを押す必要すらありません。

http://emacs.rubikitch.com/ace-isearch/

ace-isearchは以下の挙動をします。

  • 1文字の場合は ace-jump-char-mode
  • 6文字以上の場合は「自動で」helm-swoop

検索ツールのいいとこどりをしたace-isearch、
まさに究極合体ですね!!

僕の場合ace-isearchからは圧倒的にhelm-swoopが起動されますが、
この名前ではhelm-swoopが連想できないので
ちょっともったいない気がします。

M-x helm-next-errorでhelm-swoopを閉じてもマッチ行にジャンプする

M-x grepM-x occur は検索結果ウィンドウを閉じても
M-g M-pM-g M-n で検索結果に移動できます。

これをhelm-swoop等のhelm/anythingの結果にも適用できるように
拡張したコードを用意しました。

http://emacs.rubikitch.com/helm-next-error/

これでM-x occurは用済みとなりました(笑)

helm-swoopにも対応し、さらにパワーアップした
M-g M-p/M-g M-nをお楽しみください。

この部分の設定は長くなるので本記事では設定していません。

M-x helm-multi-swoopで複数のバッファを検索

helm-swoopでカレントバッファを検索しても見付からない場合、
他のバッファに求める行が見付かるかもしれません。

helm-swoopの結果を表示しているときにM-iを押せば
開いているバッファすべてを検索対象とします。

しかし、Emacs Lispで検索しているので動作は死ぬほど遅いです(泣)

<2015-03-23 Mon>C-u C-sでhelm-swoopする

isearch+helm-swoopはace-isearchもありますが、
その場合はisearchが起動しているのでカーソルが移動してしまいます。

M-x occur のようにマッチする行をリストしたい場合には不便です。

そこで C-u C-s でM-x helm-swoopを起動させるようにしてしまいます。

カーソルを移動させたくない場合はC-gでhelm-swoopを閉じればいいだけです。

さらに C-u C-u C-s でM-x helm-swoop-nomigemoを起動させ、
複数の正規表現で絞り込めるようになります。

C-u C-sは本来正規表現isearchですがhelm-swoopに置き換えても
違和感はありません。

isearchと同様の使い勝手にするため、
初期入力(helm-swoop-pre-input-function)を無効にしました。

なお、この発想は類似品swiper.el(レビュー) のアイデアの応用です。

妄想etc

M-x helm-multi-swoop はとにかく遅いので
移植性はなくなりますが実用性を重視して
grepやagなどの外部プログラムを使った
バージョンも欲しいところです。

Migemo結果の正規表現をgrepに渡すことができるのだから、
これを helm-ag-r のように絞り込めれば…

また、helm-grepなどの結果にもhelm-swoopで提供している
ハイライトやC-p/C-nによる該当行移動などができれば…

いっそのことファイルのアクションのhelm-grepも丸々置き換えて
M-x helm-mini などから起動できるようになってくれたら…

と妄想は尽きません。

$ grep -nE `cmigemo -q -d /usr/local/share/migemo/utf-8/migemo-dict -w taiou` helm-swoop.org /dev/null
helm-swoop.org:80:helm-swoopはhelm-migemoに対応しているので、
helm-swoop.org:92:C-pやC-nで行を前後すると、バッファの対応位置を表示してくれます。

helm-swoopは一番活躍しているhelmコマンドなので、
今後の発展を心から応援しています。

設定 141225040059.helm-swoop.1.el(以下のコードと同一)

;;; この前にmigemoの設定が必要
(require 'helm-migemo)
;;; この修正が必要
(eval-after-load "helm-migemo"
  '(defun helm-compile-source--candidates-in-buffer (source)
     (helm-aif (assoc 'candidates-in-buffer source)
         (append source
                 `((candidates
                    . ,(or (cdr it)
                           (lambda ()
                             ;; Do not use `source' because other plugins
                             ;; (such as helm-migemo) may change it
                             (helm-candidates-in-buffer (helm-get-current-source)))))
                   (volatile) (match identity)))
       source)))


(require 'helm-swoop)
;;; isearchからの連携を考えるとC-r/C-sにも割り当て推奨
(define-key helm-swoop-map (kbd "C-r") 'helm-previous-line)
(define-key helm-swoop-map (kbd "C-s") 'helm-next-line)

;;; 検索結果をcycleしない、お好みで
(setq helm-swoop-move-to-line-cycle nil)

(cl-defun helm-swoop-nomigemo (&key $query ($multiline current-prefix-arg))
  "シンボル検索用Migemo無効版helm-swoop"
  (interactive)
  (let ((helm-swoop-pre-input-function
         (lambda () (format "\\_<%s\\_> " (thing-at-point 'symbol)))))
    (helm-swoop :$source (delete '(migemo) (copy-sequence (helm-c-source-swoop)))
                :$query $query :$multiline $multiline)))
;;; C-M-:に割り当て
(global-set-key (kbd "C-M-:") 'helm-swoop-nomigemo)

;;; [2014-11-25 Tue]
(when (featurep 'helm-anything)
  (defadvice helm-resume (around helm-swoop-resume activate)
    "helm-anything-resumeで復元できないのでその場合に限定して無効化"
    ad-do-it))

;;; ace-isearch
(global-ace-isearch-mode 1)

;;; [2015-03-23 Mon]C-u C-s / C-u C-u C-s
(defun isearch-forward-or-helm-swoop (use-helm-swoop)
  (interactive "p")
  (let (current-prefix-arg
        (helm-swoop-pre-input-function 'ignore))
    (call-interactively
     (case use-helm-swoop
       (1 'isearch-forward)
       (4 'helm-swoop)
       (16 'helm-swoop-nomigemo)))))
(global-set-key (kbd "C-s") 'isearch-forward-or-helm-swoop)

本日もお読みいただき、ありがとうございました。参考になれば嬉しいです。