🧮

Vim/NeovimでgOでMarkdownのTOCを抽出する

2022/11/27に公開

Neovimで、gOというキーマッピングをご存知でしょうか。
VimにはないNeovimのマッピングで、ファイルのアウトラインを抽出します(gOはおそらくgo Outlineのイメージ)。

この機能は、現時点ではhelpとmanファイルにしか対応していません。
これをMarkdownでも行う設定を紹介します。

Markdown TOC抽出関数

以下を設定することで、Markdownファイル内のヘッダを抽出し、Quickfixリストに一覧します。
Markdown限定の処理なので、ftpluginに入れると良いと思います。

after/ftplugin/markdown.vim
function! s:markdown_outline() abort
  let fname = @%
  let current_win_id = win_getid()

  " # heading
  execute 'vimgrep /^#\{1,6} .*$/j' fname

  " heading
  " ===
  execute 'vimgrepadd /\zs\S\+\ze\n[=-]\+$/j' fname

  let qflist = getqflist()
  if len(qflist) == 0
    cclose
    return
  endif

  " make sure to focus original window because synID works only in current window
  call win_gotoid(current_win_id)
  call filter(qflist,
        \ 'synIDattr(synID(v:val.lnum, v:val.col, 1), "name") != "markdownCodeBlock"'
        \ )
  call sort(qflist, {a,b -> a.lnum - b.lnum})
  call setqflist(qflist)
  call setqflist([], 'r', {'title': fname .. ' TOC'})
  copen
endfunction

nnoremap <buffer> gO <Cmd>call <sid>markdown_outline()<CR>


この記事のmdファイルで使用した例

解説

最初のvimgrepは以下の形式のヘッダを抽出します。

# h1

## h2

### h3

#### h4

##### h5

###### h6

その後のvimgrepaddは以下の形式のヘッダを抽出します。

h1
====

h2
---

上記が混在した場合にファイル内の順序を揃えるため、lnumを用いてソートしています。

なお、code block内の要素はsyntax情報を利用して除去しています。

バリエーション

こちらで紹介したものは筆者の好みでQuickfixリストを使っていますが、本来のgOはロケーションリストが使用されます。
基本構造は変わらないので、getqflistgetloclistに変更するなどの修正を加えれば、ロケーションリストを使うこともできます。お好みで修正してください。

Discussion