2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

EmbarkのREADME.org

Last updated at Posted at 2025-02-21

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の一般的なファイルコマンドにバインディング(たとえばccopy-file)するキーマップだ。find-filerename-fileのようなファイルの入力を求めるコマンドを実行、その後にembark-actを実行してcを押下すればファイルをコピーすることができることを意味している。

これらのアクション用キーマップは非常に便利ではあるが、embark-actを使用する際には必ずしも必要という訳ではない。何であれミニバッファーから読み取るコマンドならアクションとして使用できるし、最初のミニバッファーのプロンプトにはそのアクションのターゲットが挿入されるからだ。embark-actの実行後にはすべてのキーバインディング、execute-extended-commandでさえコマンドの実行に使用できる。たとえばポイント位置にあるシンボルの出現箇所すべてを置換したい場合には、単にアクションとしてM-%を使うだけでよい。query-replaceをEmbarkのいずれかのキーマップでバインドする必要はないのである。その上これらのアクション用のキーマップはEmacsの普通のキーマップと同じなのだ。つまりあなたが便利だと思ったコマンドなら何であれ、使いやすいキーにバインドすることを躊躇う理由はない筈だ。

どのようなタイプの補完中であっても、embark-general-mapに定義されているアクションは利用可能である。このマップにはカレント候補をkillリングに保存するためのバインディングと、以前に選択したバッファー(ミニバッファーをオープンするコマンドを実行した時点でカレントだったバッファーのこと)にカレント候補を挿入するためのバインディングがデフォルトで含まれている。

Emacsのミニバッファーの補完システムには、どのカテゴリーを補完しているかを示すメタデータが含まれている。たとえばfind-filefileのカテゴリー、switch-to-bufferbufferのカテゴリーを示すメタデータが含まれているのだ。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-mapRETbrowse-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-allnilにすれば確認をオフに切り替えられる)。

  • embark-collectは都合のいいタイミングで吟味したアクションを実行できるように、カレント候補すべてをリストしたバッファーを生成するコマンド。候補は追加の注釈を伴うリストとして表示される。いずれかの候補に改行が含まれる場合には、候補を区切るための水平線が使用される。

    EmbarkのCollectバッファーは幾分"dired的"なバッファーだ。候補の選択と選択解除はembark-selectを通じて行う(embark-actではSPCにバインドされているアクションだがグローバルなキーバインディングを付与してもよいだろう)。EmbarkのCollectバッファーではembark-actaembark-act-allAにバインドされている。embark-act-allはもしあればカレントでマークされている候補、何もマークされていなければすべての候補にたいしてアクションを行う。これはつまりa SPCはポイント位置の候補が選択されているかどうかの切り替え、A SPCは候補が何も選択されていなければすべての候補を選択、選択された候補がある場合には選択された候補の選択を解除することを意味している。

  • embark-exportは候補セットにたいして適切なメジャーモードでバッファーのオープンを試みるコマンド。ファイルが候補ならDiredバッファー、バッファーならIbufferのバッファー、パッケージの場合にはpackage-menu-modeのバッファーにエクスポートされるだろう。

    Consultパッケージのconsult-grepconsult-git-grepconsult-ripgrepといったgrep コマンドを使っているのであれば、embark-consultパッケージをインストールするべきだろう。これはgrepの結果リストをgrep-modeのバッファーにエクスポートするためのサポートを追加する。なにせ由緒正しいgrep-modeのバッファーだから、お望みならwgrepの使用さえ可能なのだ。

エクスポートと収集のいずれを選ぶか迷ったときには、常にembark-exportを選ぶというのが失敗のない経験則だろう。与えられたタイプのターゲット向けの特別なメジャーモードへのエクスポートが利用可能ならEmbarkのCollectバッファーより機能豊富だろうし、そのようなエクスポートが設定されていなければembark-exportは汎用的なembark-collectにフォールバックするからである。

これらのコマンドは常にembark-actの"アクション"として利用でき(たとえカレントターゲットにアクションを行えなくても、すべての候補にアクションすることは可能だから)、embark-general-mapでそれぞれAS("Snapshot"の"S")、Eにバインドされている。これはつまり必要なのはembark-actにたいするキーバインディングだけであり、これらのコマンドにたいして独自にキーをバインドする必要がないことを意味している(もちろんバインドしても問題ない!)

EmbarkのCollectバッファーとEmbarkのExportバッファーはミニバッファーの補完セッションからembark-collectembark-exportを実行することで取得できる。補完セッションを再開するコマンド、すなわちミニバッファーをオープンしたコマンドはgにバインドされており、それを実行することによってミニバッファーのコンテンツをリストアするのだ。その後は通常通りそのコマンドと対話(多分ミニバッファーのコンテンツの編集とか)して、もしお望みならembark-collectembark-exportを再実行して更新されたバッファーの入手もできるだろう。

即興の候補セット作成のためのターゲット選択

前述のように候補セットにたいして処理を行うコマンド、すなわちembark-act-allembark-exportembark-collectは、デフォルトではカレントコンテキストで定義されたすべての候補に機能する。たとえばミニバッファーにおいてはカレントで補完された候補すべて、diredバッファーであればマークされているすべてのファイル(マークされたファイルがなければすべてのファイル)にたいして処理を行う。Embarkには セレクション(selection: 選択されたもの) という概念もある。セレクションにはこれらのコマンドが処理するターゲットの即興リストを蓄積することができるのだ。

セレクションはembark-select(embark-general-mapSPCにバインドされている)というアクションで制御できるので、いつでも利用することができる(もしお望みならembark-selectにグローバルなキーバインディングを与えることもできる; embark-actにたいするアクションではなく直接呼び出された際には、ポイント位置にある最初のターゲットを選択することになる)。このアクションをターゲットにたいして呼び出すことにより、カレントバッファーのEmbarkセレクションにおけるそのターゲットのメンバーシップを切り替えることができる。つまり選択されていなければセレクションに追加、すでに選択されていればセレクションから削除するということだ。あるバッファーにたいするセレクションが空でなければコマンドembark-act-allembark-exportembark-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-bufferfind-fileコマンドに効率的に転生させることができるだろう。

minibuffer-local-mapembark-becomeを何かしらのキーにバインドすることも可能だが、これはB(大文字)にバインドされたアクションとしても利用できるようにしてあるので、すでにembark-actにキーをバインドしてあれば必要ないだろう。たとえばembark-actC-.にバインドしてあるとすると、ファイルをまだオープンしていないことに気がついたらC-. B C-x C-fとタイプすることで、すでにミニバッファーでタイプしたものを失うことなくswitch-to-bufferfind-fileに転生させることができるのだ。

もっと利便性を向上させよう。カレントコマンドから転生させたいコマンドと比較して、より短いキーバインディングをembark-becomeに提案してもらうのだ。embark-becomeembark-become-keymapsのリストに載っているすべてのキーマップからカレントコマンドを探して、そのコマンドを含んだキーマップをすべてアクティブにする。たとえばembark-become-keymapsのデフォルト値にはキーマップembark-become-file+buffer-mapが含まれている。これはファイルとバッファー関連の複数のコマンドにたいするバインディングを定義しているが、特に注目すべきはswitch-to-bufferbfind-filefにバインドしている点だ。つまり勘違いしてまだオープンしていないファイルのバッファーへ切り替えようとしたとき、embark-becomeは実行したswitch-to-bufferembark-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-actembark-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-allembark-becomeembark-collectembark-exportといった他のEmbarkコマンドはembark-actの実行を通じたアクションとしてそれぞれABS("Snapshot"より)、Eにバインドされているので、実際には重複したキーバインディングは不要だが、お望みなら直接バインドすることを躊躇う理由はない。直接バインドすることを選択した場合には、minibuffer-local-mapでバインドしたいと思うのではないだろうか。これらのコマンドがもっとも役に立つ場所がミニバッファーだからだ(実際のところembark-becomeはミニバッファーでしか機能しない)。

embark-dwimはポイント位置でデフォルトアクションを実行するコマンドだ。embark-dwimのキーバインディングとして他に適しているのはM-.だろう。embark-dwimはポイント位置のシンボルにたいしてxref-find-definitionsのようなアクションを行うからだ。C-.は右クリックで表示されるコンテキストメニュー、M-.は左クリックのように動作するとみなすことができる。このキーバインディングはどちらもポイント(.)に作用するという点に着目した語呂合わせである。

Embarkはあなたのミニバッファー補完システムが何を候補リストとみなすのか、どの候補がカレント候補なのかを知っておく必要がある。Emacsのデフォルトのタブ補完、ビルトインのicomplete-modefido-mode、サードパーティ製パッケージのVertico or Ivyを使っていれば、インストールするだけでEmbarkを使用することができる筈だ。

HelmIvyにはミニバッファーの補完候補に作用するための包括的な機能が含まれているので、これらのパッケージのユーザーがEmbarkを望むことは無さそうだ(にも関わらずEmbarkにはIvyとの統合が含まれている)。

高度な設定

利用可能なターゲットとアクションの情報表示

embark-actを実行した後にすぐにアクションを選択しないと、デフォルトでは短い遅延を挟んでからEmbarkが*Embark Actions*という名前のバッファーをポップアップする。これは利用できるアクションとそのアクションのキーバインディングからなるリストを表示するバッファーだ。このバッファーは通常のscroll-other-windowscroll-other-window-downのコマンドでマウスでスクロールできる(デフォルトではそれぞれC-M-vC-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 wikiembark-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-prompterembark-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-VM-GM-BM-Uといったキーが利用できる。これはVerticoバッファーのレイアウト間を手動で切り替えるコマンドだ。

Embark外部の補完でコマンド選択

このEmbarkアクションのキーバインディングを探す補完インターフェイスが気に入ったならば、Emacsのどこでもこれを使いたいのではないだろうか。Embarkの補完ベースのコマンドプロンプターをリストにした:

  • プレフィックス配下のキーバインディング

  • ローカルなキーバインディング

  • キーバインディングすべて

プレフィックス配下のキーバインディングを使用するのであれば、以下のコンフィグを使うとよいだろう:

    (setq prefix-help-command #'embark-prefix-help-command)

こうしておけばC-xC-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-actionnilにセットすることで変更可能だ。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-hooksshell-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-hooksembark-post-action-hooksembark-around-action-hooksという3つの変数がある。これはコマンド、アクションとして使用した際にそのコマンドの前、後、あるいは前後のどのタイミングでフックを実行するべきかを関連付けるalistだ。embark-target-injection-hooksのときと同じように、alist用に2つの特別なキーが存在する。あるコマンドにたいして特に指定されたフックが存在しない場合に実行するデフォルトのフックを指定するキーがt、関連付けられたフックを無条件で実行するキーが:alwaysである。

これらの変数のデフォルト値は非常に広範に渡るので、快適さを盛り込むことができれば、スムーズなアクションの実行体験を得られるだろう。。Embarkにはこれらのフックへの追加を意図した関数が複数同梱されておりembark-pre-action-hooksembark-post-action-hooksembark-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-filekill-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-filekill-bufferrename-filerename-buffer等にたいしてこの関数を使用している。

around-actionフック用:

  • embark--mark-target: 既存のマークとポイントの位置を保存してから、ターゲットをマークしてアクションを実行する。ミニバッファー外部のポイント位置にあるターゲットのほとんどは、ターゲットがそれぞれバッファーのどのリージョンに対応するかを報告するようになっている(この情報はembark-highlight-indicatorがバッファーのどの部分をハイライトするかを得るために使用している)。これはそのリージョンをマークするための関数だ。リージョンがマークされることを期待するコマンドのaround-actionフックとして役に立つだろう。たとえばこれはデフォルトでは、S式のターゲットに機能するためにembark-highlight-indicatorが使用しているし、パラグラフのターゲットに機能するためにfill-regionが使用している。

  • embark--cd: カレントターゲットに関連付けられたディレクトリーをdefault-directoryにセットしてアクションを実行する。ターゲットのタイプはfilebufferbookmarklibraryのいずれかであること。関連付けられたディレクトリーには、それぞれのターゲットにたいしてあなたが期待するディレクトリーをセットする。

  • 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-collectembark-exportも利用可能になることに注意。

ターゲットの新たなカテゴリーにアクションを定義する

ミニバッファーあるいはミニバッファー外部の新たなターゲットのタイプにたいして、アクションを提供するようにEmbarkを設定するのは容易だ。これをどのように行うかについて、非常に詳細な例を2つ用意した。いくつかの存在する分岐点には、選べる複数の選択肢についても(簡単な方法順に)説明しようと思っている。似たような状況であっても、もっとも容易なオプションが利用できない場合もあるので、代替えのオプションも含めて説明するつもりだ。

ミニバッファーの新たなターゲットの例 - tab-barバー

例としてtab barsを覗いてみよう。名前でタブを指定するtab-bar-modeの使用時にはタブ固有のアクションを提案させるために、どのようにEmbarkを設定するかについて説明しよう。ここで説明するコンフィグは今ではEmbark(とMarginalia)に同梱されているが、自己完結型の設定例としての優秀度は以前と変わらない。タブアクションをセットアップするためには: (1) Embarkがタブを扱うコマンドを確かに知っていること、および(2) タブアクション用キーマップの定義、どのキーマップを提案するべきかをEmbarkが理解できるように設定する必要がある。

  1. タブの名前の入力を求めるコマンドを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カテゴリーを入手でき、その上新たにコマンドを定義するのではなくビルトインコマンドの強化も実現できるというアドバンテージがあるのだ。

  1. タブアクション用キーマップの定義と設定

    (無料で手に入る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))
    

    これをしばらく使用した後に、同意の確認なしでタブを閉じるのは危険だと感じたらどうしよう? いくつかオプションが考えられる:

    1. tab-bar-close-tab-by-nameコマンドの使用は継続、ただしEmbarkに同意を求めさせる:

          (push #'embark--confirm
                (alist-get 'tab-bar-close-tab-by-name
                           embark-pre-action-hooks))
      
    1. 同意を求めるプロンプトをもったコマンドを独自に記述、上記キーマップで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_Kasparovhttps://en.wikipedia.org/wiki/Garry_KasparovというURLを意味していることをEmbarkに教え込むだけだ。

認識する必要がある構文次第では、好きなだけ趣向を凝らすことも可能だ。ここでは例をシンプルに保つために、これはwikipedia:[[:alnum:]_]+というregexpにマッチするようなリンクだと仮定する。ポイントを取り囲むマッチを探して、'(url URL-OF-THE-PAGE START . END)という形式のドットリストをリターンする関数を記述することにしよう。ここでターゲットの境界を示すバッファー位置のSTARTおよびENDは、(リストembark-indicatorsembark-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-pread-from-minibufferのかわりにread-keyを使用するのだ。Embarkユーザーにはy-or-n-p-use-read-keytにセットするようお勧めする。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-command2C-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な関数をアクションとして使用するのにもいくつか利点があるのだ:

  1. 関数ならその辺に常に転がっているし、単にそれを再利用する方が簡単なので

  2. コマンドのアクションのターゲットはテキストプロパティがないシンプルな文字列だけだが、テキストプロパティをもった文字列、何なら文字列以外のターゲットを受け取るようなアクションが欲しいという具体的で技術的な理由のため

Embark、MarginaliaとConsult

EmbarkがMarginaliaConsultの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-lineconsult-outlineconsult-markからoccur-modeのバッファーを得るためのembark-export
    ビルトインのoccurコマンドのバッファーで行うようにマッチにジャンプしたら、その後はnext-errorprevious-errorで他のマッチに移動できるだろう。eを押下してoccur-edit-modeをアクティブにすれば、その場でマッチの編集も可能なのだ!

  • Consultのconsult-grepconsult-git-grepconsult-ripgrepといった非同期検索のコマンドいずれからでもgrep-modeバッファーにエクスポートできる
    ここでもnext-errorprevious-errorでマッチの間を移動できるのは同じだ。更にもしwgrepパッケージをインストールしてあれば、その場でマッチを編集できるだろう

どちらもgを押下すれば元のエクスポートしたConsultコマンドに戻って、再度入力を行うことができる(revertと似ているがこちらの方がより柔軟だ)。もしお望みなら再度エクスポートすることもできるが、入力を編集して検索する言葉を変更したり、その検索で満足したら単にキャンセルすればよいだろう。

embark-consultには候補コレクター(candidate collector)もいくつか含まれている。候補コレクターを使えばバッファーのコンテンツのライブアップデートを得ることができるのだ。

  • embark-consult-outline-candidatesconsult-outlineを使用してバッファーコンテンツのアウトラインヘッダーを生成する

  • embark-consult-imenu-candidatesconsult-imenuを使用してConsultバッファーのimenuアイテムを生成する

  • 上記の2つの関数をシンプルに組み合わせたのがembark-consult-imenu-or-outline-candidatesで、prog-modeから派生したバッファーならimenuアイテム、それ以外のバッファーにたいしてはアウトラインのヘッダーを生成する

これらの関数をembark-live(あるいはembark-collectembark-exportの場合でも)で使用するには、embark-candidate-collectorsリストの最後に関数を追加すればよい。embark-consultパッケージははデフォルトでは、最後に追加したものがもっとも賢明なデフォルトとみなされるからだ。

これらのエクスポーターや候補コレクター以外にもembark-consultパッケージではEmbarkとConsultの間の精密な微調整、小さな統合がたくさん提供されている。いくつか例を挙げておこう:

  • 非同期検索をアクションとして使用した場合には、ターゲットに関連のあるファイルだけを検索する:

    ターゲットがファイルならそれらのファイル、バッファーの場合にはバッファーに割り当てられたファイルがあればファイルから、なければそのバッファーのdefault-directoryにあるすべてのファイルから、ブックマークならリンクが指すファイルから検索する(Emacs Lispライブラリーにたいしても同様)

    これはたとえばconsult-findnamesという名前のファイルを検索、embark-act-allconsult-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を使っているか興味のある人のために、目を通すべきリンクをいくつか挙げておこう

お勧めのビデオもいくつかある:

貢献してくれる人たちへ

Embarkに貢献してくれる人は大歓迎だ。アクション、ターゲット、ファインダー、候補コレクター、エクスポーター用のwish listがある。Embarkについて他にもアイデアがあれば、遠慮なくissue trackerでissueをオープンしていただきたい。何かイカしたコンフィグのテクニックを提供する場としては、wikiがピッタリだろう。

コードへの貢献も大歓迎だがEmbarkは現在GNU ELPAに所属しているので、コードを貢献する前にFSFへの著作権譲渡が必要になるだろう。

謝辞

Embarkのコードのほとんどを記述し、かつ一部のデザイン上の決定に頑迷に拘り続けているのはわたくし、Omar Antolín Camarenaだが、他の人たちからの実のある助言について、わたしはあまりに長い間この文書に記すのを怠っていた。特にDaniel Mendlerは重要ないくつかの機能の実装、大量の有益なアイデアの価値は計り知れない。

コードに貢献してくれた人たち:

アドバイスや有益な議論で貢献してくれた人たち:

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?