# ## ## ## EmacsでMarkdownのアウトライン編集をする
(markdown-mode, outline-minor-mo
de)

Emacsでは、Markdownでも見出しをアウトラインとして扱えるため、折りたたみや章単位の移動などの編集作業がスムーズにできます。
markdown-mode と outline-minor-mode を組み合わせ、最低限の構造操作に絞った環境を整えました。

<svg class="eyecatch-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192" width="192" height="192">
  <defs>
    <style type="text/css">
      .bg { fill: white; }
      .primary { fill: #2196F3; }
      .secondary { fill: #4CAF50; }
      .accent { fill: #FF9800; }
      .dark { fill: #607D8B; }
    </style>
  </defs>
  
  <!-- 背景 -->
  <rect class="bg" width="192" height="192" rx="32" ry="32"/>
  
  <!-- ドキュメントの輪郭 -->
  <rect class="primary" x="40" y="30" width="112" height="140" rx="4" ry="4" stroke="#1976D2" stroke-width="3" fill="none"/>
  
  <!-- Markdown見出し記号と階層構造 -->
  <!-- レベル1見出し -->
  <g transform="translate(50, 50)">
    <text class="primary" font-family="monospace" font-size="20" font-weight="bold">#</text>
    <line class="secondary" x1="25" y1="10" x2="92" y2="10" stroke-width="3" stroke-linecap="round"/>
  </g>
  
  <!-- 折りたたみ表示(縦線) -->
  <line class="dark" x1="60" y1="65" x2="60" y2="95" stroke-width="2" stroke-dasharray="4,4"/>
  
  <!-- レベル2見出し(折りたたまれた状態) -->
  <g transform="translate(55, 80)">
    <text class="primary" font-family="monospace" font-size="16" font-weight="bold">##</text>
    <line class="accent" x1="30" y1="8" x2="82" y2="8" stroke-width="2.5" stroke-linecap="round"/>
  </g>
  
  <!-- 折りたたみ表示(縦線) -->
  <line class="dark" x1="60" y1="95" x2="60" y2="120" stroke-width="2" stroke-dasharray="4,4"/>
  
  <!-- レベル2見出し -->
  <g transform="translate(55, 105)">
    <text class="primary" font-family="monospace" font-size="16" font-weight="bold">##</text>
    <line class="accent" x1="30" y1="8" x2="82" y2="8" stroke-width="2.5" stroke-linecap="round"/>
  </g>
  
  <!-- 折りたたみ表示(縦線) -->
  <line class="dark" x1="60" y1="120" x2="60" y2="145" stroke-width="2" stroke-dasharray="4,4"/>
  
  <!-- レベル2見出し -->
  <g transform="translate(55, 130)">
    <text class="primary" font-family="monospace" font-size="16" font-weight="bold">##</text>
    <line class="accent" x1="30" y1="8" x2="82" y2="8" stroke-width="2.5" stroke-linecap="round"/>
  </g>
  
  <!-- 折りたたみアイコン(三角形) -->
  <g transform="translate(145, 48)">
    <circle class="secondary" cx="0" cy="0" r="12" opacity="0.9"/>
    <path d="M -4,-3 L -4,3 L 2,0 Z" fill="white" stroke="none"/>
  </g>
  
  <!-- 移動矢印(キーバインドを表現) -->
  <g transform="translate(96, 175)">
    <circle fill="#E0E0E0" cx="0" cy="0" r="8"/>
    <path d="M -3,-3 L 0,-6 L 3,-3" stroke="#607D8B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
    <path d="M -3,3 L 0,6 L 3,3" stroke="#607D8B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
  </g>
</svg>EmacsでMarkdownのアウトライン編集をする<br class="chiilabo-br is-on">(markdown-mode, outline-minor-mo<br class="chiilabo-br is-on">de)
;; Markdown編集
(use-package markdown-mode
  :ensure t
  :mode (("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :config
  (setq markdown-command "markdown"))

(with-eval-after-load 'outline
  (define-key outline-minor-mode-map (kbd "C-c C-n")
    #'outline-next-visible-heading)
  (define-key outline-minor-mode-map (kbd "C-c C-p")
    #'outline-previous-visible-heading)
  (define-key outline-minor-mode-map (kbd "C-c C-u")
    #'outline-up-heading))Code language: Lisp (lisp)

関連記事

1. markdown-mode

Emacsで文章を書くとなると、Org modeが中心ですが、最近はMarkdownがよく使われます。
ChatGPTなどの出力コピーも、Markdown記法が使われています。

長いMarkdownを編集するときに、見出しを構造として扱えるだけで、作業はかなり楽になります。

そこで、Markdown編集には markdown-mode を使うことにしました1
これは事実上の標準で、余計な癖がなく、長く使えそうです。

;; Markdown編集
(use-package markdown-mode
  :ensure t
  :mode (("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode)))Code language: Lisp (lisp)

2. 見出しをTABで開閉する

Markdownの見出しは、# の数で階層が決まっており、アウトラインそのものです。
TABキーを押すだけで、見出しの開閉が使えます。
また、Shift+TABキーでは、全体の見出しの開閉ができます。

2.1. 見出し移動のキーバインド

次は、見出し間の移動です。

  • 次の見出しへ移動(C-c C-n)
  • 前の見出しへ移動(C-c C-p)
  • 親見出しへ戻る(C-c C-u)

長い記事でも、今書いている章だけを開いて、他は畳む。
見出し以下が一塊として扱えるので、「章を丸ごと入れ替える」操作が安全にできます。

2.2. 見出し単位の移動や上げ下げ(outline-minor-mode)

見出し単位の編集は、outline-minor-modeのコマンドを使います。
よく使うものを C-c と 方向キーで実行できるようにしました。

  • サブツリーを上に移動 C-c UP
  • サブツリーを下に移動 C-c DOWN
  • サブツリーのレベルを上げる C-c LEFT
  • サブツリーのレベルを下げる C-c RIGHT
(with-eval-after-load 'outline
  (define-key outline-minor-mode-map (kbd "C-c <down>")
    #'outline-next-visible-heading)
  (define-key outline-minor-mode-map (kbd "C-c <up>")
    #'outline-previous-visible-heading)
  (define-key outline-minor-mode-map (kbd "C-c <left>")
    #'outline-up-heading)
  (define-key outline-minor-mode-map (kbd "C-c <right>")
    #'outline-forward-same-level))Code language: Lisp (lisp)
  • カーソルが リスト項目上 → リスト項目(+子)を移動
  • それ以外(見出し上) → 見出しサブツリー全体を移動

3. 見出し単位の切り取り・貼り付け(自作関数)

kill と yank だけでも章ごとの移動はできますが、操作ミスのリスクがあります。
そのため、markdown-kill-subtreeとmarkdown-yank-subtree関数を作りました。

C-c C-kC-c C-y に割り当てました。
Org mode の org-cut-subtree / org-paste-subtree と似た操作感です。

3.1. markdown-kill-subtree の実装

削除直後に次の見出しへ移動するため、連続した章の削除や整理がスムーズになります。

(defun markdown-kill-subtree ()
  "Kill the current Markdown subtree."
  (interactive)
  (outline-mark-subtree)
  (kill-region (region-beginning) (region-end))
  (setq mark-active nil)
  (outline-next-visible-heading))Code language: Lisp (lisp)

3.2. markdown-yank-subtree の実装

見出しの先頭で挿入が行われるため、位置がずれません
挿入後も挿入した章の見出しにカーソルが戻るため、続けて操作しやすくなります。

(defun markdown-yank-subtree ()
  "Yank the killed Markdown subtree."
  (interactive)
  (outline-back-to-heading)
  (yank)
  (newline)
  (outline-back-to-heading))Code language: Lisp (lisp)

3.3. 実際の使用例

例えば、以下のような構成の記事があったとします:

# はじめに
## 背景
## 目的
# 方法
## 環境構築
## 実装
# まとめCode language: PHP (php)

「方法」の章を「はじめに」の前に移動したい場合:

  1. 「方法」の見出し上で C-c C-k
  2. 「はじめに」の見出し上へ移動
  3. C-c C-y

これだけで、章全体が安全に移動します

  1. markdown-modeはJason Blevinsによって開発され、2007年から継続的にメンテナンスされているEmacsの定番パッケージです。GitHubで2,000以上のスターを獲得しており、MELPA経由で簡単にインストールできます。 – jrblevin/markdown-mode – GitHub