これはKarthik Chikmagalurさんによって記述された記事を日本語に翻訳した記事であり、記事の所有権と著作権はKarthik Chikmagalurさんに帰属します。
元の記事: Fifteen ways to use Embark | Karthinks
Embarkは非常に優れたパッケージであり、思慮深く設計されたパッケージでもある。Emacsのアクション -> オブジェクトという順序を反転させるパッケージであり、学習曲線の追加なしで習得できる。EmbarkはわたしのEmacsの使い方を完全に変えてしまったんだ。この記事でその理由について説明しよう。
Emacsのアクションモデルは、デフォルトではアクションを指定(たとえばfind-file)した後にオブジェクト(たとえばファイル)を指定する:
これはシェルで何か物事を行う方法を反映したモデルだ:
違いはシェルコマンドのほうは単なるテキスト行なので、実行前であればアクションとオブジェクトはどちらも自由に編集できることだ。Emacsではオブジェクトは自由に変更できても、アクションを変更するにはC-gを押下してから別のコマンドの呼び出しが必要となる。
ファイルマネージャーのようなGUIプログラムでは別の方法によって物事が運ぶ。オブジェクト表現(通常はアイコン)を選択して、それにたいして実行したいアクションを選択するのだ:
どのパラダイムでも良好に機能する。しかしこちらはEmacsだ。ただ1つの方法を選択する謂われなど存在しない! Embarkによって2つのパターンの間を行き来することが可能になる 。たとえばファイルを別のウィンドウでオープンしたい? 別のディレクトリーにコピーしたい? 自分が何がしたいのかを決める前に、まずはfind-fileを呼び出してファイルを選ぶといった操作が可能になるのだ。
Embarkがあれば容易いことだ。
embark-act: 実は… & でもまずは…
あなたにとって”実は…”を実現するコマンドがembark-actだ。つまりpackage-installを呼び出してパッケージを選んだものの、実はパッケージの説明が読みたかったんだ! を実現するコマンドと言える。
同時にembark-actは”ああ、でもまずは…”を実現するコマンドでもある。つまりfind-fileを呼び出しました。でもまずは念のためどこか別の場所にコピーしたいな。その後でファイルのオープンを再開しよう! を実現するコマンドと言える。
それともGUI環境における”右クリックメニュー”に相当する、キーボード駆動型の類似機能と考えるかもしれない。その類比も機能はするが、前者のほうがEmbarkによって実現が可能となった、”遅延バインディング”と怠惰の概念によく合うように思える。
Emacsではまずアクション/動詞(たとえばfind-file)を指定·決定してから、それが作用する対象(たとえばファイル)を選択する。embark-actを呼び出すとこれが逆転する。今度はオブジェクト(ファイル)を決定してから、アクションを自由に選択することができるのだ。
Helmアクションの説明に聞こえる? 分かっている。Embarkはどこでも、すべてのタイプの”オブジェクト”に跨って機能する点が異なる。最初のコマンドでも、wait-I-changed-my-mindコマンドでも機能する点が異なるのだ。事前定義された初期アクションの集合にたいして機能するように構成された、代替えアクションの集合が事前に別途定義されている訳ではない。(あなた自身を含めて)誰も連携させるアクションを前もって予想する必要はない1。この均一かつ一貫したEmacsへの統合により、確認には若干の時間を要するが、両者の間に量ではなく質の違いがもたらされるのである。
これはあるコマンドを開始してミニバッファーで候補を選択、それからembark-actを呼び出して、M-x some-other-commandでかわりそのコマンドを実行できることを意味している。C-x kでバッファーをkillしようとしたが、かわりにそのバッファーに切り替えたくなったのなら、embark-actを呼び出した後にC-x bで切り替えることができる。そのバッファーをちょっと確認したいだけであれば、kill-bufferのプロンプトを残したまま確認することすら可能なのだ!
Embarkはファイル名、バッファー、ブックマーク、URL、テキストリージョン、変数、コマンド、シンボル等、一般的なオブジェクトカテゴリーのほとんどを理解する。
embark-actを呼び出すと、Embarkがオブジェクトカテゴリーそれぞれにたいして、ユーザーが実行したいと思うような一般的なアクションに直接アクセスするためのキーマップのアクティブ化も行う。たとえあなたが実行するのが常にI-changed-my-mindコマンドであっても、このアクションを実行するたびに本来は毎回M-xを使用する必要があるのだが、これを行うことによって不要になる。もちろんわたしが行っているように、このキーマップに独自コマンドを追加することもできる。
わたしはembark-actを文字通り日に数百回使用する。以下にわたしがEmbarkを使用する際の一般的な使い方を示す。これらのうちのいくつかはビルトイン、他のものについては機能させるために若干のelispコードが必要だが、どれも驚くほど役に立つものばかりだ。誤解のないよう言っておくが、このリストはEmbarkがもつ可能性のほんの表面をなぞったに過ぎない。
デモ再現のためのレシピ
任意のウィンドウを分割して任意のバッファーをオープンする
少し背景の説明が必要だろう。ace-windowはキーボードヒントにもとづくウィンドウ切り替えを提供するパッケージだ。あまり知られていないが、単にウィンドウを切り替える以外の方法でウィンドウにアクションを行う、”ディスパッチメニュー”という機能も提供している。
ビデオ実況
- 2つ以上のウィンドウをオープンして
ace-window
を呼び出す。 - ?を押下してディスパッチメニューを立ち上げる。
- ウィンドウを水平方向に分割するためにディスパッチキー(ここではv)を押下する。
- 分割したいバッファーに応じたace-windowキー(ここではe)を押下する。
- ステップ1と2を繰り返す。
- ウィンドウを垂直方向に分割するためにディスパッチキー(ここではs)を押下する。
- 分割したいバッファーに応じたace-windowキー(ここではw)を押下する。
ディスパッチキーを使用してウィンドウのkill、移動、分割を行うことができる(?を押すとディスパッチメニューを表示)。
さて、これでEmbark経由でace-window呼び出して、任意の場所(上記ディスパッチメニューを使って作成した分割ウィンドウを含む)にある候補を表示できるようになった。これはわたしがオープンしたバッファー/ファイル/ブックマークを、スクリーン上でわたしが表示したい場所に常に配置できることを意味している。
以下のデモでは(consult-bookmarkで)ブックマーク、(find-fileで)ファイル、(consult-bufferで)バッファーを順にオープンしている。オープンする際には毎回embark-actを実行してからace-windowアクションを選択してace-windowをアクティブにしている。ace-windowで選択を行うことによって、既存のウィンドウのどれにでもバッファーを表示できる。実際のデモでは一歩進んで、ace-windowのディスパッチ機能を使って既存ウィンドウの1つを分割して、関連するバッファーの分割したウィンドウへの表示まで行っている!
ビデオ実況
- ファイル、ブックマーク、バッファーの選択を要するコマンド(switch-to-bufferとか)を実行する。
- 選択したら
embark-act
を実行する。 o
でmy/embark-ace-action
を実行する(以下参照)。- バッファーを配置したいウィンドウを選択する。
- または
v
やb
(aw-dispatch-alist
を参照)で既存のウィンドウを分割してウィンドウを選択、新たに分割されたウィンドウにバッファーを表示する。
これを機能させるにはEmbarkのファイルアクションマップにace-windowの関数をいくつか追加する必要がある:
(eval-when-compile
(defmacro my/embark-ace-action (fn)
`(defun ,(intern (concat "my/embark-ace-" (symbol-name fn))) ()
(interactive)
(with-demoted-errors "%s"
(require 'ace-window)
(let ((aw-dispatch-always t))
(aw-switch-to-window (aw-select nil))
(call-interactively (symbol-function ',fn)))))))
(define-key embark-file-map (kbd "o") (my/embark-ace-action find-file))
(define-key embark-buffer-map (kbd "o") (my/embark-ace-action switch-to-buffer))
(define-key embark-bookmark-map (kbd "o") (my/embark-ace-action bookmark-jump))
わたしはさらにウィンドウを垂直または水平に分割してバッファーをオープンするアクションも追加しているが、ace-windowのディスパッチメニューで同等以上のことができるので、あなたには多分必要ないだろう!
(eval-when-compile
(defmacro my/embark-split-action (fn split-type)
`(defun ,(intern (concat "my/embark-"
(symbol-name fn)
"-"
(car (last (split-string
(symbol-name split-type) "-"))))) ()
(interactive)
(funcall #',split-type)
(call-interactively #',fn))))
(define-key embark-file-map (kbd "2") (my/embark-split-action find-file split-window-below))
(define-key embark-buffer-map (kbd "2") (my/embark-split-action switch-to-buffer split-window-below))
(define-key embark-bookmark-map (kbd "2") (my/embark-split-action bookmark-jump split-window-below))
(define-key embark-file-map (kbd "3") (my/embark-split-action find-file split-window-right))
(define-key embark-buffer-map (kbd "3") (my/embark-split-action switch-to-buffer split-window-right))
(define-key embark-bookmark-map (kbd "3") (my/embark-split-action bookmark-jump split-window-right))
ファイルのオープン時にリモートへコピーする
ビデオ実況
- ファイルの選択を要する任意のコマンド(
find-file-other-window
とか)を実行する。 - ファイルを選択して
embark-act
を実行する。 c
でcopy-file
アクションを実行する。Embarkにはこれ用のキーがあるが、ここでM-x copy-file
の実行も可。- 目的となるパスへ移動する。ビデオでは
consult-dir
を使って、わたしのブックマークからリモートにあるパスに一時的に切り替えている。 RET
を押下してファイルをコピーする。コピー先での名前を入力できる。
ここでは何が起きているのだろうか? ファイルの入力を求める任意のプロンプトでembark-actを実行することで、(M-x copy-fileを呼び出したかのように)かわりにファイルをコピーするアクションを選択できるのだ。デモではコピー先の入力を求めるプロンプトにたいして、consult-dirを使用してわたしのサーバーを指すブックマークを挿入、ファイルのコピーはTrampが行っている。
find-fileプロンプトを残したまま、これを行うことすらできるのだ! embark-act呼び出しでプレフィックス引数を指定することにより、プロンプトをそのまま残すことができる。
embark-copy-remote-persist-demo.mp4
ビデオ実況
- ファイルの選択を要する任意のコマンド(find-file-other-windowとか)を実行する。
- ファイルを選択してプレフィックス引数とともにembark-actを実行する。つまりC-.にembark-actをバインドしている場合にはC-u C-.。
- cでcopy-fileアクションを実行する。Embarkにはこれを行うキーが用意されているがM-x copy-fileでも可。
- 目的のパスへ移動する。ビデオではconsult-dirを使って、わたしのブックマークからリモートにあるパスに一時的に切り替えている。
- RETを押下してファイルをコピーする。コピー先での名前を入力できる。
- 操作の前に行っていたfind-file-other-windowプロンプトへの入力を継続する。
最後はfind-fileプロンプトを手作業で閉じてから、ちゃんとファイルがコピーされたかリモートディレクトリーをチェックしている。
バッファーへのミニバッファー候補の挿入
シンプルだけど非常に優秀。
ビデオ実況
セッションを継続しつつミニバッファーのファイル候補のシェルコマンドを実行する
でもまず必要なのは… の完璧な例
ビデオ実況
- ファイルの選択を要求する任意のコマンド(find-fileとか)を実行する。
consult-dir
で離れたディレクトリーに切り替える。- ファイルを選択して、プレフィックス引数とともにembark-actを実行する。つまり
C-.
にembark-act
をバインドしている場合にはC-u C-.
。 &
を押下してasync-shell-command
アクションを実行する。Embarkのキーマップにはこれを行うためのキーがあるがM-x async-shell-command
、あるいはasync-shell-commandの
デフォルトのキーバインディング(M-&
)でも実行可。- プロンプトにコマンドを入力する。ファイル名はすでに用意されている。ファイルについてさらに情報を得るために、シェルコマンドfileを使用した。
RET
を押下してコマンドを実行して、find-file
の入力プロンプトにリターンする。
ファイルについてさらに情報を得るために、find-fileのプロンプトを終了させずにシェルコマンドの”file”を呼び出した。
セッションを維持したままrootでファイルをオープン
コマンドの前のsudo忘れちゃったの、のEmacs版である。シェルならプロンプトの先頭に戻ってタイプして追加、あるいはsudo !!を召喚して儀式を執り行うところだが、EmacsではEmbarkアクションを使用する。
ビデオ実況
- ファイルの選択を要求する任意のコマンドを実行する。わたしはファイルシステム上でroot所有のファイルをlocateするためにconsult-locateを使用した。
- ファイルを選択してプレフィックス引数とともに
embark-act
を実行する。つまりC-.
にembark-act
をバインドしている場合にはC-u C-.
。 S
でsudo-find
アクションを選択した。注意:このアクションをキーマップに追加する必要があるだろう(以下参照)。かわりにM-x sudo-find-file
やそれのグローバルバインディングの実行でも可。
わたしはconsult-locateをコマンドを実行したが、前の例と同じようにファイルの入力を求めるコマンドなら何でも機能するはずだ。わたしはinitファイルからもはや来歴不明なスニペットを流用したが、sudoプログラム向けのsudo-editパッケージもある。
(defun sudo-find-file (file)
"Open FILE as root."
(interactive "FOpen file as root: ")
(when (file-writable-p file)
(user-error "File is user writeable, aborting sudo"))
(find-file (if (file-remote-p file)
(concat "/" (file-remote-p file 'method) ":"
(file-remote-p file 'user) "@" (file-remote-p file 'host)
"|sudo:root@"
(file-remote-p file 'host) ":" (file-remote-p file 'localname))
(concat "/sudo:root@localhost:" file))))
Embarkアクションとしてsudo-find-fileを使用するには、embark-act呼び出し後にM-xやグローバルバインディングから実行するか、あるいはEmbarkのファイルアクションのマップに追加してさらに短縮することもできる:
(define-key embark-file-map (kbd "S") 'sudo-find-file)
テキストリージョンを0x0にアップロード
ビデオ実況
0x0-dwim関数のために0x0パッケージを使用している。URLにたいしてEmbarkアクションとして呼び出すとURLを短縮、ファイルの場合にはファイルをアップロードする。最後の(0x0-dwimからの)エコーエリアのメッセージによって、アップロードURLがkillリングにコピーされたことがわかる。他の例と同じように、embark-actの後に0x0-dwimを呼び出したり、Embarkのキーマップの1つとしてより短いキーを定義してもよい:
(define-key embark-region-map (kbd "U") '0x0-dwim)
ミニバッファーからパッケージのURLをvisit
ビデオ実況
今回は “実は…URLを頼む” の前にdescribe-packageコマンドを実行したが、describe-packageでも特別な扱いは不要であり、他の例と同様である。ミニバッファーでパッケージのリストを表示するすべてのコマンドは、同じ一連のEmbarkアクションを提供する。
バッファー内の任意の場所から変数をセット
素早く変数をセットする。コードのテスト時には非常に役に立つ。
ビデオ実況
このデモではEmbark自身の変数キーマップにset-variable用のエントリーを所有している(=にバインド)が、単にM-x set-variableで呼び出しでも可。
任意の場所からコマンド名にキーバインディングを追加
すべてのキーをセットする。
ビデオ実況
Embarkは自身のキーマップでglobal-set-keyを実行するアクションを提供しているが、コマンド名にポイントを配置してembark-actを実行した後にM-x global-set-keyを呼び出してもよい。embarkのキーマップにはlocal-set-keyもセットされている。
embark-export: 要点が知りたいのでリストください
すべてがEmbarkだったなら、わたしは幸せだったこちだろう。しかしembark-actさえもっとも優れた機能ではないのだ。構成を可能にするということにかけては、embark-export(と劣化版のembark-collect)こそがEmbarkの至宝と言えるだろう。これらのコマンドはミニバッファーの候補リストから永続的なコレクションを作成する。一方はivy-occur、もう一方はEmacsよりも巧妙にEmacsライブラリー同士を結びつける接着剤の役割りを果たすのだ。理由は例を使って説明しよう:
Emacsのパッケージ候補をパッケージメニューにエクスポート
package-menu-modeでEmacsのシェル関連のすべてのパッケージを見たい? それならembark-exportの守備範囲だ:
embark-package-export-demo.mp4
ビデオ実況
embark-exportの背後には、可能なら常にEmacsのビルトイン機能を再利用するという優れたアイデアが存在する。すでにpackage-menuライブラリーがパッケージの表示を処理しているので、それを再利用するのだ。ユーザーが指定した条件でパッケージのリストを生成しているのに、車輪を再発明する必要があるだろうか?
“imenu-list”候補の収集
embark-collectは(ユーザーの入力によってフィルタリングされた)ミニバッファーの補完候補にたいする永続的なコレクション作成する。これにより、基本的に何かを”リスト”するようなすべてのパッケージは、わたしにとって時代遅れのものとなってしまった。以下の例ではファイルに何かを行う際に使用できるimenuアイテムのフィルター済みリストを作成している:
ビデオ実況
デモでは示さなかったが、Collectionバッファーではすべてのembark-actアクションが利用できる。embark-collect-direct-action-minor-modeをチューニングすれば、(最初にembark-actを呼び出さなくとも)直接呼び出すことさえ可能だ。
dired-bufferへのファイル候補のエクスポート
入手までに紆余曲折を経た、保管しておきたいファイルリストをお持ちでは? ファイルのリストを作成するために作られたdired、作成されたファイルリストへの配慮はembark-exportに任せよう:
ビデオ実況
“リスティング”に基づいたこれとは別の機能であるfind-name-diredも過去のものとなった。
ibufferへのバッファー候補のエクスポート
バッファーのリストなら何でもibufferにエクスポートできる(そう来ると思った、と言われそう)。
ビデオ実況
これは変数の検索から、必要に応じて関連アイテムへの本格的なaproposへと遷移するための非常に優れた手段として使用できる。
grepバッファーへのgrepや行の候補のエクスポート
occurのような結果(consult-lineやgrep、xrefなどの出力)であればgrepバッファーにエクスポートできる。
ビデオ実況
これは通常のgrepバッファーであることに注意してほしい。つまりgrepバッファーを編集して変更のすべてをファイルに書き戻す、wgrepのようなあなたのいつものテクニックのすべてが使用可能なのだ。
ボーナス: EmbarkのアクションをHelmのように使う
これまでの例では利用可能なembarkアクションは、そのフレームのどこかのウィンドウに表示されていた。Embarkにはプリセットされたアクションをリストする”プロンプター(prompter)”が複数あり、少し手間をかければHelmと似たような何かをセットアップできる2:
ビデオ実況
デモではC-<tab>で、(helmのように)アクションリストや補完リスト間を行ったり来たり切り替えている。アクションリストではアクションをタイプするか(マッチにはcompleting-readを使用)、@を前置してキーバインディングで直接アクションを呼び出すことができる。
手間の部分:
(defun with-minibuffer-keymap (keymap)
(lambda (fn &rest args)
(minibuffer-with-setup-hook
(lambda ()
(use-local-map
(make-composed-keymap keymap (current-local-map))))
(apply fn args))))
(defvar embark-completing-read-prompter-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "<tab>") 'abort-recursive-edit)
map))
(advice-add 'embark-completing-read-prompter :around
(with-minibuffer-keymap embark-completing-read-prompter-map))
(define-key vertico-map (kbd "<tab>") 'embark-act-with-completing-read)
(defun embark-act-with-completing-read (&optional arg)
(interactive "P")
(let* ((embark-prompter 'embark-completing-read-prompter)
(embark-indicators '(embark-minimal-indicator)))
(embark-act arg)))
上記例のvertico-mapについては、お好みの補完システムのミニバッファー用のアクティブキーマップに置き換えてほしい。デフォルトはminibuffer-local-completion-mapだ。
Embark使えばアクションに制限がないことを忘れないでほしい。Helmとは違うのだ! embark-actを実行した後にキーバインディングやM-xで必要に応じてコマンドそのものを呼び出すことができるのだ。
33%
これが”Embarkの例のやつ”と評される便利な15の機能だが、embark-becomeについてはまだ触れていない。embark-prefix-help-map、embark-which-key-prompter、Embarkのターゲットやターゲット巡回、他にも半ダースに及ぶEmbarkの気の利いた機能や魅力についても同様。でもそれは次の機会にでも。
かわりといってはなんだが、デモで使用した主なパッケージに触れて結びとさせてもらおう。
- Omar Antolin Camarenaのembark。彼と交流するのは楽しかったし、機能にたいするわたしのリクエストにもこころよく応えてくれた。embarkキーマップへのアクション追加やEmbarkをカスタマイズするなら、READMEはとても読みやすく非常に有益なので、熟読することをお勧めする。
- Emacsビルトインにたいするさまざまな機能強化についてはこちらをconsult(参照)してほしい。ファイルのオープンにはconsult-locateconsult-find(実際はconsult-fd)、グルーピングつきのカラフルなimenuにはconsult-imenu、ディレクトリーを横断するgrepにはconsult-ripgrepがある。
ミニバッファーの注釈にはmarginaliaを使用した。Omar AntolinとDaniel Mendlerにより共同で保守されている。
- ミニバッファーの補完インターフェイスとしてはverticoを使用した Consult、Vertico、MarginaliaはすべてDaniel Mendlerによって開発された。リストには含めなかったCorfu以外にもこれだけのパッケージだ、彼はきっと眠らないに違いない。
- 補完スタイルはorderlessを使用した。これも開発者はOmar Antolinで、テキストをミニバッファーの補完候補と個別にマッチするために使用している。これら5つのパッケージを組み合わせることによって形成されるMOVECペンタグラム(訳注:MOVECはMarginalia、Orderless、Vertico、、Embark、Consultの頭文字、ペンタグラムは五芒星で魔術的な意味をもつ5つの頂点をもつ星印)は、緩やかにバインドされたEmacsパッケージ全体を、現代的で密に結合するための構成可能な拡張パッケージコレクションである。
- 素早いディレクトリー移動にはconsult-dirを使用した。上述した例において、ミニバッファーの中から遠くのディレクトリーに移動する際に何度か使用している。
- popperはembark-collectやヘルプ、その他の一時的なバッファーがスクリーン上に表示される際に使用している(訳注: 開発は著者さん)。
- abo-aboが開発したace-windowを使用している。Ace-WindowとAvyのdispatch-keyのアイデアについては、速攻でpopperにパクらせていただいた。わたしの理解が正しければ、彼のIvy-Occurは初期のEmbark-Collectにも影響を与えているはずだ。
- William Vaughnの0x0は、自分で思った以上に頻繁に使用している。
最後にDoom Emacsユーザー向け注意事項を簡潔に記す: Doomには素の状態でEmbarkが同梱されている(2019年9月現在)ので、embark-actとembark-collectのキーを調べる以外何も行う必要はない。
これらの例が示唆する事実にも関わらず、おそらくわたしはEmbarkの能力の3分の1も使用できていないのだろう。たとえそうであったとしても、いつでもアクションの変更したり連鎖させられるので、経験と勘によるEmacsの操縦を可能にしてくれるのだ。2つ目の予期せぬ利点として、わたしが決して使うことがなかったであろうコマンドやリストを、抵抗なく利用できるようになることだ。transpose-regionsやapply-macro-to-region-linesのようなコマンド、あるいはdired、ibuffer、package-menuのような機能を使わなければアクセスできなかったカスタマイズされたリストをスムーズに利用できるようになった3。このようなバッファーを素早く作成できるので、diredやibufferを使う方法を知ることによって、数倍の利益が得られるようになる。これらのような機能とミニバッファーとの対話やテキストリージョンをシームレスに構成する際に、Emacsに組み込まれた無数のコマンドやライブラリーのパワーを増幅するレバーとして振る舞うのがEmbarkなのである。