Vim+TeXでページ追従


TL;DR

こんなことができるようになりました。

今回紹介するプラグイン

xuhdev/vim-latex-live-preview
VimでTeXのリアルタイムプレビューを可能にするプラグインです。
今回はこれにページ追従を加えます。

プラグイン導入

プラグインマネージャとしてdeinを使っている場合はこれで動くと思います。
pdfビューアはevinceを指定しています。試した限りではevinceが最もよく動きました。
pLaTeXでなくLuaLaTeXを指定しているのは中間ファイルを作らず、直接pdfを出力するからです。
jsarticle/jsreport/jsbookと高い互換性のある文書クラスltjsarticle/ltjsreport/ltjsbookがあるので、表紙テンプレートがpLaTeX版で渡されるなどしても簡単に対応できると思います。

~/.vim/rc/dein_lazy.toml
[[plugins]]
repo = 'xuhdev/vim-latex-live-preview'
on_ft = 'tex'
hook_add = '''
  let g:livepreview_previewer = 'evince'
"  let g:livepreview_engine = 'luajittex --fmt=luajitlatex.fmt' "コンパイル速度を上げたい場合はこちら
  let g:livepreview_engine = 'lualatex'
  set swapfile
  set updatetime=1000
'''

追加する関数

自分でプラグインを変更したくない方はこちら

ソースコードの行番号と生成されたpdfのページ番号の対応をダンプする関数

~/dein/repos/github.com/xuhdev/vim-latex-live-preview/plugin/latexlivepreview.vim
function! s:Preprocess(file)
execute s:py_exe "<< EEOOFF"
with open(vim.eval("a:file"), "r") as f:
    lines = f.readlines()
for i in range(len(lines) - 1):
    lines[i] = '\write\pagedump{\    \\thepage ,}' + lines[i]
lines[-1] = '\write\pagedump{\    \\thepage ]}' + lines[-1]
lines.insert(0, '\\newwrite\pagedump\n')
lines.insert(1, '\openout\pagedump=pagedump.txt\n')
lines.insert(2, '\write\pagedump{let pagedump = [}')
lines.append('\closeout\pagedump')
with open(vim.eval("a:file"), "w") as f:
    f.writelines(lines)
EEOOFF
endfunction

この関数の動きを説明します。以下のようなTeXソースコードがあったとき、

before_preprocess.tex
\documentclass{ltjsarticle}
\begin{document}
page1
\newpage
page2
\end{document}

次のようにコンパイル前に前処理をします。

after_preprocess.tex
\newwrite\pagedump
\openout\pagedump=pagedump.txt
\write\pagedump{let pagedump = [}
\write\pagedump{\ \thepage ,}\documentclass{ltjsarticle}
\write\pagedump{\ \thepage ,}\begin{document}
\write\pagedump{\ \thepage ,}page1
\write\pagedump{\ \thepage ,}\newpage
\write\pagedump{\ \thepage ,}page2
\write\pagedump{\ \thepage ]}\end{document}
\closeout\pagedump

コンパイルすると次のような配列の定義ファイルが$TEXMFOUTPUT/pagedump.txtに書き出されます。
これをvimからsource pagedump.txtすることでカーソル行とpdfのページ番号の対応を扱うことができます。

pagedump.txt
let pagedump = [
\ 1,
\ 1,
\ 1,
\ 1,
\ 2,
\ 2]

s:Compile()の末尾でコンパイル直前にs:Preprocessを呼ぶように書き換えます

~/dein/repos/github.com/xuhdev/vim-latex-live-preview/plugin/latexlivepreview.vim
    " Write the current buffer in a temporary file
    silent exec 'write! ' . b:livepreview_buf_data['tmp_src_file']
    call s:Preprocess(b:livepreview_buf_data['tmp_src_file'])
    call s:RunInBackground(b:livepreview_buf_data['run_cmd'])
    lcd -
endfunction

evinceにページ番号を指定するキーストロークを投げる関数

私の使っているWSLとXmingの組み合わせでは、WSL内からxdotoolでキーを投げるとXmingが扱っているウィンドウのうち、直近に選択されたウィンドウに投げられる。という挙動になっています。
そのため、Vimの中からフォーカスを外さずに裏のevinceにキーストロークを投げることができます。
当然ながら、他の環境ではこの関数を書き換える必要があります。

~/dein/repos/github.com/xuhdev/vim-latex-live-preview/plugin/latexlivepreview.vim
function! g:EvinceGoTo()
    execute 'source '                   .
    \ b:livepreview_buf_data['tmp_dir'] .
    \      expand('%:p:h')              .
    \      '/pagedump.txt'
    call system("xdotool key 'ctrl+l'")
    call system("xdotool key 'ctrl+a'")
    call system("xdotool key " . g:pagedump[getcurpos()[1]-1])
    call system("xdotool key KP_Enter")
endfunction

操作方法

:LLPStartPreviewでevinceを表示したのち、:call EvinceGoTo()でカーソル行のページが表示されます。

~/dein/repos/github.com/xuhdev/vim-latex-live-preview/plugin/latexlivepreview.vim
nnoremap <Leader>e :<C-u>call EvinceGoTo()<CR>

としておけばSpace eですみます。(私は<Leader>にスペースを指定している)
ただし、現状:call EvinceGoTo()ごとにpagedump.txtを読み込みに行っているので、autocmd cursormovedでフックするにはもうすこしpagedump.txtの読み出しタイミングを工夫する必要がありそうです。

明日はクリスマス・イブですね、VimConf2018ではDark Poweredな発表で話題だったΛlisueさんの記事になります。
全く関係ないですが下は大学で最近見かけたポスターです。いったいモデルは何すえさんなのか…?。

そういえばVimConf2018ではVimの父Bramさんからサインを頂いちゃいました。

それでは、Happy Vimming!