vimでtexのアウトラインを作ってみる
:vimgrep
とかをするとロケーションリストで文字列と行番号が紐ついてるやつがでてくる。
texを書いてるときに\section
とかをツリー表示にして見れるようにしたい
vim-lspのLspDocumentSymbol
やvista.vimだとツリー表示になってないので自分で作る
まず、バッファ内のsection
を取得する。
let l:lines = getline(1, '$')
let l:sections = []
for l:idx in range(len(l:lines))
if l:lines[l:idx] =~ '\(=?sub\)*section'
let l:sections += [l:lines[l:idx] . ':' . string(l:idx+1)]
endif
endfor
call map(l:qlist, {_, v -> substitute(v, '^\s*', '', '')[1:})
これで先頭の空白文字と\
を取り除いた(sub)*section
を取得できる
これをツリーっぽい表現に変える。
それぞれの要素の深さを持つl:depth
も取っておく。
(sub
の数で云々するので今はいらないけど後でsection
以外も出したくなるとあったほうが便利そうなのでつけておく)
function! s:get_sections()
let l:lines = getline(1, '$')
let l:sections = []
let l:depths = []
for l:idx in range(len(l:lines))
if l:lines[l:idx] =~ '\(=?sub\)*section'
let l:sections += [l:lines[l:idx][1:] . ':' . string(l:idx+1)]
let l:depths += [count(l:lines[l:idx], 'sub')]
endif
endfor
call map(l:qlist, {_, v -> substitute(v, '^\s*', '', '')})
return make_tree(l:sections, l:depths)
endfunction
function! s:is_last_element(i, d) abort
return a:i == len(a:d) - 1
\ || a:d[a:i] > a:d[a:i + 1]
endfunction
function! s:make_tree(contents, depths) abort
let l:results = map(
\ a:contents,
\ {i, val -> repeat('| ', a:depths[i]) . (s:is_last_element(i, a:depths) ? '└ ' : '├ ') . a:contents[i]}
\ )
return l:results
endfunction
is_last_element
で配列を渡しているが、vimだとdictとlistは参照渡しをするようなのであまり問題はなさそう
ホントはクロージャにしようとしたけどうまくいかなかったので後回しにする
出来上がる配列
\section{1}
\subsection{1-1}
\subsection{1-2}
\subsubsection{1-2-1}
\subsubsection{1-2-2}
\subsection{1-3}
\section{2}
\seccction{this should not be shown}
で実行すると
[
\ '├ section{1}:1',
\ '| ├ subsection{1-1}:2',
\ '| ├ subsection{1-2}:3',
\ '| | ├ subsubsection{1-2-1}:4',
\ '| | └ subsubsection{1-2-2}:5',
\ '| └ subsection{1-3}:6',
\ '└ section{2}:8'
\ ]
となる
できた配列をlocation-listに流し込めればよさそう
でも単純にcall setloclist(0, ['foo', 'bar'])
してもうまくいかないみたい
lexpr
を使えばよさそう
lgetexpr l:results | vertical botright lopen
でいけた
errorfotmat
の形式があっていないのでうまくいかない。
正規表現的に表すと(│ )*[└├] (sub)*section{\.}:\d
。
最後の\d
が行番号になる
let &errorformat = 'section{}:%l'
にするとつぎのようになる
|1|
|| subsection{}:2
|| subsection{}:3
|| subsubsection{}:4
|| subsubsection{}:5
|| subsection{}:6
|8|
対応する部分が||
の中に入るようだ
location-listでやるのは難しいのかもしれないなあ
自分でバッファを作ることにする
自分でバッファ作ってやるとするとジャンプ処理を書かないといけないなあ
開いてるアウトラインのバッファがどのバッファに紐付いてるかを取得しないといけない
vistaだとvista#cursor#FoldOrJump()
が呼ばれているみたい
やっぱりlocation-listでなんとかしたい感じもある
ロケーションリストにしたいなら最低限ファイル名と行番号がいるみたい
function! Hoge() abort
let l:foo = [
\ '[' .. expand('%') .. ']taro:1',
\ '[' .. expand('%') .. ']jiro:2'
\ ]
let l:eft = &errorformat
let &errorformat = '[%f]%m:%l'
lgetexpr l:foo | vertical botright lopen
let &errorfile = l:eft
endfunction
これを実行すると次のようになる
ジャンプもできる。
ファイル名を削るとできない
最低限ファイル名だけあれば飛べるみたい
対応する行にとべてないようにみえる
%mも必要な要素みたい
各要素にバッファ名を入れて
['[foo.tex] ├ section{hoge}']
みたいな文字列の配列にした上で let &errorformat = '[%f]%m:%l'
にするとうまく行く
ただし、バッファ名と行番号が表示されて幅を取るのでconcealするかなにかして隠さないといけない
を参考にlocation-listにconcealしてみる
まず、普通のバッファにconcealできるか
foobarfoo
このバッファに対して以下を実行
:setlocal conceallevel=3
:syntax match test 'foo' conceal
両脇のfoo
が消えてbar
だけが残った
カーソルを合わせるとconcealが解除されるのはset concealcursor=
で制御できる。
ロケーションリスト(nomodifiable
)に対してやるのでsetlocal concealcursor=nc
で良さそう
fyi: これ、プラグインにしてある