orgエクスポータのディスパッチャを水平分割画面に表示する


はじめに

Org mode には強力なエクスポート機能が実装されています.そのため「Org で書いて XX に出力」が非常に簡単です.しかも,エクスポート機能の追加は,ox.elのフレームワークに合わせることで簡単に実現できます.

たとえば,pandoc を使った出力機能を組み込むには, ox-pandoc.el を読み込むだけです.(require 'ox-pandoc) するだけで,エクスポート用のディスパッチャ画面に pandoc で出力可能な形式がリストされます.なお,読み込みは,org-modules に追加する方法もあります.

問題

拡張パッケージを読み込むだけで,新しい出力形式に容易に対応可能なのは素晴らしいのですが,組み込みを繰り返していくと,ディスパッチャが縦方向に長くなり,閲覧性が低下します.

先程の pandoc が良い例で,20行以上に渡って出力形式がリストされてしまいます.

私の場合は他にも,様々なモジュールを組み込んでいるので,100行に迫る勢いのリストが構築されます.そのため縦方向の表示に余裕が無くなります.

(with-eval-after-load "ox"
    (add-to-list 'org-modules 'ox-odt)
    (add-to-list 'org-modules 'ox-org)
    (add-to-list 'org-modules 'ox-json)

    (require 'ox-twbs nil t)
    (require 'ox-reveal nil t)
    (require 'ox-hugo nil t)
    (require 'ox-pandoc nil t)
    (require 'ox-qmd nil t)
    (require 'ox-gfm nil t))

解決

縦方向に画面を分割してディスパッチャを表示するのではなく,横方向に画面分割させるように改良します.以下は,debug-on-errornil の状態,あるいは,org のHEADを使用していることを前提にします.

(with-eval-after-load "ox"
  (defvar my-org-export-before-hook nil)
  (add-hook 'my-org-export-before-hook #'split-window-horizontally)

  (defvar my-org-export-after-hook nil)
  (add-hook 'my-org-export-after-hook #'delete-window)

  (defun my-org-export--post-processing ()
    (when (eq this-command 'org-export-dispatch)
      (run-hooks 'my-org-export-after-hook))
    (when this-command
      (remove-hook 'post-command-hook #'my-org-export--post-processing)))

  (defun my-org-export-dispatch (f ARG)
    (cond (org-export-dispatch-use-expert-ui
           (apply f ARG))
          ((> (frame-width) 160)
           (when my-org-export-after-hook
             (add-hook 'post-command-hook #'my-org-export--post-processing))
           (run-hooks 'my-org-export-before-hook)
           (apply f ARG))
          (t
           (apply f ARG))))
  (advice-add 'org-export-dispatch :around #'my-org-export-dispatch)

  (defun my-org-export-insert-default-template (f &optional backend subtreep)
    (let ((this-command nil))
      (apply f backend subtreep)))
  (advice-add 'org-export-insert-default-template :around
              #'my-org-export-insert-default-template))

この設定を組み込むと,フレームの横幅が160を超える時に,水平分割でディスパッチャを表示するようになります.

なお,my-org-export-before-hookmy-org-export-after-hook には任意の関数をぶら下げられますから,ディスパッチャ表示の前後で何か処理したい時に利用してください.

moom と組み合わせる

私は80桁原理主義者なので,フレームの横幅が160というような幅広で使うことがほぼありません.逆に,臨機応変にフレーム幅を変更したいので,拙作の moom.el を使っています.以下は,ディスパッチャを呼び出すときだけフレーム幅を広げて,作業を終えたら自動的に戻す設定です.

(with-eval-after-load "ox"
  (defvar my-org-export-before-hook nil)
  (add-hook 'my-org-export-before-hook #'moom-split-window)

  (defvar my-org-export-after-hook nil)
  (add-hook 'my-org-export-after-hook #'moom-delete-windows)

  (defun my-org-export--post-processing ()
    (when (eq this-command 'org-export-dispatch)
      (run-hooks 'my-org-export-after-hook))
    (when this-command
      (remove-hook 'post-command-hook #'my-org-export--post-processing)))

  (defun my-org-export-dispatch (f ARG)
    (cond
     (org-export-dispatch-use-expert-ui
      (apply f ARG))
     ((eq (frame-width) 80)
      (if (not (require 'moom nil t))
          (apply f ARG)
        (when my-org-export-after-hook
          (add-hook 'post-command-hook #'my-org-export--post-processing))
        (run-hooks 'my-org-export-before-hook)
        (apply f ARG)))
     (t
      (apply f ARG))))
  (advice-add 'org-export-dispatch :around #'my-org-export-dispatch)

  (defun my-org-export-insert-default-template (f &optional backend subtreep)
    (let ((this-command nil))
      (apply f backend subtreep)))
  (advice-add 'org-export-insert-default-template :around
              #'my-org-export-insert-default-template))

こうすると,エクスポートの時だけフレームが広がり,ディスパッチャを表示してくれます.

バッファにエクスポートで自動的にコピーする

エクスポートの選択肢には,ファイルに書き出す以外にバッファに書き出すオプションが提供されていることがあります.org-export-show-temporary-export-buffert の時,書き出されたバッファがポップアップして表示されますが,コピーまではしてくれません.

次のように拡張すると,ちょと強引ですが,バッファにエクスポートされた内容が自動的にコピーされます.適当なバッファでヤンクするか,環境によってはそのまま別のアプリでペーストできます.本記事の my-org-export-after-hook を早速使っていますので,上記の設定に追加する形で使用してください.

(with-eval-after-load "ox"
  (defvar my-org-export-last-buffer nil)

  (defun my-org-export-to-buffer (_backend
                                  buffer
                                  &optional _async _subtreep _visible-only
                                  _body-only _ext-plist _post-process)
    (setq my-org-export-last-buffer buffer))
  (advice-add 'org-export-to-buffer :after #'my-org-export-to-buffer)

  (defun my-copy-exported-buffer ()
    (interactive)
    (when my-org-export-last-buffer
      (with-current-buffer my-org-export-last-buffer
        (mark-whole-buffer)
        (kill-ring-save (point-min) (point-max))
        (message "Copied: %s" my-org-export-last-buffer))
      (setq my-org-export-last-buffer nil)))
  (add-hook 'my-org-export-after-hook #'my-copy-exported-buffer))

まとめ

org エクスポータを水平方向に分割してディスパッチャを表示する方法を示しました.水平方向に分割して表示できないの?というアイディアは, EmacsJPの slack で @conao3 さんにいただきました.感謝!

なお,先日リリースされた最新の Org mode version 9.3 では,ソースブロックの編集画面を水平分割したウィンドウに表示する機能が新たに実装されています.こちらもあわせてお試しください.