2024-02-16 ,

org-modeでの入力補完

org-modeでの補完候補はorg-pcomplete.elで設定しているようで、特に#+で始まるオプションについてはpcomplete/org-mode/file-option関数で候補を生成しています。そのソースコードは次の通りです。

;; org-pcomplete.elより引用
(defun pcomplete/org-mode/file-option ()
  "Complete against all valid file options."
  (require 'org-element)
  (pcomplete-here
   (org-pcomplete-case-double
    (append (mapcar (lambda (keyword) (concat keyword " "))
                    org-options-keywords)
            (mapcar (lambda (keyword) (concat keyword ": "))
                    org-element-affiliated-keywords)
            (let (block-names)
              (dolist (name
                       '("CENTER" "COMMENT" "EXAMPLE" "EXPORT" "QUOTE" "SRC"
                         "VERSE")
                       block-names)
                (push (format "END_%s" name) block-names)
                (push (concat "BEGIN_"
                              name
                              ;; Since language is compulsory in
                              ;; export blocks source blocks, add
                              ;; a space.
                              (and (member name '("EXPORT" "SRC")) " "))
                      block-names)
                (push (format "ATTR_%s: " name) block-names)))
            (mapcar (lambda (keyword) (concat keyword ": "))
                    (org-get-export-keywords))))
   (substring pcomplete-stub 2)))

この関数で次のような文字列を補完候補として生成しています。

なぜこれを調べたかというと、 #+ATTR_HTML が補完候補に現れないことに気が付いたからです。

理由は上のコードを見れば分かるとおり、ATTR_で始まるものはCENTER、COMMENT、EXAMPLE、EXPORT、QUOTE、SRC、VERSEしか登録していないからです。

え、ちょっと待って? #+ATTR_CENTER: #+ATTR_COMMENT: #+ATTR_EXAMPLE: #+ATTR_EXPORT: #+ATTR_QUOTE: #+ATTR_SRC: #+ATTR_VERSE: なんてありましたっけ? 聞いたこともありませんし使ったこともありません。ちょっと検索しても分かりませんでした。どういうこと??

ATTR_HTMLはエクスポートキーワードの中にあるのかなとも思ったのですが、そういうわけでも無さそうです。

ATTR_HTMLを追加しようにもこの関数の途中に処理を挟むのは難しいので、諦めて全部上書きしてしまうことにしました。ついでに独自のspecial blocks(org-special-blocks.el ― turn blocks into LaTeX envs and HTML divs)も補完候補に加えます(#+begin_figures-col2<div class="figures-col2> にしてくれます)。ATTR_CENTER等は削除してしまいましょう。よく分かりませんし、使いませんし。

(with-eval-after-load "org-pcomplete"
  ;; org-pcomplete.elの`pcomplete/org-mode/file-option'よりコピーして改変
  (defun pcomplete/org-mode/file-option ()
    "Complete against all valid file options."
    (require 'org-element)
    (pcomplete-here
     (org-pcomplete-case-double
      (append (mapcar (lambda (keyword) (concat keyword " "))
                      org-options-keywords)
              (mapcar (lambda (keyword) (concat keyword ": "))
                      org-element-affiliated-keywords)
              ;; ★[変更]: 追加
              (mapcar (lambda (keyword) (concat keyword ": "))
                      '("ATTR_HTML" "ATTR_ORG"))
              (let (block-names)
                (dolist (name
                         '("CENTER" "COMMENT" "EXAMPLE" "EXPORT" "QUOTE" "SRC"
                           "VERSE"
                           ;; ★[変更]: 追加。CSSで画像を並べるのに使っています
                           "FIGURES-FLOW" "FIGURES-COL2" "FIGURES-COL3")
                         block-names)
                  (push (format "END_%s" name) block-names)
                  (push (concat "BEGIN_"
                                name
                                ;; Since language is compulsory in
                                ;; export blocks source blocks, add
                                ;; a space.
                                (and (member name '("EXPORT" "SRC")) " "))
                        block-names)
                  ;; ★[変更]: 削除。ATTR_CENTERって何……?
                  ;;(push (format "ATTR_%s: " name) block-names)
                  ))
              (mapcar (lambda (keyword) (concat keyword ": "))
                      (org-get-export-keywords))))
     (substring pcomplete-stub 2))))

この所Corfuの自動補完をいじっていましたが、もちろんこれらの設定も自動補完に影響します。 #+beg くらいまで打てば自動で補完候補が出現します。corfu-auto-prefixが3なので#+の後に3文字入力したら表示されるのでしょう。

本当は #+ と入力しただけで補完候補が現れてくれればいいのですが。

一応次のようにすれば実現出来ます(corfu--auto-complete-deferredにいったい幾つadviceを仕掛けるつもりだ)。

(defun my-corfu--auto-complete-deferred:around:for-org (oldfun &rest args)
  (let (;; org-modeで#+が出たら即自動補完する
        (corfu-auto-prefix
         (if (and (derived-mode-p 'org-mode) ;;org-modeで……
                  ;; <bol><spaces>#+<identifier>
                  (save-excursion
                    (and (skip-chars-backward "-A-Za-z0-9_+")
                         (eq (char-before) ?+)
                         (eq (char-before (1- (point))) ?#)
                         (goto-char (- (point) 2))
                         (skip-chars-backward " \t")
                         (bolp))))
             0
           corfu-auto-prefix)))
    (apply oldfun args)))
(advice-add 'corfu--auto-complete-deferred :around
            #'my-corfu--auto-complete-deferred:around:for-org)

[[ を入力したらリンクタイプも補完してほしいんですよね。先日書いたやつ[[elisp-function: を入力するのが大変なので(よくfucntionやらfunctoinやら打ち間違えます。やっぱり [[elfun: にでもしておけば良かったかな)。 まぁ、そのうち。

(追記:リンクタイプも補完するようにしました)

ああよく見たら、同じくorg-pcomplete.elpcomplete/org-mode/linkというのがあるんですね。

;; org-pcomplete.elより引用
(defun pcomplete/org-mode/link ()
  "Complete against defined #+LINK patterns."
  (pcomplete-here
   (pcomplete-uniquify-list
    (copy-sequence
     (mapcar (lambda (e) (concat (car e) ":"))
             (append org-link-abbrev-alist-local
                     org-link-abbrev-alist))))))

リンクタイプを差し置いてlink abbrevだけあるのかよ!

まぁ、これもそのまま修正しちゃいましょう。(2024-02-23追記:org-link-completion.elでリンクタイプを補完できるようにしたので、それを使えば次の変更は不要です)

(with-eval-after-load "org-pcomplete"
  ;; org-pcomplete.elの`pcomplete/org-mode/link'よりコピーして改変
  (defun pcomplete/org-mode/link ()
    "Complete against defined #+LINK patterns."
    (pcomplete-here
     (pcomplete-uniquify-list
      (copy-sequence
       (mapcar (lambda (e) (concat (car e) ":"))
               (append org-link-abbrev-alist-local
                       org-link-abbrev-alist
                       ;; ★[変更]: 追加
                       org-link-parameters)))))))

上で書いたコードもちょっと修正して [[ で自動補完が開始されるようにしましょう。あ、ソースコードブロック中の [[ にも反応しちゃうかな。まあいいや。

(defun my-corfu--auto-complete-deferred:around:for-org (oldfun &rest args)
  (let (;; org-modeで#+や[[が出たら即自動補完する
        (corfu-auto-prefix
         (if (and (derived-mode-p 'org-mode) ;;org-modeで……
                  (or
                   ;; <bol><spaces>#+<identifier>
                   (save-excursion
                     (and (skip-chars-backward "-A-Za-z0-9_")
                          (eq (char-before) ?+)
                          (eq (char-before (1- (point))) ?#)
                          (goto-char (- (point) 2))
                          (skip-chars-backward " \t")
                          (bolp)))
                   ;; [[<link-type>
                   (save-excursion
                     (and (skip-chars-backward "-A-Za-z0-9_+") ;;file+sysがある
                          (eq (char-before) ?\[)
                          (eq (char-before (1- (point))) ?\[)))
                   ;; (2024-02-18追記:見出しの補完を追加)
                   ;; [[*<heading>
                   (save-excursion
                     (and (skip-chars-backward "^*\t\n[")
                          (eq (char-before) ?*)
                          (eq (char-before (1- (point))) ?\[)
                          (eq (char-before (- (point) 2)) ?\[)))))
             0
           corfu-auto-prefix)))
    (apply oldfun args)))
(advice-add 'corfu--auto-complete-deferred :around
            #'my-corfu--auto-complete-deferred:around:for-org)