embark - カーソル位置にあるテキストに対するアクションを実行(org link対応)

「何か」に対して何かを「実行」するembark

テキストファイルに書いてあるURLを開きたい、と思ったとき、あなたはどうしますか?

今どきのテキストエディタはURLやメールアドレスなどを認識し、リンクとして表示してくれますね。このリンクを開くには、リンクにマウスカーソルをあて、クリックするのが普通です。

Emacsでもモードによっては同じように動作します(orgモードなど)。この際、マウスではなくてキーボードでも開くことができます。orgモードですと、テキストカーソルをリンクの上に移動させ、C-c C-oで開けます。org-open-at-pointという関数が呼び出されます。リンクはURLやメールアドレスだけでなく、他のorgファイルへの参照や、脚注があります。

さて、話をより一般的にして「いまカーソルの位置にある何か」に対して「アクション」を行うことをできるようにする、embark1というパッケージがあります。「URLを開く」というアクションだけでなく、ソースコードのシンボルの定義を開く、ヘルプを調べるなど様々なアクションを実行できます。

何かをしたい(ex URLを開く、シンボルの定義を参照する、等)文字列があればそこにカーソルをもっていき、embark-act(私はM-.に設定)を実行しましょう。そうすれば、ミニバッファにメニューが表示され、実行したいアクションを選択できます。

また、 embark-dwim (C-.に設定)を押すとembarkがおすすめのアクションを選んで実行してくれます。なお、dwimとはdo what i meanの略で、「私の意図するところを(察して)行え」といった意味でしょうか。意訳すれば、「適当によろしく」みたいなものかな。

org linkを辿るアクションが見当たらない!

さて、この便利なembark、とても不思議なことに、orgのリンクを扱うアクションが見当たりませんでした。結構、いろいろ検索して見当たらなかったので、「ない理由が分からない自分のほうが変なのかも」とも思った次第です。

まあ、とはいえ、あってもだれも困らないとも思うので、自分で作りました。orgモードと、もちろん、embarkに依存するので、leafで設定をしています。

(leaf *my-embark-orglink
  :after org embark
  :config
  (defun my-embark-orglink-at-point ()
    "Target a link at point of orglink."
    (save-excursion
      (let* ((cur (point))
             (beg (progn (search-backward "[" nil t) (point)))
             (end (progn (search-forward  "]" nil t) (point)))
             (str (buffer-substring-no-properties beg end)))
        (when (and (<= beg cur) (<= cur end))
          (save-match-data
            (when (string-match "\\(\\[.+\\]\\)" str)
              `(orglink
                ,(format "%s" (match-string 1 str))
                ,beg . ,end)))))))
  (add-to-list 'embark-target-finders 'my-embark-orglink-at-point)
  (embark-define-keymap embark-orglink-map
    "Orglink keymap"
    ("RET" org-open-at-point)
    ("o" org-open-at-point))
  (add-to-list 'embark-keymap-alist '(orglink . embark-orglink-map)))

全体の設定

せっかくですので、設定全体も掲載しておきますね。バインディングに使っているC-@ですがヘルプのプレフィックスです。私はC-hをBSにしているので、C-@をヘルプ(もともとのC-hの機能)にしています。

(leaf embark
  :straight t
  :bind
  (("M-." . embark-act)
   ("C-." . embark-dwim)
   ("C-^ B" . embark-bindings) ;; C-h -> C-^ にしています
   )
  :init
  (setq prefix-help-command #'embark-prefix-help-command)
  :config
  (add-to-list 'display-buffer-alist
               '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
                 nil
                 (window-parameters (mode-line-format . none))))
  :config
  (leaf *my-embark-orglink
    :after org embark
    :config
    (defun my-embark-orglink-at-point ()
      "Target a link at point of orglink."
      (save-excursion
        (let* ((cur (point))
               (beg (progn (search-backward "[" nil t) (point)))
               (end (progn (search-forward  "]" nil t) (point)))
               (str (buffer-substring-no-properties beg end)))
          (when (and (<= beg cur) (<= cur end))
            (save-match-data
              (when (string-match "\\(\\[.+\\]\\)" str)
                `(orglink
                  ,(format "%s" (match-string 1 str))
                  ,beg . ,end)))))))
    (add-to-list 'embark-target-finders 'my-embark-orglink-at-point)
    (embark-define-keymap embark-orglink-map
      "Orglink keymap"
      ("RET" org-open-at-point)
      ("o" org-open-at-point))
    (add-to-list 'embark-keymap-alist '(orglink . embark-orglink-map))))

閑話休題

探せばどこかに必ずあるはずだ、と思って探したものがどうしても見つからないとき、「あるはずだ」と思った自分が間違っているのではないか、とも思ってしまうのは、自分の弱さなのでしょうか。

ところで、embarkとは「Emacs Mini-Buffer Actions Rooted in Keymaps」の頭文字を取って名付けたようです。キーマップに基づきミニバッファで行うアクションといった意味になりますが、もともと embark は英語で「乗船する」という単語です。なかなかおしゃれなネーミングなのですね。