Software Design連載記事を掲載します。
株式会社技術評論社の許可を得て掲載しています。
草稿なので細かい部分は実際の記事とは異なることがあります。
他の記事は左下にある「■雑誌連載中(全文公開)」から見られます。
Emacsを支配するもの
ども、るびきちです。本連載も来月で1周年となりますけれども、1年の締めくくりとして今月と来月の2回に分けてhelmという超絶便利なパッケージを紹介します。
helmというパッケージとは何かを言葉で説明するのはとても難しいです。しかし、一度使ってみればその他のEmacsのコマンドとは異次元のものを感じることは間違いありません。なぜなら、その他のEmacsのコマンドとは考え方そのものが根底から異なるのです。
helmとはヘルメットではなくて「舵(かじ)」とか「支配」という意味なので、「Emacsの舵」とか「Emacsの支配者」ということになります。そしてその名に相応しい活躍をします。
新しいパラダイムへ
プログラミングの世界では幾度かパラダイムシフトが起こりました。手続型からオブジェクト指向へと移行したのは特筆すべきことです。例えば長さを求める関数は、手続型の場合だと型に応じて別な関数名を用意する必要がありますが、オブジェクト指向ならば同じメソッド名でよくなりました。string_length(a_string)、array_length(an_array)がa_string.length()、an_array.length()と書けます。より人間にわかりやすいコードになりました。それをさらに推し進めると配列に似たオブジェクト「リスト」も定義できます。リストに配列と同じメソッドを用意してあげれば、人間にとっては「リストは配列と同じように扱えて覚えるのが簡単だ」と思います。
また、書き方も手続型では関数を先に指定するのに対し、オブジェクト指向ではオブジェクトを先に指定します。
これと同じことがhelmによりEmacsの世界で起こるようになります。
通常のEmacsは手続型の世界です。なぜなら、ファイルを開くことに関しても「ファイル名を指定してファイルを開く」「最近開いたファイルを開く」「ファイルキャッシュから開く」「locateの出力結果から開く」などが別々のコマンドとして存在しています。しかし、どれも「ファイルを開く」ということには変わりありません。ファイルを指定する前に特定のコマンドを指定する意味でも手続型です。
これを手続型の疑似コードで書くと「open_from_recent_file(a_recent_file)」、「open_from_file_cache(a_file_in_file_cache)」などとなります。どう見ても型違いの同じ働きをする関数ですよね。
オブジェクト指向になると「a_recent_file.open()」、「a_file_in_file_cache.open()」となります。a_recent_fileもa_file_in_file_cacheもファイル名なので、ファイルを開くopenメソッドは共通のものが使えることになります。
つまり、何らかの手段で最近開いたファイルやファイルキャッシュから1つのファイルを選び出すことができれば、Emacsでもオブジェクト指向できることになります。これを行うのがhelmです。これでもなおhelmのたった一面しか説明できていませんが…
「最近開いたファイルの集合」、「ファイルキャッシュ全体」などはオブジェクト指向における『クラス』に相当しますが、helmでは情報源(source)といいます。
多くのアクション
ファイルを開くことに関しても、いろいろな開き方があります。普通に「現在のウィンドウで開く」以外にも「別ウィンドウで開く」とか「別フレームで開く」などです。最近開いたファイルにおける疑似コードで「open_from_recent_file_other_window(a_recent_file)」とか「open_from_recent_file_other_frame(a_recent_file)」などと用意するのは切りがありません。そうするくらいならば「a_recent_file.open_other_window()」などのように新しい『メソッド』を定義するでしょう。
ファイルに関するメソッドはファイルを開く関連だけでなく、ファイルの内容をバッファに挿入したり、ファイルを削除したりなど、たくさんあります。
このオブジェクト指向におけるメソッドをhelmではアクションといいます。
各情報源には複数のアクションが定義されていることが多いですが、一番よく使われるであろうアクションが優先度最上位のデフォルトアクションになります。バッファやファイルでは開くことです。
絞り込み検索
つまり、用意された情報源から1つの候補とアクションリストから1つのアクションを素早く選択することさえできれば、Emacsのオブジェクト指向化は成されることになります。オブジェクト指向化が成されれば、「最近開いたファイルを別ウィンドウで開く」などEmacs標準では用意されていないことが簡単にできるようになります。なぜなら、「『最近開いたファイル』から1つのファイルを選択」し、「『別ウィンドウで開く』というアクションを選択」すればよいのですから…。
そのときに超重要になるのが候補選択の部分です。helmの使い勝手は候補選択ひとつに大きく関わってきます。候補選択は、情報源からだけでなく、アクションリストからでも使われるからです。
候補選択においてはスペースで区切った正規表現を複数個指定して絞り込み検索ができます。たとえば「org el」でorgとel双方にマッチする候補がリストアップされます。一方「!」を指定すればマッチしない正規表現になります。「org el !dblock」でorgとelにマッチするがdblockにマッチしないものが対象になります。
デフォルトアクションは候補選択後にすぐRETで実行できます。よって、バッファを開くのであればバッファの候補選択をしてRETと、操作感覚がidoとあまり変わりません。
この絞り込み検索機能のおかげでhelmは世界中から熱狂的な支持を得ています。それでも、まだhelmの半分しか説明できてないのが恐しいところです…
ひとまとめにされた情報源
helmが「Emacsの舵」と言えるのは、複数個の情報源を串刺し検索できてしまうところにあります。情報源は何の集合であっても構いません。バッファ名とファイル名が混在していても構いません。たとえば、開いているバッファと最近開いたファイルから「helm」が含まれているものをまとめて検索できます。
このおかげで、バッファを切り替えるコマンドと最近開いたファイルを開くコマンドを別個に割り当てる必要はなく、どちらもひとつのhelmコマンドでまかなえてしまいます。最近開いたバッファを間違えて削除してしまった場合にも、今度は自動的に最近開いたファイルから拾い出せます。
つまり、複数の情報源から目的のオブジェクトとやりたいアクションを絞り込み検索するところにhelmの凄味があるのです。現代の潤沢なマシンパワーによる強引な力業です。
anythingからhelmへ
helmの前身はEmacs界に新たな流れを呼び込んだanythingでした。anything時代から前節で説明したことはすでに達成されていました。今はanythingに関する情報もたくさん見付かるのでhelmを理解するにあたっては大いに役立ちます。また、anythingとhelmは共存させられます。それでは、anythingからの変化を見ていきましょう。
anything時代
anythingは2007年にTamas Patrovics氏が開発したものです。筆者はそれにものすごい惚れ込み2008年にはTamas氏より開発を引き継ぎました。そして、絞り込み検索機能(anything-match-plugin)、Migemo対応(anything-migemo)、候補バッファの導入(candidates-in-buffer)、チラ見機能(persistent-action)、独立したanythingコマンド(M-x anything-miniなど)など、今のhelmの核となる機能を実装しました。2008年の時点ですでに機能として完成されていました。
2008年の2月にはSoftware DesignのEmacs特集でanythingの原稿も書きましたし、2010年の『Emacsテクニックバイブル 作業効率をカイゼンする200の技〜』、2011年の『Emacs Lispテクニックバイブル 〜真髄を知るLispの掟〜』には独立した章を設けてanythingを解説しました。
anythingの情報源がすべて入っているanything-config.elは膨大な量となりました。「候補選択といえばanything」という流れになり、たくさんの人によって数えきれないほどの情報源・anythingコマンドが書かれました。
フォークからリネーム!
anythingが活発だった時代、筆者とThierry Volpiatto氏の二人で開発していましたが、二人の意見が正反対でした。そのころにはanythingは十分成熟していたと筆者は感じていたので、筆者は互換性を最優先し、変化に対してはきわめて慎重派でした。対して彼はanythingを抜本から変えたいという急進派でした。
よって、名前をanythingからhelmという名前に変更して開発を続けると彼は言い出しました。名前が変更されればanythingの範囲内においては非互換に悩まされることはないという彼の配慮でした。筆者は喜んでそれを受け入れ、開発の主導権が移動したのでした。
彼の熱意で始まったhelmはどんどんと抜本的なコード改造がなされ、基本的にはanythingでありながらも、様々な機能が追加されるようになりました。helmインターフェースも数多くMELPAに登録されました。時代はanythingからhelmに取って代わったのです。
彼は熱狂的な開発者で、ものすごい勢いでコードを書いています。しかし、ユーザの視点に立つ配慮に欠けているため、突然操作法を変更することを平気でやってしまいます。そのため、anythingから移行する際、あるいはhelmを更新する際に戸惑う可能性があります。helmの内部は複雑であり、解説も英語であるため、Emacs熟練者でなければ問題解決できないこともあります。その点については気付いた限り筆者のサイトでお伝えすることにします。
一方、筆者は今尚anythingも使いメンテナンスしていますが、ここ数年は大きな変更はありません。今後のEmacsでも動作させるようにはします。細かい点がいろいろと変更されたhelmよりも昔ながらのanythingが好きと言ってくれる人がいてくれるのはありがたいことです。
新旧入れ交じった様相はかつての「Ruby 1.8 vs Ruby 1.9」、今の「Perl5 vs Perl6」、「Python2 vs Python3」を思わせます。
変化していく使い方
コマンドの観点からanything〜helmを振り返るのも分散・集中を周期的に繰り返すシステム構成を見ているようで興味深いです。
初期のanythingは、使う情報源を変数anything-sourcesに設定し、M-x anythingで起動するものでした。これは確かにシンプルで複数の情報源から同時に検索していろいろな開き方ができることを表現しました。情報源は数個しかなかったものの、バッファからも最近開いたファイルからも同じように開けるということは、当時は大きな衝撃でした。
これだけ強力な機能でありながら、情報源が少ないのではもったいない話です。そこでanything本体と情報源などの設定を格納したanything-configに分かれました。別々にメンテナンスされることで作者以外の方による情報源もたくさん書かれ、情報源の選択肢が一気に広まりました。
一方ユーザの観点では「どの情報源を設定すればいいか」が見えてこないのでanythingは敷居が高いものでした。そこで、すぐにanythingの恩恵を味わってもらうために「設定済みanything(anythingコマンド)」が登場しました。すでに情報源が設定されているので、ユーザはM-x anything-miniやM-x anything-for-filesという形ですぐにanythingの恩恵を受けられるようになりました。
これらのように複数の情報源を設定したanythingコマンドはいいのですが、たちまち単一情報源のanythingコマンドが多数定義されるようになりました。anythingの絞り込み検索があまりにも強力なので、それだけを目当てにしたanythingコマンドです。複数情報源・複数アクションだからこそ真価を発揮するanythingもこれでは、手続型の「コマンドが覚えられない」問題に逆戻りしてしまいます。
今も単一情報源のhelmコマンドが多く存在しています。そろそろ再び統合の流れが来るのではないでしょうか。
再構成されたファイル群
anything時代は本体anything.elと設定集anything-config.elの2つのファイルで構成されていました。しかし、anything-config.elが肥大化していったため、見通しが悪くなってきました。そこでhelmでは目的別に多数のファイルに分割されました。
- helm.el
- 本体
- helm-config.el
- キーの設定、その他
- helm-mode.el
- helmを用いた補完
- helm-match-plugin.el
- 絞り込み検索
- helm-help.el
- ヘルプ機能
- helm-source.el
- 情報源を定義するときに使う
- helm-utils.el
- ユーティリティ関数
- その他
- 各パッケージに関する情報源を定義
helmはとても活発に開発されております。helmパッケージには数多くのhelmコマンドが定義されているので、あなたが欲しい情報源・helmコマンドはすでに存在するかもしれません。興味ある方はお宝探索気分でM-x grep等でhelmのソースコードを検索してみるといいです。
helm-miniに見るhelmの力
helmをインストールしたら真っ先に使っていただきたいコマンドがM-x helm-miniです。名前の通り最小構成のhelmコマンドですが、これを詳しく知るだけでもhelmの威力をまざまざと感じられます。
インストール
それでは、helmを実際に使うためにインストールしましょう。helmはMELPAに登録されています。
==
List1:パッケージを使うための初期設定
(package-initialize) (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
==
M-x package-refresh-contents
M-x package-install helm
情報源はバッファとrecentfだけ!
M-x helm-miniの情報源は「開いているバッファ」「最近開いたファイル」「バッファ新規作成」の3つだけです。そもそもファイル以外のバッファを作成すること自体が少ないので、実質バッファと最近開いたファイルのみです。
バッファの切り替えを効率的にすることは、Emacsライフにおいて大きな意味を持ちます。なるべく少ない打鍵数で目的のバッファに切り替えられるとストレスが少なくなります。そこでhelmの絞り込み検索がとても役立ちます。同種の目的にidoがありますが、絞り込み検索ができる点、そしてバッファに対する多数のアクションが用意されている点でhelmが勝っています。
一方、最近開いたファイルはrecentfパッケージです。使ってみればわかりますが、過去に開いたファイルを簡単に開けます。新規ファイル以外では過去に開いたファイルを開くことがとても多いので有用性は実感できるはずです。
M-x helm-miniはバッファを削除してしまった場合でも、今度は最近開いたファイルから見つけ出してくれます。よって、recentfはバッファ検索で見付からないときの保険になってくれます。
Fig1: M-x helm-miniでorg8と入力したところ
37のアクション
helmはオブジェクトのタイプによって多くのアクションを持っていることが特徴です。M-x helm-miniはバッファとファイルを扱うので、それらのアクションを実行できます。
執筆時点でバッファのアクションは17個、ファイルのアクションは20個用意してあるので、M-x helm-miniでは実に37個の機能を持っていることになります。しかも開いているバッファと最近開いたファイルは同時に検索できるので単に37個のコマンドがバラバラに用意されているのに比べて圧倒的に便利です。
バッファのアクション
M-x helm-miniを起動すると、即座にバッファのリストが見えます。そこでTABを押すと以下のようにアクションが羅列します。
アクションのうち一番上にあるのがデフォルトアクション(Switch to buffer)です。アクションリストを開かずとも候補をRETで選択すると実行されます。
他にも目立つのがファンクションキーとアクションに書いてあるキーバインドです。たとえば、アクション「Switch to buffer other frame」はアクションリストを開いても開かなくてもかずとも<f3>かC-c oで実行できます。
このことは、デフォルトアクション以外を実行する場合でもキーバインドさえ覚えていればアクションリストを開かなくてもすぐに実行できることを意味します。helmを使いはじめたばかりの頃ではアクションリストを開いて選択していたけど、慣れてくると候補選択後に特定のキーを押せばそのアクションが実行できます。
各情報源によって使えるキーバインドは異なりますが、C-c ?で見られるヘルプを参照してください。
==
バッファのアクション(;以下は注釈)
[f1] Switch to buffer ; バッファを開く
[f2] Switch to buffer in popup window ; ポップアップウィンドウで開く
[f3] Switch to buffer other window `C-c o' ; 別ウィンドウで開く
[f4] Switch to buffer other frame `C-c C-o' ; 別フレームで開く
[f5] Display buffer in Elscreen ; 新しいelscreenを作成する
[f6] Query replace regexp `C-M-%' ; 正規表現置換(複数可)
[f7] Query replace `M-%' ; 置換(複数可)
[f8] View buffer ; view-modeで開く
[f9] Display buffer ; 表示するだけでウィンドウを選択しない
[f10] Grep buffers `M-g s' (C-u grep all buffers) ; grepする(複数可)
[f11] Multi occur buffer(s) `C-s' ; multi-occur(elisp版grep)する(複数可)
[f12] Revert buffer(s) `M-U' ; 再読み込み(複数可)
Insert buffer ; バッファを挿入する
Kill buffer(s) `M-D' ; バッファを削除する(複数可)
Diff with file `C-=' ; バッファと保存されているファイルをdiffする(未保存時のみ)
Ediff Marked buffers `C-c =' ; 2つのバッファのediffを取る
Ediff Merge marked buffers `M-=' ; 2つのバッファをマージしたバッファを作成する
==
アクションをざっと見てみると、開き方だけでも多種多様あることがわかります。<f2>のpopup windowとはpopwin.elがインストールされているときに使えます。<f5>のElscreenとはelscreen.elがインストールされているときに新しいウィンドウ構成としてそのバッファを開きます。<f8>ではview-modeで開き、<f9>ではバッファを選択しないで表示だけします。
外部ライブラリに依存するアクションは、ライブラリがインストールされていない場合には表示されません。そのため、環境によってはファンクションキーがずれることがあります。
一部のアクションは(s)と複数形も受け付けるようになっています。これは、候補をC-SPCでマークすることで、マークしたバッファ全てにおいて実行するアクションです。diredでファイル操作するとき、マーク無しのときはカーソル位置、マーク有りのときはマークされたファイルが対象になるのと同じです。例えば一連のバッファを削除したい場合は、削除したいバッファをマークしてM-Dを押せばいいです。
他にも置換(Query replace)や検索(Grep、Multi occur)、diffが取れたりもします。マークすれば複数のバッファにわたる置換や検索ができます。
ediffはEmacs屈指の人気機能でdiffをわかりやすく示してくれます。2つのバッファをマークしたらそれらの比較をし、マークがない場合はカレントバッファとの比較になります。ediff mergeは、2つのバッファをマージしたバッファを作成します。ediffはそのうち記事にする予定です。
このようにアクションは本当にたくさん用意されていますが、わざわざ覚える必要はありません。そういえばこんなことができたなと頭の片隅に置いてさえすれば、TABでアクションリストを開き、該当アクションを選択すればよいだけです。
ファイルのアクション
ファイルのアクションは以下のものです。
==
ファイルのアクション
[f1] Find file
[f2] Find file as root
[f3] Find file other window
[f4] Find file other frame
[f5] Open dired in file's directory
[f6] Grep File(s) `C-u recurse'
[f7] Zgrep File(s) `C-u Recurse'
[f8] Pdfgrep File(s)
[f9] Insert as org link
[f10] Checksum File
[f11] Ediff File
[f12] Ediff Merge File
Etags `M-., C-u tap, C-u C-u reload tag file'
View file
Insert file
Add marked files to file-cache
Delete file(s)
Open file externally (C-u to choose)
Open file with default tool
Find file in hex dump
==
ファイルを開くアクションはいろいろあることがわかります。一般ユーザでは書き込み不能なファイルはroot権限で開けます。別ウィンドウ、別フレームで開いたり、そのファイルが収められているディレクトリをdiredで開いたり、view-modeで開いたりできます。最後のhex dumpというのはバイナリファイルを16進ダンプで開くhexl-find-fileを起動します。
バッファ同様、ファイルに対してもediffが使えます。org-modeのリンクを挿入するアクションは、org-modeファンにとって嬉しい限りです。
C-zでチラ見
helmでC-zを押すとpersistent-actionを実行します。いわゆるチラ見機能で、helmを終了することなくそのオブジェクトの内容を見られます。ファイルやバッファならばそれを開き、関数や変数ならばその説明を表示します。絞り込んだオブジェクトの内容をひとつひとつ見たい場合に便利です。
スクロールはあたかも別ウィンドウを操作するようにC-M-vとC-M-S-vでできます。
チラ見するだけでhelmを終了するには、C-gを押します。ウィンドウ構成がhelm起動時のものに戻ります。
Fig2: C-zを押してチラ見!
まとめ
今回は最小構成のM-x helm-miniを通してhelmの概念を大まかに解説しました。helmコマンドはそれこそ無数に定義されていますが、ファイルやバッファを開くのであればM-x helm-miniで8割方間に合うでしょう。M-x helm-miniで使われているバッファの絞り込みは独特のものになっているのですが、残念ながら紙面の都合上、来月に回すことにいたしました。来月は他のhelmコマンドの解説もしていきますので楽しみにしててください。
筆者のサイト http://rubikitch.com/ はEmacs総合サイトを目指して日々更新しています。helmについての記事も多数書いてあります。
本日もお読みいただき、ありがとうございました。参考になれば嬉しいです。