VS Code(LaTeX Workshop)

拡張を一つも入れていない Visual Studio Code で .tex を開くと、それだけでもう色が付きます。VS Code が LaTeX の文法定義を最初から同梱しているからで、しかもその文法は LaTeX Workshop 拡張から切り出されたものです。ただし VS Code が LaTeX について知っているのはそこまで——色だけ。ビルドも PDF の表示も、ソースと PDF を行き来するジャンプも、すべて LaTeX Workshop の仕事で、組版そのものは PC に入れた TeX ディストリビューションが子プロセスとして行います。このページでは、ビルドを記述する「ツールとレシピ」の二層構造、既定のレシピが多くの文書で別のエンジンを呼んでしまう理由、そして内蔵 PDF ビューアと SyncTeX を扱います。

VS Code 単体が LaTeX について知っていること

素の VS Code が登録しているのは、三つの言語 ID とそれぞれの文法だけです。.sty.clstex.texlatex.bibbibtex——それで終わりで、ビルドコマンドも PDF ビューアも補完も \ref のジャンプもありません。この文法ファイル群は jlelong/vscode-latex-basics というリポジトリから来ていて、その README には「これらのファイルはもともと LaTeX Workshop の一部だった」と書かれています。VS Code が同梱しはじめたのは 2022 年 1 月リリースから。つまり .tex を開いた瞬間に目に入る色は、拡張を入れる前からすでに LaTeX Workshop の仕事なのです。

残りをすべて引き受けるのが LaTeX Workshop(作者は James Yu、マーケットプレースの ID は James-Yu.latex-workshop)です。ただしこの拡張も TeX を同梱してはいません。latexmkpdflatexbiber といった実行ファイルを子プロセスとして起動し、その出力を読んでいるだけなので、拡張の健康状態は背後のディストリビューション——TeX Live、MiKTeX、MacTeX——の健康状態を超えられません。ここから実務上の切り分けが一つ導けます。設定を一行も触る前に、同じプロジェクトをターミナルで一度ビルドすること。 そこで latexmk が失敗するなら settings.json をいくら直しても無駄ですし、逆にターミナルでは通るのに拡張が「コマンドが見つからない」と言うなら、疑うべきは拡張ではなく VS Code が受け継いだ環境変数です。

導入そのものは普通です——拡張ビュー(Ctrl/Cmd+Shift+X)で「LaTeX Workshop」を検索して入れるだけ。そのかわり入るものは作業面のほぼ全部で、ビルドコマンド、PDF プレビュー、補完、\ref\cite から参照先へのジャンプ、文書のアウトライン、そして \input\include をたどって組み立てたプロジェクトのファイル木(自動ビルドが監視するのもこの一覧です)。TeX を入れるために PATH を書き換えた直後なら、VS Code を——できればログインし直して——再起動し、新しい環境を読み直させてください。そのうえで、まずディストリビューションが応答するかどうかを端末で確かめます。

terminal
# does the TeX distribution answer at all?
latexmk --version

# does the project build outside the editor?
latexmk -pdf main.tex

# with a .latexmkrc that already picks the engine, no flags are needed
latexmk main.tex

# is the extension looking at the same PATH you are?
which latexmk

ターミナルで通ってしまえば、残る問題は VS Code 側の三点に絞られます。どのレシピが走るのかどのファイルがルートなのかPDF をどこに出すのか。以下はその三つの話です。

ツールとレシピ——latex-workshop.latex.recipes の読み方

ビルドは二層で書かれています。ツール(latex-workshop.latex.tools)は「1 回起動するコマンド」の定義で、namecommand(実行ファイル)・args(引数の配列)から成ります。レシピ(latex-workshop.latex.recipes)は「ツール名を並べた順序つきのリスト」です。latexmk はツール 1 個のレシピ、pdflatex -> bibtex -> pdflatex * 2 はツール 4 個のレシピ。二層に分かれているのは、同じ実行ファイルを引数違いで何度も使いたいからで、既定で入っているツールは latexmklualatexmkxelatexmklatexmk_rconlypdflatexbibtextectonic など。レシピはこれらを組み替えて作られていて、コマンドの定義そのものは重複しません。

terminal
{
  "name": "latexmk",
  "command": "latexmk",
  "args": [
    "-synctex=1",
    "-interaction=nonstopmode",
    "-file-line-error",
    "-pdf",
    "-outdir=%OUTDIR%",
    "%DOC%"
  ],
  "env": {}
}

引数を一つずつ読むと、この拡張の設計思想が見えてきます。-synctex=1 は後述の SyncTeX 対応表を出力させる指定、-interaction=nonstopmode はエラーで対話停止せず最後まで走らせる指定、-file-line-error はエラーを main.tex:42: Undefined control sequence の形で出させる指定です。最後の一つが効いていると、拡張は「問題」パネルからその行へ直接飛べます。-pdf は「pdfLaTeX で PDF を直接作れ」という latexmk への指示——これが後で問題になります。大文字の %…% はプレースホルダで、拡張が実行直前に置き換えます。

プレースホルダ展開される値
%DOC%ルートファイルのパス(拡張子なし)
%DOC_EXT%ルートファイルのパス(拡張子つき)
%DOCFILE%ルートファイルの名前だけ(拡張子なし)
%DIR%ルートファイルがあるディレクトリ。outDir の既定値
%OUTDIR%latex-workshop.latex.outDir で決めた出力先
%TMPDIR%中間ファイル用の一時ディレクトリ。ソースを汚さない
%WORKSPACE_FOLDER%いま開いているワークスペースのパス

どのレシピが走るかは latex-workshop.latex.recipe.default が決めます。既定値は "first"——一覧の先頭が使われるという意味で、これを "lastUsed" にすると前回選んだレシピを覚えます。ビルドの起動は Ctrl+Alt+B(Mac は Cmd+Alt+B)。特定のレシピを選んで走らせたいときはコマンドパレットの「LaTeX Workshop: Build with recipe」を使い、ファイル単位で固定したいときは 1 行目に %!LW recipe=latexmk (lualatex) と書きます。ただしこの指定は、パネルから手でレシピを選んだときは無視されます。

既定のレシピが別のエンジンを呼んでしまう理由

答えは上のツール定義の中にあります。-pdf は latexmk に「pdfLaTeX で直接 PDF を作れ」と言う引数だからです。このたった一語が、「ターミナルでは通るのに VS Code では失敗する」という報告のかなりの部分を説明します。プリアンブルで fontspec を読み込む文書——OpenType フォントを使うもの、unicode-math を使うもの、最近のテンプレートの多く——では、ビルドが ! Fatal Package fontspec Error: The fontspec package requires either XeTeX or LuaTeX. で止まります。日本語・中国語・韓国語をそのまま打ち込んだ場合は ! LaTeX Error: Unicode character こ (U+3053) not set up for use with LaTeX. になります。どちらのメッセージにも VS Code は出てきません。VS Code の問題ではないからです。

直しかたは「別のレシピを選ぶ」だけで、恒久性の低い順に四つあります。この一回だけならコマンドパレットの「Build with recipe」。このファイルだけなら 1 行目に %!LW recipe=…この先ずっと同じものをなら latex-workshop.latex.recipe.default"lastUsed" に。プロジェクト全体で固定するなら settings.jsonlatex-workshop.latex.recipes を並べ替えて、使いたいレシピを先頭に置きます(既定が "first" だからです)。既定のレシピ一覧にはすでに latexmk (lualatex)latexmk (xelatex)latexmk (latexmkrc) が入っているので、たいていの場合は書くのではなく選ぶだけで済みます。

エンジンを settings.json ではなく .latexmkrc に書く

エディタ設定に書いたエンジンの選択は、そのエディタを使っている自分の PC からは出ていきません。.latexmkrc に書けば、それはプロジェクトと一緒に移動します——共著者の TeXstudio でも、CI のコンテナでも、素の latexmk main.tex でも同じ結果になります。既定レシピの latexmk (latexmkrc) は、まさにこのために用意されたもので、引数を一切足さずに latexmk %DOC% だけを実行します。次は upLaTeX + dvipdfmx(日本語の論文で長く定石だった組み合わせ)の例です。

latex
$latex = 'uplatex -synctex=1 -interaction=nonstopmode -file-line-error %O %S';
$bibtex = 'upbibtex %O %B';
$biber = 'biber --bblencoding=utf8 -u -U --output_safechars %O %S';
$makeindex = 'upmendex %O -o %D %S';
$dvipdf = 'dvipdfmx %O -o %D %S';
$pdf_mode = 3;
$max_repeat = 5;

要点は $pdf_mode の一語です。3 は「DVI を作ってから $dvipdf で PDF にする」経路、1 は pdfLaTeX で直接、4 は LuaLaTeX。索引は和文の並べ替えができる upmendex、文献は upbibtex に任せます。%S%O%D%B は latexmk 側のプレースホルダで、それぞれソース・追加オプション・出力先・拡張子なしのベース名を表します(拡張の %DOC% などとは別系統なので混同しないでください)。$latex-synctex=1 を入れてあるのが地味に重要で、これを落とすと後述のクリックでのジャンプが黙って効かなくなります。

逆に、.latexmkrc を使わず settings.json だけで完結させたい場合は、ツールとレシピを自分で書いて先頭に置きます。次は LuaLaTeX で組む自己完結の例です(LuaLaTeX なら luatexjaltjsclasses 系で和文が組めるため、dvipdfmx を経由する必要がありません)。

terminal
{
  "latex-workshop.latex.tools": [
    {
      "name": "lualatexmk",
      "command": "latexmk",
      "args": [
        "-synctex=1",
        "-interaction=nonstopmode",
        "-file-line-error",
        "-lualatex",
        "-outdir=%OUTDIR%",
        "%DOC%"
      ],
      "env": {}
    }
  ],
  "latex-workshop.latex.recipes": [
    { "name": "lualatexmk", "tools": ["lualatexmk"] }
  ]
}

最初に決める三つの設定——出力先・自動ビルド・PDF の表示先

設定は settings.json に書きます。Ctrl/Cmd+, の設定画面を開き、右上の「設定 (JSON) を開く」から、ユーザー全体のファイルか、プロジェクト直下の .vscode/settings.json のどちらかを選びます。共著者やビルドサーバーと共有したいものは必ず後者に置きます。 数十ある設定のうち、最初に意味を決めておくと後が楽になるのは次の三つです。

  • latex-workshop.latex.outDir — 中間ファイルと PDF の出力先。既定は %DIR%.tex と同じ場所)。%DIR%/out にすると .aux.log.fls がソースの隣に散らばらず、.gitignore も 1 行で済みます。
  • latex-workshop.latex.autoBuild.run — 自動ビルドの契機。既定は onFileChange(依存ファイルの変更をディスク上で監視するため、エディタ外の変更にも反応します)。ほかに onSave(保存時のみ)と never(手動のみ)。何がビルドを起こしたのか分からなくなりやすい人には onSave が読みやすい選択です。
  • latex-workshop.view.pdf.viewer — PDF の表示先。tab(既定。VS Code 内のタブ)/browser(既定のブラウザ)/external(外部ビューア、実験的扱い)。SyncTeX を素直に使うなら tab
terminal
{
  "latex-workshop.latex.outDir": "%DIR%/out",
  "latex-workshop.latex.autoBuild.run": "onSave",
  "latex-workshop.view.pdf.viewer": "tab",
  "latex-workshop.latex.recipe.default": "lastUsed"
}

出力先を分けたときの落とし穴が一つあります。outDir を変えると、拡張が .aux.fls を探す場所も同時に変わります。 ここが実際のビルドの出力先とずれていると、PDF はできているのに拡張が見つけられない、参照が解決されない、といった状態になります。.latexmkrc 側で $out_dir を指定している場合はとくに、両者を同じ値にそろえてください。なお中間ファイルの後始末は latex-workshop.latex.autoClean.run が受け持ちますが、out/ にまとめてしまえばフォルダごと消せるので、多くの場合そこまで要りません。

章ファイルを編集しながら main.tex をビルドする——% !TEX root

子ファイルの 1 行目に % !TEX root = ../main.tex と書きます。これだけで、その章だけを開いていても本体からビルドが始まります。この一行が効くのは、LaTeX Workshop がルートファイルを五段階で探し、その一段目がこのマジックコメントだからです——(1) % !TEX root、(2) 開いているファイル自身に \documentclass\begin{document} があるか、(3) ワークスペース直下の .tex を順に見てクラス宣言を持つものを探す、(4) subfiles パッケージの構成、(5) .fls ファイルの解析。放っておいても当たることは多いのですが、章ファイルが数十ある卒論では、当たり外れがあること自体が事故のもとです。

latex
% !TEX root = ../main.tex
% !TEX program = lualatex

\section{Method}
% Building from inside this chapter still starts at main.tex.
  • マジックコメントは % !TEX root のほかに % !TEX program% !TEX options% !BIB program も読まれます。まとめて無効にするには latex-workshop.latex.build.enableMagicCommentsfalse にします。
  • ワークスペースは main.tex のあるプロジェクトルートで開きます。章フォルダだけを単独で開くと、(3) の探索がそもそも本体に届きません。
  • % !TEX root のパスは、それを書いたファイルからの相対パスです。章を別フォルダへ移したら書き換えが要ります。
  • レシピの選択と % !TEX program の指定が食い違うと迷子になります。チームで使うなら .latexmkrc に寄せて、レシピは latexmk (latexmkrc) にそろえるのが安全です。

内蔵 PDF ビューアと、Ctrl+クリックで飛ぶ SyncTeX

tab を選んだときに開く PDF ビューアの正体は、Mozilla の PDF.js を組み込んだ Web ページで、拡張がローカルに立てたサーバーが配信しています。だから browser に切り替えても見た目も操作も同じものが出ますし、PDF の描画が OS やビューアの違いに左右されません。external だけは別で、ファイルを他のプログラムに渡すだけなので実験的な扱いです——外部ビューアでの前方検索は latex-workshop.view.pdf.external.synctex.command などで別途組む必要があります。

ここで押さえておきたいのは、SyncTeX はエディタの機能ではないという点です。ソースの行と PDF 上の位置の対応表を書き出すのは TeX エンジンで、そのスイッチが -synctex=1。拡張が .synctex.gz を読めるのは、レシピがそのフラグを渡していたからにすぎません。自分でツールを定義するときに -synctex=1 を書き忘れると、ビルドは成功し、PDF もできて、しかしクリックでのジャンプだけが黙って効かなくなります。エラーはどこにも出ません。「昨日まで飛べたのに」と思ったら、まずレシピの引数を疑ってください。

操作は二つ覚えれば足ります。前方検索(ソース → PDF) はカーソル位置から PDF の対応箇所へ飛ぶ操作で、Ctrl+Alt+J(Mac は Cmd+Alt+J)。コマンドパレットからは「LaTeX Workshop: SyncTeX from cursor」です。ビルド直後に自動で飛ばすなら latex-workshop.synctex.afterBuild.enabledtrue に。逆方向検索(PDF → ソース) は内蔵ビューアで Ctrl(Mac は Cmd)+クリック。この操作は latex-workshop.view.pdf.internal.synctex.keybindingctrl-click(既定)か double-click を選べます。ついでに、ビルドは Ctrl+Alt+B、PDF を開くのは Ctrl+Alt+V です。

DVI を経由する日本語のビルドでも SyncTeX は生きています。上の .latexmkrc のように $latex-synctex=1 を渡しておけば、upLaTeX が書いた対応情報は dvipdfmx を通って PDF まで届きます。「pdfLaTeX 直行でないと飛べない」ということはありません。SyncTeX そのもののしくみ——.synctex.gz の中身や、負の値を渡すと圧縮されない読めるテキストになること——は SyncTeX のページに譲ります。