-
embark.elのバージョン1.1のREADME.orgの日本語訳です
-
元の文書: https://github.com/oantolin/embark/blob/master/README.org
-
ライセンス: パッケージのライセンスと同じ、すなわちGPLv3.0ライセンスです
Table of Contents
概説
ミニバッファーでの補完セッション、あるいは通常のバッファーにおいてポイント付近に何があるかに応じて、実行するコマンドを簡単に選べるようにするのがEmbarkだ(ある意味HelmやCounselのユーザーにとってはお馴染みの方法かもしれない)。何かのキーにコマンドembark-act
をバインドしよう。これはポイント周辺の target(ターゲット)に関係のあるaction(アクション; コマンドのこと) のキーマップにたいして、プレフィックスキーのように振る舞うのだ。バッファーのURL上にポイントがあればブラウザやewwでオープンしたり、そのURLが指すファイルをダウンロードできるだろう。バッファーの切り替え中に古いバッファーを見つけたら、その場で古いバッファーをkillして(訳注: こバッファーを閉じるkill-buffer
というコマンドがある)、別のバッファーを探す作業を継続できるのだ。Embarkにはファイル、バッファー、識別子、S式、センテンスといった一般的なターゲット用に100以上のアクションが事前に定義されている。Embarkはミニバッファーのすべての候補をoccur風のバッファーに収集(collect)することもできる。候補のタイプに特化したメジャーモードのバッファーにエクスポートすることも可能だ。ファイルのセット(set: 集合)ならdired、バッファーセットならibuffer、変数セットの場合にはcustomizeといったように、そのターゲット用のメジャーモードのバッファーにターゲットをエクスポートできるという訳だ。
ターゲットへのアクション
右クリックで表示されるコンテキストメニューという機能があるが、embark-act
はそれのキーボード版と考えることができる。コンテキストによりターゲットが決定される。そのターゲットに関係のあるアクションを提供するキーマップがあり、そのキーマップのプレフィックスキーとして動作するのがembark-act
コマンドだ(使いやすいキーにバインドしよう)。
- ミニバッファーではカレントでトップにある補完候補がターゲット
-
*Completions*
バッファーでのターゲットはポイント位置にある補完 - 通常のバッファーではリージョンがアクティブならリージョン、それ以外の場合にはポイント位置にあるファイル、シンボル、URL、S式、defunがターゲット
同じ場所に複数のターゲットが存在する場合には、embark-act
にバインドしたキーを繰り返し押下することで、それらのターゲット間を巡回できる。提案されるアクションのタイプはターゲットのタイプ次第だ。デフォルトのコンフィグにおいて提案されるアクションの例をいくつか挙げてみよう:
-
ファイルにたいして提案されるアクションは削除、コピー、リネーム、別ウィンドウでのvisit、そのファイルへのシェルコマンド実行等
-
バッファーの場合にはバッファーへの切り替え、バッファーのkill等のアクション
-
パッケージ名ならインストール、削除、あるいはホームページへのvisit等のアクション
-
Emacs Lispのシンボルにたいするアクションは定義の検索、ドキュメント照会、評価(変数なら値を即座に表示、関数ならまず渡す引数を指定できる)など
変数特有のアクション(直接値をセット、カスタマイズシステム経由でセット等)もあれば、コマンド特有のアクション(キーへのバインディング等)もある
デフォルトではembark-act
を使用する際にすぐにアクションが選択されなければ、短い遅延の後にアクションとそのアクションのキーバインディングのリストを表示するバッファーをEmbarkがポップアップする。ミニバッファーの外部でembark-act
を使用していれば、Embarkがカレントターゲットのハイライトも行うだろう。これらの振る舞いは変数embark-indicators
で設定可能だ。embark-act
の後にC-h
をタイプすれば、キーバインディングでアクションを選択するかわりに補完つきでアクションの名前を選択することもできる。
カレントターゲットの決定、クラス分け、そのクラス分けしたタイプそれぞれにたいして提案するアクションの判断など、すべてのことが簡単に設定できるようになっている。上記の例はデフォルトのコンフィグの一部を記したに過ぎないのだ。
あるタイプにどのアクションを提案するかについての設定は特に簡単であり、プログラミングはまったく必要ない。変数embark-keymap-alist
にはターゲットのタイプにたいして、変数(キーマップを含んでいる)が関連付けられている。そのキーマップにアクション用のバインディングが含まれているのだ(利用可能なカテゴリーと関連付けられたキーマップを調べるにはC-h v embark-keymap-alist
、あるいはcustomize機能でこの変数を調べることができる)。たとえばデフォルトのコンフィグではタイプfile
にはシンボルembark-file-map
が関連付けされている。このシンボルが命名しているのは、一文字のキーをEmacsの一般的なファイルコマンドにバインディング(たとえばc
はcopy-file
)するキーマップだ。find-file
やrename-file
のようなファイルの入力を求めるコマンドを実行、その後にembark-act
を実行してc
を押下すればファイルをコピーすることができることを意味している。
これらのアクション用キーマップは非常に便利ではあるが、embark-act
を使用する際には必ずしも必要という訳ではない。何であれミニバッファーから読み取るコマンドならアクションとして使用できるし、最初のミニバッファーのプロンプトにはそのアクションのターゲットが挿入されるからだ。embark-act
の実行後にはすべてのキーバインディング、execute-extended-command
でさえコマンドの実行に使用できる。たとえばポイント位置にあるシンボルの出現箇所すべてを置換したい場合には、単にアクションとしてM-%
を使うだけでよい。query-replace
をEmbarkのいずれかのキーマップでバインドする必要はないのである。その上これらのアクション用のキーマップはEmacsの普通のキーマップと同じなのだ。つまりあなたが便利だと思ったコマンドなら何であれ、使いやすいキーにバインドすることを躊躇う理由はない筈だ。
どのようなタイプの補完中であっても、embark-general-map
に定義されているアクションは利用可能である。このマップにはカレント候補をkillリングに保存するためのバインディングと、以前に選択したバッファー(ミニバッファーをオープンするコマンドを実行した時点でカレントだったバッファーのこと)にカレント候補を挿入するためのバインディングがデフォルトで含まれている。
Emacsのミニバッファーの補完システムには、どのカテゴリーを補完しているかを示すメタデータが含まれている。たとえばfind-file
はfile
のカテゴリー、switch-to-buffer
はbuffer
のカテゴリーを示すメタデータが含まれているのだ。Embarkにはアクションの対象となるターゲットのタイプに関する概念があり、メタデータでカテゴリーが与えられた場合には、ミニバッファーの補完候補がターゲットとなった際のタイプと同じように解釈される。Emacsには有益なカテゴリーをメタデータにセットしないコマンドが多いのだが、この欠落したメタデータを提供するパッケージがMarginaliaだ。Embarkと一緒に使用することを強く推奨する。
Embarkのデフォルトのコンフィグにはファイル、バッファー、シンボル、パッケージ、URL、ブックマークといったターゲットタイプ用のアクション、更に特殊ケース向けとしてリージョンがアクティブなとき用のアクションが定義されている。GitHubのプロジェクトwikiにあるdefault actions and their key bindingsに目を通すのもよいだろう。
ターゲットへのデフォルトアクション
Embarkにはターゲット用のデフォルトアクションという概念がある:
-
ターゲットがミニバッファーの補完候補の場合には、そもそもミニバッファーをオープンする発端となったコマンドがデフォルトアクション。たとえば
kill-buffer
ならデフォルトのアクションはバッファーのkill。 -
ターゲットが通常バッファー由来(ミニバッファーではない)の場合のデフォルトアクションは、そのターゲットのタイプにたいするキーマップで
RET
にバインドされているアクション。たとえばEmbarkののデフォルトの設定では、ポイント位置にあるURLにたいするデフォルトアクションはbrowse-url
になる。これはキーマップembark-url-map
でRET
がbrowse-url
にバインドされているから。
デフォルトアクションはembark-act
を実行した後にRET
を押下すれば実行できる。与えられた位置に複数の異なるターゲット(それぞれ独自のデフォルトアクションがある)が存在する場合には、目標とするターゲットまで巡回してそれからRET
を押下すれば、そのターゲットに応じたデフォルトアクションが実行されるだろう。
最初に見つけたターゲットにデフォルトアクションを実行するembark-dwim
もある。これはミニバッファー以外のバッファーでは非常に役に立つと思う。Embarkのデフォルトのコンフィグでは以下のようにデフォルトアクションを実行するのだ:
-
ポイント位置のファイルをオープン
-
ポイント位置のURLをウェブブラウザでオープン(
browse-url
を使用) -
ポイント位置のメールアドレスで電子メールを新たに作成
-
ポイントが開きカッコか閉じカッコの直後にあればその式を評価(Emacs Lispバッファーの場合)
-
ポイント位置のEmacs Lispの関数、変数、マクロの定義にジャンプ
-
ポイント位置のEmacs Lispライブラリーに相当するファイルを検索
アクション可能なターゲット集合にたいする処理
Embarkはターゲットに個別にアクションする場合を除いて、ターゲットの候補セットを固まりとして処理できるようになっている。たとえばミニバッファーにいるときの候補とは、単に入力から補完可能な候補だろう。Emacsには候補セットの処理用に主に3つのコマンドを提供している:
-
embark-act-all
はカレント候補それぞれにたいして同じアクションを実行するコマンド。候補ごとに順番にembark-act
を行うのと同じ(意図したより多くの候補に簡単にアクションを実行できてしまうので、デフォルトではembark-act-all
を使用する際にはEmbarkが確認を求めるようになっているが、ユーザーオプションembark-confirm-act-all
をnil
にすれば確認をオフに切り替えられる)。 -
embark-collect
は都合のいいタイミングで吟味したアクションを実行できるように、カレント候補すべてをリストしたバッファーを生成するコマンド。候補は追加の注釈を伴うリストとして表示される。いずれかの候補に改行が含まれる場合には、候補を区切るための水平線が使用される。EmbarkのCollectバッファーは幾分"dired的"なバッファーだ。候補の選択と選択解除は
embark-select
を通じて行う(embark-act
ではSPC
にバインドされているアクションだがグローバルなキーバインディングを付与してもよいだろう)。EmbarkのCollectバッファーではembark-act
はa
、embark-act-all
はA
にバインドされている。embark-act-all
はもしあればカレントでマークされている候補、何もマークされていなければすべての候補にたいしてアクションを行う。これはつまりa SPC
はポイント位置の候補が選択されているかどうかの切り替え、A SPC
は候補が何も選択されていなければすべての候補を選択、選択された候補がある場合には選択された候補の選択を解除することを意味している。 -
embark-export
は候補セットにたいして適切なメジャーモードでバッファーのオープンを試みるコマンド。ファイルが候補ならDiredバッファー、バッファーならIbufferのバッファー、パッケージの場合にはpackage-menu-mode
のバッファーにエクスポートされるだろう。Consultパッケージの
consult-grep
、consult-git-grep
、consult-ripgrep
といったgrep コマンドを使っているのであれば、embark-consult
パッケージをインストールするべきだろう。これはgrepの結果リストをgrep-modeのバッファーにエクスポートするためのサポートを追加する。なにせ由緒正しいgrep-modeのバッファーだから、お望みならwgrepの使用さえ可能なのだ。
エクスポートと収集のいずれを選ぶか迷ったときには、常にembark-export
を選ぶというのが失敗のない経験則だろう。与えられたタイプのターゲット向けの特別なメジャーモードへのエクスポートが利用可能ならEmbarkのCollectバッファーより機能豊富だろうし、そのようなエクスポートが設定されていなければembark-export
は汎用的なembark-collect
にフォールバックするからである。
これらのコマンドは常にembark-act
の"アクション"として利用でき(たとえカレントターゲットにアクションを行えなくても、すべての候補にアクションすることは可能だから)、embark-general-map
でそれぞれA
、S
("Snapshot"の"S")、E
にバインドされている。これはつまり必要なのはembark-act
にたいするキーバインディングだけであり、これらのコマンドにたいして独自にキーをバインドする必要がないことを意味している(もちろんバインドしても問題ない!)
EmbarkのCollectバッファーとEmbarkのExportバッファーはミニバッファーの補完セッションからembark-collect
かembark-export
を実行することで取得できる。補完セッションを再開するコマンド、すなわちミニバッファーをオープンしたコマンドはg
にバインドされており、それを実行することによってミニバッファーのコンテンツをリストアするのだ。その後は通常通りそのコマンドと対話(多分ミニバッファーのコンテンツの編集とか)して、もしお望みならembark-collect
やembark-export
を再実行して更新されたバッファーの入手もできるだろう。
即興の候補セット作成のためのターゲット選択
前述のように候補セットにたいして処理を行うコマンド、すなわちembark-act-all
、embark-export
、embark-collect
は、デフォルトではカレントコンテキストで定義されたすべての候補に機能する。たとえばミニバッファーにおいてはカレントで補完された候補すべて、diredバッファーであればマークされているすべてのファイル(マークされたファイルがなければすべてのファイル)にたいして処理を行う。Embarkには セレクション(selection: 選択されたもの) という概念もある。セレクションにはこれらのコマンドが処理するターゲットの即興リストを蓄積することができるのだ。
セレクションはembark-select
(embark-general-map
でSPC
にバインドされている)というアクションで制御できるので、いつでも利用することができる(もしお望みならembark-select
にグローバルなキーバインディングを与えることもできる; embark-act
にたいするアクションではなく直接呼び出された際には、ポイント位置にある最初のターゲットを選択することになる)。このアクションをターゲットにたいして呼び出すことにより、カレントバッファーのEmbarkセレクションにおけるそのターゲットのメンバーシップを切り替えることができる。つまり選択されていなければセレクションに追加、すでに選択されていればセレクションから削除するということだ。あるバッファーにたいするセレクションが空でなければコマンドembark-act-all
、embark-export
、embark-collect
は常にそのセレクションにたいして処理を行うことになる。
embark-act-all
を通してembark-select
を使用すれば、選択されたすべてのターゲットの選択を解除することができる。これによりカレントセレクションのメンバーそれぞれにたいしてembark-select
が実行されるからだ。同様にしてミニバッファーの補完セッション中、何もターゲットが選択されていない状態からembark-act-all
経由でembark-select
を実行することにより、カレントの補完候補すべてを選択できる。
カレントバッファーで何かターゲットが選択されていれば、デフォルトではモードラインに選択されているターゲットの個数が表示される。これはユーザーオプションembark-selection-indicator
をカスタマイズしてオフに切り替えることができる。
セレクションの機能はすべてのバッファーでサポートされている:
-
これはミニバッファーにおいて、シンプルなパターンにしたがわない複数の補完候補にアクションを行う、便利な手段を提供する。選択したいターゲットを補完で絞り込んでから
embark-act-all
を使用すれば、たとえば複数のファイルを一度でメールに添付することができるだろう。 -
この機能をEmbarkのCollectバッファーで使えば、さまざまな候補にマークをつけてからすべての候補に一度にアクションを適用するという、dired風のワークフローが有効になる(少しインターフェイスは異なるが、これはEmbarkのCollectバッファーでのみ実装されていた前述のdired風の即興リストを置き換えるワークフローである)。
-
これをewwバッファーで使用すれば、辿りたいさまざまなリンクを選択してバッファーに収集することができる。同様にしてEmacsのinfoマニュアルを読みながら、更にマニュアルを調べたいシンボルをいくつか選択、それを
apropos-mode
のバッファーにエクスポートすればよいだろう。 -
通常のテキストやプログラミング用のバッファーで複雑な編集操作を行う際にセレクションを使用できる。たとえば1つファイルに散在する3つのパラグラフを1つにまとめたい場合には、パラグラフそれぞれを選択してからすべてを他の場所に挿入、最後に(元の場所にある)パラグラフをすべて削除すればよいだろう。
embark-live: embark-collectのライブアップデート版
最後にembark-collect
の変種としてembark-live
というコマンドも存在する。これはコレクションを収集した後にソースバッファーが変更されると、収集したコレクションを自動的に更新するコマンドだ。候補リストを自動的に更新、表示するような(Vertico、Icomplete、Fidoモード、MCTのような)補完UIのユーザーであれば、補完候補のライブ更新と表示を二重に所有することになるので、ミニバッファーからembark-live
を使いたいと思うことは多分ないとは思うが!
より相応しいのは通常のバッファーからembark-live
を呼び出して、そのバッファーの"目次"をライブ更新で表示するような使い方だろう。これはembark-candidate-collectors
に適切な候補コレクターが設定されているかどうかに依存する。Embarkのデフォルトのコンフィグにはあまり存在しないが、次のような実験を試すのはどうだろう。非常に大量のファイルがあるディレクトリーをdiredでオープンして、いくつかファイルをマークしてからembark-live
を実行するのだ。これによりマークしたファイルだけを含むEmbarkのCollectバッファーが入手できた。これはdiredでのファイルのマーク、マーク解除に応じて更新されるバッファーだ。正真正銘役に立つembark-live
にするためには、別の候補コレクターが必要になる。(このマニュアルの終わりで触れる)embark-consult
パッケージにもいくつか含まれている。1つはimenuアイテム、もう1つはoutline-minor-mode
で使用するようなアウトラインのヘディングだ。実際にembark-live
に目次のようなフィーリングを与えてくれるのは、これらのコレクターが行うこと次第といえるだろう。
無駄なタイプなしで違うコマンドに切り替える
Embarkにはembark-become
というコマンドもある。これは何かコマンドを実行してミニバッファーでタイプし始めてから、実は違うコマンドを実行したかったのに気づいたというような状況で役に立つコマンドだ。わたしの場合もっとも一般的なのはswitch-to-buffer
してバッファー名のタイプを始めた後に、念頭にあるファイルをまだオープンしていないことに気がついたといったケースだ! この状況をembark-become
を説明するための実行例として用いることにしよう。このような事態が発生したとしても、C-g
を押下してからfind-file
を実行してファイルをオープンできる。当然だ。しかしこれにはすでに部分的にタイプしたファイル名を、もう一度タイプする必要があるだろう。このプロセスはembark-become
で無駄を減らすことができる。まだswitch-to-buffer
の実行中にembark-become
を実行すれば、この実行におけるswitch-to-buffer
をfind-file
コマンドに効率的に転生させることができるだろう。
minibuffer-local-map
でembark-become
を何かしらのキーにバインドすることも可能だが、これはB
(大文字)にバインドされたアクションとしても利用できるようにしてあるので、すでにembark-act
にキーをバインドしてあれば必要ないだろう。たとえばembark-act
をC-.
にバインドしてあるとすると、ファイルをまだオープンしていないことに気がついたらC-. B C-x C-f
とタイプすることで、すでにミニバッファーでタイプしたものを失うことなくswitch-to-buffer
をfind-file
に転生させることができるのだ。
もっと利便性を向上させよう。カレントコマンドから転生させたいコマンドと比較して、より短いキーバインディングをembark-become
に提案してもらうのだ。embark-become
はembark-become-keymaps
のリストに載っているすべてのキーマップからカレントコマンドを探して、そのコマンドを含んだキーマップをすべてアクティブにする。たとえばembark-become-keymaps
のデフォルト値にはキーマップembark-become-file+buffer-map
が含まれている。これはファイルとバッファー関連の複数のコマンドにたいするバインディングを定義しているが、特に注目すべきはswitch-to-buffer
をb
、find-file
をf
にバインドしている点だ。つまり勘違いしてまだオープンしていないファイルのバッファーへ切り替えようとしたとき、embark-become
は実行したswitch-to-buffer
をembark-become-file+buffer-map
で発見してこのキーマップ(およびembark-become-file+buffer-map
のバインディングを含む他のキーマップ)をアクティブにする。最終的にはC-. B f
とタイプすればfind-file
に切り替えられるという結果を得られるだろう。
クイックスタート
Embarkをもっとも簡単にインストールできるのはGNU ELPAからインストールする方法だろう。単にM-x package-install RET embark RET
を実行するだけだ(MELPAも利用可)。Marginaliaのインストールも強く推奨する(これもGNU ELPAでインストール可)。Embarkがより多くのコンテキストで事前に定義されたアクションを提案できるようになるからだ。use-package
のユーザーは、使い始めは以下のようなコンフィグが妥当だろう:
(use-package marginalia
:ensure t
:config
(marginalia-mode))
(use-package embark
:ensure t
:bind
(("C-." . embark-act) ;; 何か使いやすいバインディングを選ぶべし
("C-;" . embark-dwim) ;; M-.の代替えとして適切なキーを選ぶべし
("C-h B" . embark-bindings)) ;; `describe-bindings'の代役
:init
;; completing-readインターフェイスでキーヘルプを置き換え(オプション)
(setq prefix-help-command #'embark-prefix-help-command)
;; Eldocを通じてポイント位置のEmbarkターゲットを表示する
;; 複数プロバイダドキュメントを参照したければEldocストラテジの調節が必要かもしれない
;; これを使用すると少し目障りに感じるかもしれないことに注意
;; ミニバッファーに表示されるメッセージが2行以上になるとモードラインが上下に移動するので
;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target)
;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)
:config
;; Embarkのliveバッファーと補完バッファーでモードラインを非表示にする
(add-to-list 'display-buffer-alist
'("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
nil
(window-parameters (mode-line-format . none)))))
;; Consultユーザーにはembark-consultパッケージもよいだろう
(use-package embark-consult
:ensure t ; EmbarkがConsultが見つけたらロードするのでインストールに必要なだけ
:hook
(embark-collect-mode . consult-preview-at-point-mode))
embark-act
とembark-dwim
に提案したキーバインディングについて:
-
これらのキーバインディングは端末では機能しないと思うが、端末のユーザーならすぐ気づくだろうし別のバインディングを選択すべきことも知っているだろう。
-
提案した
C-.
というバインディングは、(少なくともいくつかのバージョンの)GNOMEではデフォルトでemoji入力に使用されているので、Emacsにはこのバインディングに応答する機会さえ巡ってこない。embark-act
用に別のキーバインディングを選択するか、あるいはibus-setup
を使用してemoji挿入のショートカットを変更できるだろう(Emacs 29ならC-x 8 e e
を使えばシステム全体で同じバインディングを使用できる筈だ)。 -
M-.
の代替えとしてembark-dwim
に提案したキーバインディングは、デフォルトではxref-find-definitions
にバインドされている。これは非常に役に立つコマンドではあるがembark-dwim
で上書きするのは理に適っている。Embarkのデフォルトのコンフィグであれば、embark-dwim
でポイント位置の識別子の検索も行われるからだ(xref-find-definitions
の場合にはプレフィックス引数を指定すると識別子の入力を求めるが、このケースはembark-dwim
ではカバーされていないことに注意)。
embark-act-all
、embark-become
、embark-collect
、embark-export
といった他のEmbarkコマンドはembark-act
の実行を通じたアクションとしてそれぞれA
、B
、S
("Snapshot"より)、E
にバインドされているので、実際には重複したキーバインディングは不要だが、お望みなら直接バインドすることを躊躇う理由はない。直接バインドすることを選択した場合には、minibuffer-local-map
でバインドしたいと思うのではないだろうか。これらのコマンドがもっとも役に立つ場所がミニバッファーだからだ(実際のところembark-become
はミニバッファーでしか機能しない)。
embark-dwim
はポイント位置でデフォルトアクションを実行するコマンドだ。embark-dwim
のキーバインディングとして他に適しているのはM-.
だろう。embark-dwim
はポイント位置のシンボルにたいしてxref-find-definitions
のようなアクションを行うからだ。C-.
は右クリックで表示されるコンテキストメニュー、M-.
は左クリックのように動作するとみなすことができる。このキーバインディングはどちらもポイント(.
)に作用するという点に着目した語呂合わせである。
Embarkはあなたのミニバッファー補完システムが何を候補リストとみなすのか、どの候補がカレント候補なのかを知っておく必要がある。Emacsのデフォルトのタブ補完、ビルトインのicomplete-mode
やfido-mode
、サードパーティ製パッケージのVertico or Ivyを使っていれば、インストールするだけでEmbarkを使用することができる筈だ。
HelmやIvyにはミニバッファーの補完候補に作用するための包括的な機能が含まれているので、これらのパッケージのユーザーがEmbarkを望むことは無さそうだ(にも関わらずEmbarkにはIvyとの統合が含まれている)。
高度な設定
利用可能なターゲットとアクションの情報表示
embark-act
を実行した後にすぐにアクションを選択しないと、デフォルトでは短い遅延を挟んでからEmbarkが*Embark Actions*
という名前のバッファーをポップアップする。これは利用できるアクションとそのアクションのキーバインディングからなるリストを表示するバッファーだ。このバッファーは通常のscroll-other-window
とscroll-other-window-down
のコマンドでマウスでスクロールできる(デフォルトではそれぞれC-M-v
とC-M-S-v
にバインドされている筈だ)。
これはembark-mixed-indicator
が提供する機能だが、Embarkにはターゲットとターゲットのタイプ、カレントターゲットのタイプ用のアクションマップでキーバインディングをもっているアクションはどれかといった情報を提供するために、別のインジケーターが存在する。一度に任意の個数のインジケーターをアクティブにできる。これを使用するには使用したいインジケーターのリストをユーザーオプションembark-indicators
にセットする必要がある。
Embarkに同梱されているインジケーターは以下の通りだ:
-
embark-minimal-indicator
: カレントターゲットを起点にすべてのターゲットとそのタイプを記したメッセージを、エコーエリアまたはミニバッファーのプロンプトに表示する。 -
embark-highlight-indicator
: ポイント位置のターゲットをハイライト(デフォルトでオン)。 -
embark-verbose-indicator
: バッファーにアクションとそのキーバインディングのテーブルを表示する。次に説明するミックスされたインジケーターの方を優先したため、デフォルトでオンにはなっていない。 -
embark-mixed-indicator
: 最初は最小限のインジケーターとして振る舞うが、短い遅延を挟んだ後はverbose(冗長、情報が多い)なインジケーターとして振る舞う(デフォルトでオン)。 -
embark-isearch-highlight-indicator
: ポイント位置のシンボルがカレントターゲットのときだけ何かを行うインジケーター。この場合だとIsearchのように、カレントバッファーでそのシンボルのすべての出現箇所を遅延ハイライトする(これもデフォルトでオン)。
人気パッケージのwhich-keyのユーザーは、Embark wikiのembark-which-key-indicator
の方が好みに合うかもしれない。このwikiからあなたのコンフィグに定義をコピーしたら、mixedとverboseのインジケーターを除外してembark-which-key-indicator
を含めるように、ユーザーオプションembark-indicators
をカスタマイズするだけでよい筈だ。
Verticoを使っているのであれば、選択肢リストを補完で絞り込んでwhich-key
風に表示する更に簡単な方法がある。次セクションの最後に説明しよう。
キーバインディングではなく補完によるコマンド選択
verboseやmixedといったインジケーター(これらについては前セクションを参照)にリストされたアクションを読むかわりに、embark-act
の実行後にembark-help-key
(デフォルトではC-h
)を押下するという方法もある(C-h
はプレフィックスとして使用できるように自由にしておいて、かわりに?
を使う方が気に入るかもしれない)。ヘルプキーを押下することにより、補完付きでアクション名の入力を求めるプロンプトが表示されるだろう(提案された候補にないコマンドを入力しても構わない!)。更にキーバインディングのリマインド機能もついている。プロンプトでembark-keymap-prompter-key
(デフォルトでは@
)、その後に名前を入力したアクションに相当するキーバインディングを押下すればよいのだ。
*Embark Actions*
バッファーがポップアップしてキーバインディングを教えてくれるので、アクション名の選択に補完を使いたいと思う訳がないと考えるかもしれない。しかしわたしの個人的な経験が教えてくれたのは、アクションのリスト全体を視覚的にスキャンするよりも、候補リストを絞り込むためにアクション名のほんの一部をタイプする方が断然速く感じられるということだ。
アクションの選択としてこの方法が気に入ったのなら、変数embark-prompter
にembark-completing-read-prompter
をセットすれば、常にアクションの入力を求めるようにEmbarkを設定できる。
その一方でもっとも頻繁に行うアクションにはキーバインディングを使い続けて、利用可能なアクションを更に探すときやキーバインディングを忘れてしまった場合のみ補完を使いたい場合もあるだろう。このような場合には*Embark Actions*
バッファーをポップアップしないminimalなインジケーターを使い、ヘルプが必要ならいつでもembark-help-key
を使える方が気に入るかもしれない。この地味なセットアップは以下の設定で実現できる筈だ:
(setq embark-indicators
'(embark-minimal-indicator ; デフォルトはembark-mixed-indicator
embark-highlight-indicator
embark-isearch-highlight-indicator))
Verticoユーザーであれば、人気パッケージwhich-keyを想起させるが、もちろん補完によるコマンドリストの絞り込みによって強化されている、アクションとキーバインディング用のグリッド表示を設定したいと思うかもしれない。グリッド表示を手に入れるためには、以下をVerticoのコンフィグに記述すればよいだろう:
(add-to-list 'vertico-multiform-categories '(embark-keybinding grid))
(vertico-multiform-mode)
これで利用可能なキーがwhich-key
のようなコンパクトなグリッドに表示されるだろう。vertico-multiform-mode
ではM-V
、M-G
、M-B
、M-U
といったキーが利用できる。これはVerticoバッファーのレイアウト間を手動で切り替えるコマンドだ。
Embark外部の補完でコマンド選択
このEmbarkアクションのキーバインディングを探す補完インターフェイスが気に入ったならば、Emacsのどこでもこれを使いたいのではないだろうか。Embarkの補完ベースのコマンドプロンプターをリストにした:
-
プレフィックス配下のキーバインディング
-
ローカルなキーバインディング
-
キーバインディングすべて
プレフィックス配下のキーバインディングを使用するのであれば、以下のコンフィグを使うとよいだろう:
(setq prefix-help-command #'embark-prefix-help-command)
こうしておけばC-x
やC-c
といったプレフィックスシーケンスで開始したときには、C-h
を押下することでビルトインのprefix-help-command
のEmbark版が立ち上がる筈だ。これにはそのプレフィックスの配下にあるキーがリストされていて、目当てのコマンドを補完で選択したり、embark-keymap-prompter-key
を押下すればキーバインディングで選択できるようになっている。
ローカルのキーバインディングやグローバルなキーバインディングをリストする場合には、コマンドembark-bindings
を使用できる。このコマンドをC-h b
にバインドすれば、これはビルトインのdescribe-bindings
にたいするデフォルトのキーバインディングなので、コマンドの置き換えが完了だ。デフォルトではembark-bindings
がリストするのはローカルのキーバインディングであり、典型的にはメジャーモードのキーマップによってバインドされたキーが該当するだろう。プレフィックス引数C-u
とともに呼び出せば、同じようにしてグローバルなキーバインディングを得ることができるだろう。
アクション後にミニバッファーを閉じる
ミニバッファーからembark-act
を呼び出すと、デフォルトではアクションを実行した後にミニバッファーは閉じられるようになっている。これはユーザーオプションembark-quit-after-action
をnil
にセットすることで変更可能だ。embark-act
がミニバッファーを閉じないようにすることで、コマンドを小さな"thing manager"に変身させる役に立つかもしれない。たとえば小さなファイルマネージャーとしてfind-file
、小さなパッケージマネージャーとしてdescribe-package
といったコマンドを実行して、一連のアクションを行い、そのコマンドを終了するといった使い方ができるだろう。
コマンドごとにt
(閉じる)またはnil
(閉じない)を関連付けるalistをembark-quit-after-action
にセットすれば、アクションに応じてよりきめ細かいやり方で閉じる際の挙動を制御できる。alistを使用する際には、デフォルトの挙動を指定するための特別なキーとしてt
を使うことができる。たとえばデフォルトアクションではミニバッファーを閉じないが、アクションとしてkill-buffer
を使用したときには閉じるような指定には、以下のコンフィグを使用できるだろう:
(setq embark-quit-after-action '((kill-buffer . t) (t . nil)))
変数embark-quit-after-action
に指定できるのはデフォルトだけである。つまりプレフィックス引数なしでembark-act
を呼び出したときに、ミニバッファーを閉じるか否かの制御だけを行うのだ。ただしプレフィックス引数C-u
とともにembark-act
を呼び出した場合には、この変数の指定とは反対の挙動を選択できる仕組みにしてある。ミニバッファーの外部でembark-act
を呼び出したときには、変数embark-quit-after-action
とプレフィックス引数C-u
のどちらとも効果がないことに注意して欲しい。
何のアクションかに関係なく閉じるバージョンのembark-act
と閉じない変種バージョンの使用頻度が同程度であるようなら、1つのコマンドを2回に1回はC-u
付きで呼び出すやり方よりも、シンプルに別のコマンドを定義する方が好みに合うかもしれない。たとえば以下のようにすれば、デフォルトでは閉じるembark-act
の挙動を維持しつつ、閉じないバージョンを定義できるだろう:
(defun embark-act-noquit ()
"アクションを実行した後にミニバッファーを閉じない"
(interactive)
(let ((embark-quit-after-action nil))
(embark-act)))
ターゲット挿入後のセットアップ実行
あるアクションのミニバッファープロンプトにターゲットを挿入した後に何が発生するかはカスタマイズ可能だ。ミニバッファーへターゲットを差し込んだ後にデフォルトで実行されるのがembark-target-injection-hooks
である(inject: 注射、導入、差し挟む)。embark-target-injection-hooks
はコマンドとコマンドのセットアップのフックを関連付けるalistを格納する変数だ。特別なキーが2つある。あるアクションにセットアップフックが指定されていない場合にはt
に関連付けられたフックが実行される、もう1つアクションとは関係なく常に実行されるのが:always
に関連付けられたフックである(この変数の以前の少し曖昧なembark-setup-action-hooks
は変更されたので、コンフィグを更新して欲しい)。
たとえばファイル補完中のアクションとしてshell-command
を使用する場合を考えてみよう。ポイントをプロンプトの先頭に残したままターゲットのファイル名の前にスペースが挿入してあれば、そのファイルに実行するシェルコマンドをすぐにタイプできるので役に立つのではないだろうか。Embarkのembark-target-injection-hooks
のデフォルト設定では、shell-command
に関連付けられたフックにembark--shell-prep
(ファイル名に含まれるすべてのスペースをクォートしてその行の左端にポイントを残したまま行頭に余分なスペースを挿入するシンプルなヘルパー関数)を含んだエントリーがに存在するのは、これが理由である。
そうなると今度はアクションのターゲットをミニバッファープロンプトに挿入後にEmbarkが通常行うこと、つまり"RET
の押下"でそのターゲットを受け入れるという動作をEmbarkが行った場合には、embark--shell-prep
が計らった準備は役立たずになってしまうだろう。shell-command
にたいしてEmbarkがこれを行った場合には、実行するコマンドをあなたがタイプする機会は巡ってこないのだ! これがEmbarkのデフォルト設定でembark-target-injection-hooks
のshell-command
にたいするエントリーに、関数embark--allow-edit
が含まれている理由だ。
以前のEmbarkにはembark-allow-edit-actions
という変数があった。これはターゲットを挿入した後に、EmbarkにRET
を押下させないコマンドを追加するための専用の変数だった。この効果は汎用的なメカニズムであるembark-target-injection-hooks
を通じて実現できたこともあり、この変数はEmbarkを簡素化するために削除された。以下のような設定をしていたら忘れずに更新して欲しい:
(add-to-list 'embark-allow-edit-actions 'my-command)
以下のように書き換える必要がある:
(push 'embark--allow-edit
(alist-get 'my-command embark-target-injection-hooks))
同意を要するdelete-file
のような"危険(dangerous)"なアクションに、embark--allow-edit
を悪用することもできるが別のフック、具体的にはembark-pre-action-hooks
の適切なエントリーにembark--confirm
関数を追加して同意を求める処理を実装する方がよい。
embark--allow-edit
とは別に、Embarkにはアクションのセットアップフックにおける汎用的なユーティリティとして、embark--ignore-target
という別の関数が同梱されている。これはターゲットの挿入には適していないが、ミニバッファーでプロンプトに入力を求めるコマンドに使用できるだろう。一般的な状況では発生しないだろうが、ときどき発生することはあるだろう。shell-command-on-region
にたいするデフォルトとして使用されているのがその一例だ。このコマンドはリージョンターゲットのアクションとして使用されており、シェルコマンドの入力を求めるのだ。このケースではターゲットはリージョンのコンテンツになるので、普通はプロンプトに入力して欲しいとは思わない筈だ!
アクションの前、後、前後でのフック実行
Embarkにはembark-pre-action-hooks
、embark-post-action-hooks
、embark-around-action-hooks
という3つの変数がある。これはコマンド、アクションとして使用した際にそのコマンドの前、後、あるいは前後のどのタイミングでフックを実行するべきかを関連付けるalistだ。embark-target-injection-hooks
のときと同じように、alist用に2つの特別なキーが存在する。あるコマンドにたいして特に指定されたフックが存在しない場合に実行するデフォルトのフックを指定するキーがt
、関連付けられたフックを無条件で実行するキーが:always
である。
これらの変数のデフォルト値は非常に広範に渡るので、快適さを盛り込むことができれば、スムーズなアクションの実行体験を得られるだろう。。Embarkにはこれらのフックへの追加を意図した関数が複数同梱されておりembark-pre-action-hooks
、embark-post-action-hooks
、embark-around-action-hooks
のデフォルト値として使用されている。
pre-actionフック用:
<!-- - **`embark--beginning-of-target`:** Move to the beginning of the target (for targets that report bounds). This is used by default for backward motion commands such as `backward-sexp`, so that they don't accidentally leave you on the current target. -->
-
embark--confirm
: アクション実行の前にユーザーに確認を求める。これは"危険"とみなされているコマンド、もっと正確にいうとdelete-file
やkill-buffer
といったアンドゥ(undo)によって取り消すのが困難なコマンド用のデフォルトに使用されている。 -
embark--unmark-target
: アクティブなリージョンのマークを解除(unmark)する。リージョンをアクティブにせずにそのコンテンツにアクションさせたいコマンドに使用する。デフォルトのコンフィグではoccur
およびquery-replace
にたいするpre-actionフックとしてこの関数を使用している。たとえばそのリージョンに含まれるテキストをバッファー全体から検索するために、リージョンのターゲットへのアクションとしてこれらの関数を使用するのだ。このpre-actionフックを使わずにリージョンのターゲットへのアクションとしてoccur
を使うのは無意味だ。そのリージョンの中でそのリージョンのコンテンツを検索しても、(典型的にはregexpの詳細により)見つかるマッチは1つだけだろう! -
embark--beginning-of-target
: (境界を報告するターゲットにたいして)ターゲットの先頭に移動する。これはbackward-sexp
のような後方への移動コマンドのデフォルトとして使用されている。これを使えば間違ってカレントターゲット上に取り残される恐れはないだろう。 -
embark--end-of-target
: ターゲットの終端に移動する。上記関数と同じように使用されるが、eval-last-sexp
のような最後のS式(s-expressionsexpとも略される)にアクションするコマンドにも使用される。これによりS式内のどこからでもS式にアクションできるし、依然としてアクションとしてeval-last-sexp
を使うことができるだろう。 -
embark--xref-push-markers
: カレントの位置をxrefマーカースタックにpushする。あなたをどこかへ連れて行った後に、xref-pop-marker-stack
を使った場所に戻れるようなコマンドにたいして使用する。これはfind-library
にたいしてデフォルトで使用されている。
post-actionフック用:
-
embark--restart
: 補完候補のリストを更新するために、ミニバッファーでカレントでプロンプトを表示しているコマンドをリスタートする。これは補完候補の削除やリネームを行うコマンドのpost-actionフックとして役に立つだろう。たとえばembark-post-action-hooks
のデフォルト値ではdelete-file
、kill-buffer
、rename-file
、rename-buffer
等にたいしてこの関数を使用している。
around-actionフック用:
-
embark--mark-target
: 既存のマークとポイントの位置を保存してから、ターゲットをマークしてアクションを実行する。ミニバッファー外部のポイント位置にあるターゲットのほとんどは、ターゲットがそれぞれバッファーのどのリージョンに対応するかを報告するようになっている(この情報はembark-highlight-indicator
がバッファーのどの部分をハイライトするかを得るために使用している)。これはそのリージョンをマークするための関数だ。リージョンがマークされることを期待するコマンドのaround-actionフックとして役に立つだろう。たとえばこれはデフォルトでは、S式のターゲットに機能するためにembark-highlight-indicator
が使用しているし、パラグラフのターゲットに機能するためにfill-region
が使用している。 -
embark--cd
: カレントターゲットに関連付けられたディレクトリーをdefault-directory
にセットしてアクションを実行する。ターゲットのタイプはfile
、buffer
、bookmark
、library
のいずれかであること。関連付けられたディレクトリーには、それぞれのターゲットにたいしてあなたが期待するディレクトリーをセットする。 -
embark--narrow-to-target
: カレントターゲットにナローイングしたバッファーにアクションを実行する。リージョンに限定した動作をまだ行っていないアクションの影響を局所化するために、around-actionフックとして使用する。デフォルトのコンフィグではrepunctuate-sentences
に使用されている。 -
embark--save-excursion
: アクションを実行して最後にポイントをリストアする。カレントのデフォルトコンフィグでは使用していないが、ユーザーは利用可能。
独自キーマップの設定
内部的なキーマップは、すべて標準のヘルパーマクロdefvar-keymap
で定義されている。たとえばファイル用のアクションキーマップの簡素化バージョンは、以下のように定義できるだろう:
(defvar-keymap embark-file-map
:doc "ファイル用アクションをいくつか定義したキーマップ例"
:parent embark-general-map
"d" #'delete-file
"r" #'rename-file
"c" #'copy-file)
これらのアクションキーマップは、正真正銘Emacsの通常のキーマップである。デフォルトのEmbarkアクションにアクセスする場合には、embark-general-map
から継承したキーマップを使いたいと思うかもしれない。embark-general-map
経由でembark-collect
とembark-export
も利用可能になることに注意。
ターゲットの新たなカテゴリーにアクションを定義する
ミニバッファーあるいはミニバッファー外部の新たなターゲットのタイプにたいして、アクションを提供するようにEmbarkを設定するのは容易だ。これをどのように行うかについて、非常に詳細な例を2つ用意した。いくつかの存在する分岐点には、選べる複数の選択肢についても(簡単な方法順に)説明しようと思っている。似たような状況であっても、もっとも容易なオプションが利用できない場合もあるので、代替えのオプションも含めて説明するつもりだ。
ミニバッファーの新たなターゲットの例 - tab-barバー
例としてtab barsを覗いてみよう。名前でタブを指定するtab-bar-mode
の使用時にはタブ固有のアクションを提案させるために、どのようにEmbarkを設定するかについて説明しよう。ここで説明するコンフィグは今ではEmbark(とMarginalia)に同梱されているが、自己完結型の設定例としての優秀度は以前と変わらない。タブアクションをセットアップするためには: (1) Embarkがタブを扱うコマンドを確かに知っていること、および(2) タブアクション用キーマップの定義、どのキーマップを提案するべきかをEmbarkが理解できるように設定する必要がある。
-
タブの名前の入力を求めるコマンドをEmbarkに指示する
ステップ(1) 補完付きでタブを尋ねる際に
tab-bar-mode
コマンドが補完カテゴリーとしてtab
を報告してくれたら素晴らしいだろう(たとえばEmacsでファイル名の入力を求めるビルトインコマンドは、コマンドが期待しているのがfile
であることを示すメタデータをもっている)。残念ながらタブの場合は報告してくれないので、これに対処する方法についていくつか説明してみるとしよう。これらのタブコマンドをもっとも簡単に強化できるのは、おそらくMarginaliaを設定する方法だろう。
tab-bar-*-tab-by-name
のようなコマンドのミニバッファー用プロンプトには、すべて"tab by name"という単語が含まれているので、以下を使うことができる:(add-to-list 'marginalia-prompt-categories '("tab by name" . tab))
これでOKだ! しかしこの例では、あなたが期待するターゲット用のプロンプトを備えたコマンドが、まだ存在しない状況に対処できるように、適切な
category
のメタデータを使って独自のコマンドを記述する方法について説明しよう:(defun my-select-tab-by-name (tab) (interactive (list (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) (tab-bar-tabs)) (user-error "No tabs found")))) (completing-read "Tabs: " (lambda (string predicate action) (if (eq action 'metadata) '(metadata (category . tab)) (complete-with-action action tab-list string predicate))))))) (tab-bar-select-tab-by-name tab))
ご覧いただいた通りカテゴリーのメタデータをセットするためのビルトインサポートは非常に使いやすいとはいえないし、見た目も可愛くない。これの助勢としてお勧めなのが、Consultという素晴らしいパッケージが提供する
consult--read
関数だ。この関数を使えば、コマンドを以下のように書き直すことができるだろう:(defun my-select-tab-by-name (tab) (interactive (list (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) (tab-bar-tabs)) (user-error "No tabs found")))) (consult--read tab-list :prompt "Tabs: " :category 'tab)))) (tab-bar-select-tab-by-name tab))
よくなった!
my-select-tab-by-name
コマンドをどのように定義するかは関係なく、Marginaliaを使った最初のアプローチとプロンプト検出には、一度でtab-bar-*-bar-by-name
コマンドすべてのtab
カテゴリーを入手でき、その上新たにコマンドを定義するのではなくビルトインコマンドの強化も実現できるというアドバンテージがあるのだ。
-
タブアクション用キーマップの定義と設定
(無料で手に入るkillリングへのタ名の保存のようなEmbarkの一般的なアクションに加えて)タブレットにたいする選択、リネーム、閉じるアクションを提供したいとする。それは以下のようになるだろう:
(defvar-keymap embark-tab-actions :doc "tab-barのタブ用のアクションのキーマップ(名前でタブ指定の場合)" :parent embark-general-map "s" #'tab-bar-select-tab-by-name "r" #'tab-bar-rename-tab-by-name "k" #'tab-bar-close-tab-by-name) (add-to-list 'embark-keymap-alist '(tab . embark-tab-actions))
これをしばらく使用した後に、同意の確認なしでタブを閉じるのは危険だと感じたらどうしよう? いくつかオプションが考えられる:
-
tab-bar-close-tab-by-name
コマンドの使用は継続、ただしEmbarkに同意を求めさせる:(push #'embark--confirm (alist-get 'tab-bar-close-tab-by-name embark-pre-action-hooks))
-
同意を求めるプロンプトをもったコマンドを独自に記述、上記キーマップで
tab-bar-close-tab-by-name
のかわりに使用する:(defun my-confirm-close-tab-by-name (tab) (interactive "sTab to close: ") (when (y-or-n-p (format "Close tab '%s'? " tab)) (tab-bar-close-tab-by-name tab)))
これはEmbarkに依存せずに
M-x
から直接使うこともできるコマンドだという点に注目して欲しい。M-x
から私用した場合にはタブ名の補完が得られないので、幾分物足りなく感じるのではないだろうか。
-
お望みならこれは前のセクションで説明した通り修正すればよいだろう。
通常バッファーにおける新たなターゲットの例 - Wikipediaへのショートリンク
任意の通常バッファーにおいてwikipedia:Garry_Kasparov
という形式のテキストをWikipediaへのリンクとして扱うこと、このターゲットにはリンク先のWikipediaページをewwや外部ブラウザでオープンしたり、あるいはそのページのURLをkillリングに保存するというアクションが用意されていることをEmbarkに教え込みたいとしよう。EmbarkにはURL用のアクションが事前定義されているというアドバンテージがあるので、わたしたちに必要なのはwikipedia:Garry_Kasparov
がhttps://en.wikipedia.org/wiki/Garry_Kasparov
というURLを意味していることをEmbarkに教え込むだけだ。
認識する必要がある構文次第では、好きなだけ趣向を凝らすことも可能だ。ここでは例をシンプルに保つために、これはwikipedia:[[:alnum:]_]+
というregexpにマッチするようなリンクだと仮定する。ポイントを取り囲むマッチを探して、'(url URL-OF-THE-PAGE START . END)
という形式のドットリストをリターンする関数を記述することにしよう。ここでターゲットの境界を示すバッファー位置のSTART
およびEND
は、(リストembark-indicators
にembark-highlight-indicator
が含まれている場合には)Embarkがハイライトに使用するだろう(ターゲットファインダーのリターン値にたいするオプションは、境界位置はオプション、1つのターゲットファインダーが複数のターゲットをリターン可といったのように他にもいくつか存在する。詳細についてはembark-target-finders
のドキュメントを参照のこと)。
(defun my-short-wikipedia-link ()
"ターゲットはポイント位置にあるwikipedia:Page_Nameおいう形式のリンク"
(save-excursion
(let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point)))
(end (progn (skip-chars-forward "[:alnum:]_:") (point)))
(str (buffer-substring-no-properties start end)))
(save-match-data
(when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str)
`(url
,(format "https://en.wikipedia.org/wiki/%s"
(match-string 1 str))
,start . ,end))))))
(add-to-list 'embark-target-finders 'my-short-wikipedia-link)
Embarkはどうやってアクションを呼び出すのか?
Embarkのアクションとはすなわち普通のEmacsのコマンドである。つまりinteractive仕様をもつ関数のことだ(訳注: interactiveすなわち対話的という意味もあるが、Emacsには本来は対話的ではない関数を対話的にするinteractiveというスペシャルフォームがある)。あるアクションを実行するためには、Embarkがそのコマンドをcall-interactively
で呼び出す必要がある。そうすればコマンドはあたかもユーザーが直接呼び出したかのように、ユーザーの入力を読み取ることができるのだ。たとえばミニバッファーをオープンして文字列読み取ったり(read-from-minibuffer
)、補完インターフェイスをオープンする(completing-read
)といったことが可能になる。これが行われるとEmbarkがターゲット文字列を受け取って、ユーザー入力を扱う場合と同じようにそれを自動的にミニバッファーへと挿入する。文字列を挿入した後はEmbarkがミニバッファーを抜けることで入力の完了だ(特定のアクションでは入力の編集を可能にするためにミニバッファーの即時脱出を無効にできる; これはembark-target-injection-hooks
の適切なエントリーにembark--allow-edit
関数を追加することで行われる)。Embarkはアクションのコマンドがミニバッファーを最初にオープンしたタイミングでターゲット文字列を挿入する。そのコマンドが2回目のプロンプトでユーザー入力を求めた場合には、2回目以降のプロンプトとも通常のやり方で依然として対話できるだろう。ミニバッファーでユーザーにプロンプトを表示して入力を求めないコマンドでも、そのコマンドのアクションとしての使用をEmbarkが禁ずることはなうが、ターゲットをどこかに挿入することはできなくなる(デフォルト設定のアクションマップにはユーザーにプロンプトで入力を求めないコマンド例が大量に存在するが、それはたとえばほとんどがリージョン用のアクションだ)。
これがEmbarkが普通のコマンドをアクションとして管理する方法である。このメカニズムによってEmbarkを念頭に記述された訳ではないコマンドでも、Embarkのアクションコマンドとして使用することが可能なのだ(そして確かにEmbarkのアクションキーマップでデフォルトでバインドされているほとんどのアクションは、Emacsの標準コマンドである)。これは更にEmbark抜きでも役に立つような方法による、新たなカスタムアクションの記述の後押しにもなると思う。
Emacsにはy-or-n-p-use-read-key
という変数がある。これがt
にセットされていると、y-or-n-p
はread-from-minibuffer
のかわりにread-key
を使用するのだ。Embarkユーザーにはy-or-n-p-use-read-key
をt
にセットするようお勧めする。y-or-n-p
のプロンプトにたいしてターゲットを挿入する動作をEmbarkに強いてもほとんど意味がないからだ。これは独自にアクションコマンドを構築する場合の教訓と考えることもできるだろう。そのコマンドがy-or-n-p
を使用させるのは、ターゲットにプロンプトを使わせた後だけにしよう。
上述したユーザーからの入力を読み取るさまざまな例を説明するためのシンプルな例を用意した。アクションとして使用できるように以下のコマンドをembark-symbol-map
にバインドしてから、何かシンボルにポイントを置いてembark-act
を実行してみて欲しい:
(defun example-action-command1 ()
(interactive)
(message "入力は `%s'." (read-from-minibuffer "Input: ")))
(defun example-action-command2 (arg input1 input2)
(interactive "P\nsInput 1: \nsInput 2: ")
(message "1つ目の入力は %swas `%s'、2つ目は `%s'."
(if arg "ガチで " "")
input1
input2))
(defun example-action-command3 ()
(interactive)
(message "あなたの選択は `%s'."
(completing-read "Select: " '("E" "M" "B" "A" "R" "K"))))
(defun example-action-command4 ()
(interactive)
(message "入力のプロンプト出してない故にターゲット無視!"))
(keymap-set embark-symbol-map "X 1" #'example-action-command1)
(keymap-set embark-symbol-map "X 2" #'example-action-command2)
(keymap-set embark-symbol-map "X 3" #'example-action-command3)
(keymap-set embark-symbol-map "X 4" #'example-action-command4)
アクションの呼び出しにキーバインディングを使い場合には、通常の方法でアクションにプレフィックス引数を渡せることにも注意して欲しい。たとえば上記のexample-action-command2
はC-u X2
と呼び出せばより強調されたメッセージをプリントするアクションをデモンストレーションしている。アクションにプレフィックス引数を渡せる能力は、デフォルトの設定にあるembark-shell-command-on-buffer
.のような一部のアクションにとって役に立つだろう。
非interactiveな関数からアクションへ
Embarkがサポートしているアクションのタイプはもう1つある。1つの引数を受け取る非interactiveな関数だ。ターゲットは関数に引数として渡される。たとえば:
(defun example-action-function (target)
(message "The target was `%s'." target))
(keymap-set embark-symbol-map "X 4" #'example-action-function)
通常だとキーマップで非インタラクティブな関数をバインドしても、実行しようとしても"Wrong type argument: commandp, example-action-function"というエラーメッセージが表示されるだけで使い物にならないことに注意。一般的に新たに何かEmbarkアクションを記述する場合にはコマンド、つまりinteractiveな関数として記述する方がより柔軟性がある。これならEmbarkを介さずに直接実行することもできるからだ。しかし非interactiveな関数をアクションとして使用するのにもいくつか利点があるのだ:
-
関数ならその辺に常に転がっているし、単にそれを再利用する方が簡単なので
-
コマンドのアクションのターゲットはテキストプロパティがないシンプルな文字列だけだが、テキストプロパティをもった文字列、何なら文字列以外のターゲットを受け取るようなアクションが欲しいという具体的で技術的な理由のため
Embark、MarginaliaとConsult
EmbarkがMarginalia、Consultの2つのパッケージと相性よく協力して動作する。Embarkはいずれのパッケージにたいしても依存関係をもっていない。むしろいずれのパッケージも連れ沿いとして強く推奨するべきパッケージである。EmbarkはConsultの強化の助けとなり、MarginaliaはEmbarkの有用性を飛躍的に向上させるだろう。
このセクションの残りの部分でMarginaliaが正確には何をEmbarkにしてくれるのか、そしてEmbarkがConsultにできることを説明しよう。
Marginalia
Embarkにはシンボルのためのembark-symbol-map
(コマンド、関数、変数にたいして定義検索、ドキュメント照会、評価等を行うアクション)、パッケージ用にはembark-package-keymap
(インストール、削除、URL閲覧等を行うアクション)というキーマップが同梱されている。
これらのキーマップに関係のあるアクションであっても、残念ながらEmbarkがそれらを自動的に提案することはない。これはEmacsのビルトインコマンドの多くが正確なカテゴリーのメタデータを報告しないからだ。たとえばミニバッファーからパッケージの名前を読み取るdescribe-package
のようなコマンドは、この事実を示すようなメタデータをもっていない。
この欠落したメタデータを提供する関数はEmbarkの古いバージョンには存在したが、これらはMarginaliaに移動された。Emacsの多くのコマンドが正確なカテゴリーメタデータを報告するように強化するのがMarginaliaだ。単にmarginalia-mode
をアクティブにするだけで、前のようにシンボルやパッケージにたいするアクションを適切なときにEmbarkが提案できるようにしてくれる。Marginaliaパッケージは他にもEmbarkのCollectバッファーの候補用に注釈も提供する。
-
Marginaliaをインストールして
marginalia-mode
をアクティブにすれば、EmbarkのCollectバッファーが自動的にMarginaliaの注釈を使用する -
Marginaliaをインストールしないで目にできるのはEmacsに同梱の注釈だけ(
M-x
のキーバインディングやC-x 8 RET
のUnicode文字とか
Consult
Consultはcompleting-read
関数を介してミニバッファーで使用できる多くのコマンドを提供する素晴らしいパッケージだ。提供するコマンドの大部分はEmacsのビルトインコマンドの強化されたバージョンであり、一部のコマンドはまったく新しい機能をもっている。すべてのコマンドが提供する一般的な強化機能としてはプレビューという納得の機能が挙げられるだろう。たとえばconsult-buffer
は実際に切り替える前にバッファーのクイックプレビューを表示してくれるのだ。
ConsultとEmbarkの両方を使うのであれば、両者間を統合するembark-consult
をインストールしよう。これはいくつかのConsultコマンドをエクスポートするエクスポーター、そしてアクションとしてembark-act
を使用した際のスムーズなエクスペリエンスのために多くのConsultコマンドの振る舞いの(気づけさえしないような)微調整も提供してくれるのだ。これらのご利益は得るために必要なのはインストールだけである。Consultを見つけたらEmbarkが自動的にロードを行う。
embark-consult
によって提供されるエクスポーターは以下の通り:
-
consult-line
、consult-outline
、consult-mark
からoccur-mode
のバッファーを得るためのembark-export
ビルトインのoccur
コマンドのバッファーで行うようにマッチにジャンプしたら、その後はnext-error
とprevious-error
で他のマッチに移動できるだろう。e
を押下してoccur-edit-mode
をアクティブにすれば、その場でマッチの編集も可能なのだ! -
Consultの
consult-grep
、consult-git-grep
、consult-ripgrep
といった非同期検索のコマンドいずれからでもgrep-mode
バッファーにエクスポートできる
ここでもnext-error
とprevious-error
でマッチの間を移動できるのは同じだ。更にもしwgrepパッケージをインストールしてあれば、その場でマッチを編集できるだろう
どちらもg
を押下すれば元のエクスポートしたConsultコマンドに戻って、再度入力を行うことができる(revertと似ているがこちらの方がより柔軟だ)。もしお望みなら再度エクスポートすることもできるが、入力を編集して検索する言葉を変更したり、その検索で満足したら単にキャンセルすればよいだろう。
embark-consult
には候補コレクター(candidate collector)もいくつか含まれている。候補コレクターを使えばバッファーのコンテンツのライブアップデートを得ることができるのだ。
-
embark-consult-outline-candidates
はconsult-outline
を使用してバッファーコンテンツのアウトラインヘッダーを生成する -
embark-consult-imenu-candidates
はconsult-imenu
を使用してConsultバッファーのimenuアイテムを生成する -
上記の2つの関数をシンプルに組み合わせたのが
embark-consult-imenu-or-outline-candidates
で、prog-mode
から派生したバッファーならimenuアイテム、それ以外のバッファーにたいしてはアウトラインのヘッダーを生成する
これらの関数をembark-live
(あるいはembark-collect
やembark-export
の場合でも)で使用するには、embark-candidate-collectors
リストの最後に関数を追加すればよい。embark-consult
パッケージははデフォルトでは、最後に追加したものがもっとも賢明なデフォルトとみなされるからだ。
これらのエクスポーターや候補コレクター以外にもembark-consult
パッケージではEmbarkとConsultの間の精密な微調整、小さな統合がたくさん提供されている。いくつか例を挙げておこう:
-
非同期検索をアクションとして使用した場合には、ターゲットに関連のあるファイルだけを検索する:
ターゲットがファイルならそれらのファイル、バッファーの場合にはバッファーに割り当てられたファイルがあればファイルから、なければそのバッファーの
default-directory
にあるすべてのファイルから、ブックマークならリンクが指すファイルから検索する(Emacs Lispライブラリーにたいしても同様)これはたとえば
consult-find
でnamesという名前のファイルを検索、embark-act-all
でconsult-grep
すればマッチしたファイルを検索といったように、embark-act-all
で一度に複数のファイルにアクションする際には特に強力な機能だ -
関連付けられたファイルがない、あるいは取り扱う際のセオリーが存在しないその他のターゲットについては、(非同期かどうかに関係なく)ターゲットのテキストをConsultは検索するが、Consultコマンドと対話できるようにミニバッファーをオープンしたままにしておく
-
consult-imenu
はターゲットを検索して一意なimenuエントリーにマッチしたら直接その場所に移動、そのようなエントリーがなければマッチ間を行き来できるようにミニバッファーをオープンしたままにしておく
関連パッケージ
Embarkに似た機能を提供するパッケージもいくつか存在する。
-
ミニバッファーの補完候補にアクション: 有名なIvyとHelmというパッケージでも補完候補にアクションするコマンド(それぞれ独自のAPIで記述されている)がサポートされており、HelmやIvyを意識したパッケージ(Ivy用は普通は名前に"counsel"がある)からなる大規模なエコシステムがコマンドや相応のアクションを提供している
-
ポイント位置にアクション: ビルトインの
context-menu-mode
がマウス駆動で文脈依存、コンフィグ可能なメニューを提供しているし、キーボード駆動方式ならPhilip Kaludercic氏が提供するdo-at-point
パッケージがある(GNU ELPAで入手可) -
補完候補をバッファーに収集: IvyにはIvyのAPIで記述された
ivy-occur
というembark-collect
と似たコマンドがある
リソース
他の人たちがどのようにEmbarkを使っているか興味のある人のために、目を通すべきリンクをいくつか挙げておこう
-
Fifteen ways to use Embark: Karthik Chikmagalur氏によるブログ投稿だ
-
Protesilaos Stavrou氏のdotemacs: "Extended minibuffer actions and more (embark.el and prot-embark.el)"というセクションを調べてみよう
お勧めのビデオもいくつかある:
-
Embark and my extras: Protesilaos Stavrou氏による
-
Embark – Key features and tweaks: Raoul Comninos氏のYouTubeチャンネルEmacs-Elements
-
Livestreamed: Adding an Embark context action to send a stream message: Sacha Chua氏による
-
System Crafters Live! - The Many Uses of Embark: David Wilson氏による
-
Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark: Mike Zamansky氏による
貢献してくれる人たちへ
Embarkに貢献してくれる人は大歓迎だ。アクション、ターゲット、ファインダー、候補コレクター、エクスポーター用のwish listがある。Embarkについて他にもアイデアがあれば、遠慮なくissue trackerでissueをオープンしていただきたい。何かイカしたコンフィグのテクニックを提供する場としては、wikiがピッタリだろう。
コードへの貢献も大歓迎だがEmbarkは現在GNU ELPAに所属しているので、コードを貢献する前にFSFへの著作権譲渡が必要になるだろう。
謝辞
Embarkのコードのほとんどを記述し、かつ一部のデザイン上の決定に頑迷に拘り続けているのはわたくし、Omar Antolín Camarenaだが、他の人たちからの実のある助言について、わたしはあまりに長い間この文書に記すのを怠っていた。特にDaniel Mendlerは重要ないくつかの機能の実装、大量の有益なアイデアの価値は計り知れない。
コードに貢献してくれた人たち:
- Daniel Mendler
- Clemens Radermacher
- José Antonio Ortega Ruiz
- Itai Y. Efrat
- a13
- jakanakaevangeli
- mihakam
- Brian Leung
- Karthik Chikmagalur
- Roshan Shariff
- condy0919
- Damien Cassou
- JimDBh
アドバイスや有益な議論で貢献してくれた人たち: