VS Code (LaTeX Workshop)

Wer eine .tex-Datei in einem frisch installierten Visual Studio Code öffnet, sieht sofort Syntaxfarben – ganz ohne Erweiterung. VS Code bringt nämlich eine LaTeX-Grammatik mit, und diese Grammatik stammt aus der Erweiterung LaTeX Workshop. Mit den Farben ist allerdings alles gesagt, was VS Code über LaTeX weiß. Das Bauen, die PDF-Anzeige, das Springen zwischen Quelle und PDF: All das leistet LaTeX Workshop, und gesetzt wird ohnehin von der TeX-Distribution auf dem eigenen Rechner, die als Kindprozess gestartet wird. Diese Seite behandelt das zweistufige Modell, mit dem ein Build beschrieben wird – Tools und Rezepte –, den Grund, warum das voreingestellte Rezept für sehr viele Dokumente eine andere Engine startet als nötig, und den eingebauten PDF-Betrachter, der SyncTeX zu einem Strg-Klick macht.

Was VS Code von sich aus über LaTeX weiß

Ein nacktes VS Code registriert drei Sprach-IDs samt je einer Grammatik und sonst nichts: tex für .sty und .cls, latex für .tex, bibtex für .bib. Kein Build-Befehl, kein PDF-Betrachter, keine Vervollständigung, kein Sprung zu einem \ref. Diese Grammatikdateien stammen aus dem Repository jlelong/vscode-latex-basics, dessen README festhält, dass die Dateien ursprünglich Teil von LaTeX Workshop waren; VS Code liefert sie seit der Version vom Januar 2022 mit. Die Farben, die beim Öffnen einer .tex-Datei erscheinen, sind also bereits das Werk der Erweiterung – Monate bevor man sie installiert.

Alles Übrige liefert LaTeX Workshop (von James Yu, Marketplace-ID James-Yu.latex-workshop) – alles außer TeX. Die Erweiterung startet ausführbare Dateien wie latexmk, pdflatex oder biber als Kindprozesse und liest deren Ausgabe zurück; gesünder als die dahinterliegende Distribution – TeX Live, MiKTeX, MacTeX – kann sie also nie sein. Daraus folgt eine Faustregel für die Fehlersuche: Das Projekt einmal im Terminal bauen, bevor eine einzige Einstellung angefasst wird. Scheitert latexmk dort, rettet keine Zeile in settings.json; klappt es dort und meldet die Erweiterung trotzdem, der Befehl sei nicht auffindbar, dann ist die von VS Code geerbte Umgebung verdächtig und nicht die Erweiterung.

Die Installation ist unspektakulär – Erweiterungsansicht (Ctrl/Cmd+Shift+X), nach „LaTeX Workshop“ suchen. Was damit ankommt, ist beinahe die gesamte Arbeitsfläche: Build-Befehle, die PDF-Vorschau, Vervollständigung, der Sprung von einem \ref oder \cite zum Ziel, eine Gliederung des Dokuments und ein Projektdateibaum, der aus \input und \include zusammengesetzt wird – zugleich die Liste der Dateien, die der automatische Build beobachtet. Wurde für die TeX-Installation der PATH geändert, sollte VS Code neu gestartet werden, am besten mit Ab- und erneuter Anmeldung, damit die neue Umgebung greift. Danach im Terminal prüfen, ob die Distribution überhaupt antwortet.

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

Läuft der Build im Terminal, liegt alles Weitere auf der VS-Code-Seite und schrumpft auf drei Fragen: Welches Rezept läuft?, Welche Datei ist die Wurzel? und Wo erscheint das PDF? Der Rest dieser Seite handelt von diesen drei Fragen.

Tools und Rezepte: latex-workshop.latex.recipes lesen

Ein Build wird zweistufig beschrieben. Ein Tool (latex-workshop.latex.tools) definiert genau einen zu startenden Befehl: name, command (die ausführbare Datei) und ein args-Array. Ein Rezept (latex-workshop.latex.recipes) ist eine geordnete Liste von Tool-Namen. latexmk ist ein Rezept aus einem Tool, pdflatex -> bibtex -> pdflatex * 2 eines aus vieren. Die Trennung existiert, weil dieselbe ausführbare Datei je nach Lage mit anderen Argumenten gebraucht wird: Mitgeliefert sind unter anderem latexmk, lualatexmk, xelatexmk, latexmk_rconly, pdflatex, bibtex und tectonic; Rezepte kombinieren sie neu, statt die Befehlsdefinitionen zu verdoppeln.

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

Liest man die Argumente einzeln, wird der Entwurf sichtbar. -synctex=1 fordert die weiter unten besprochene SyncTeX-Zuordnung an; -interaction=nonstopmode läuft bis zum Ende durch, statt bei einem Fehler auf Eingabe zu warten; -file-line-error gibt Fehler in der Form main.tex:42: Undefined control sequence aus – und genau dieses Flag erlaubt der Erweiterung den Sprung aus dem Problemfenster direkt in die Zeile. -pdf weist latexmk an, das PDF unmittelbar mit pdfLaTeX zu erzeugen; dieses Flag stiftet weiter unten Ärger. Die %…%-Marken sind Platzhalter, die die Erweiterung kurz vor dem Start ersetzt.

PlatzhalterWird ersetzt durch
%DOC%Pfad der Wurzeldatei ohne Endung
%DOC_EXT%Pfad der Wurzeldatei mit Endung
%DOCFILE%nur der Dateiname der Wurzeldatei, ohne Endung
%DIR%das Verzeichnis der Wurzeldatei; Vorgabe für outDir
%OUTDIR%das über latex-workshop.latex.outDir gesetzte Ausgabeverzeichnis
%TMPDIR%ein temporäres Verzeichnis für Hilfsdateien; hält die Quelle sauber
%WORKSPACE_FOLDER%der Pfad des aktuell geöffneten Arbeitsbereichs

Welches Rezept läuft, entscheidet latex-workshop.latex.recipe.default. Vorgabe ist "first"der oberste Eintrag der Liste gewinnt –; mit "lastUsed" merkt sich die Erweiterung das zuletzt gewählte Rezept. Ein Build startet mit Ctrl+Alt+B (auf dem Mac Cmd+Alt+B). Für einen einzelnen Lauf mit einem bestimmten Rezept dient „LaTeX Workshop: Build with recipe“ aus der Befehlspalette; um eines an eine Datei zu binden, schreibt man %!LW recipe=latexmk (lualatex) in die erste Zeile. Diese Anweisung wird ignoriert, sobald man das Rezept von Hand im Panel auswählt.

Warum das Standardrezept eine andere Engine startet als gewünscht

Die Antwort steckt in der obigen Tool-Definition: -pdf ist das Argument, das latexmk anweist, das PDF direkt mit pdfLaTeX zu erzeugen. Dieses eine Wort erklärt einen großen Teil der Meldungen „im Terminal geht es, in VS Code nicht“. Lädt die Präambel fontspec – alles mit OpenType-Schriften, alles mit unicode-math, die meisten neueren Vorlagen –, bricht der Build ab mit ! Fatal Package fontspec Error: The fontspec package requires either XeTeX or LuaTeX. Tippt man stattdessen Japanisch, Chinesisch oder Koreanisch direkt ins Dokument, erscheint ! LaTeX Error: Unicode character こ (U+3053) not set up for use with LaTeX. Keine der beiden Meldungen erwähnt VS Code, denn VS Code ist nicht das Problem.

Die Abhilfe besteht schlicht darin, ein anderes Rezept zu wählen; dafür gibt es vier Wege, nach zunehmender Dauerhaftigkeit. Nur dieses eine Mal: „Build with recipe“ aus der Befehlspalette. Nur für diese Datei: %!LW recipe=… in der ersten Zeile. Ab jetzt das zuletzt gewählte: latex-workshop.latex.recipe.default auf "lastUsed" setzen. Fest für das ganze Projekt: latex-workshop.latex.recipes in settings.json so umsortieren, dass das gewünschte Rezept vorn steht, denn die Vorgabe lautet "first". In der mitgelieferten Liste stehen bereits latexmk (lualatex), latexmk (xelatex) und latexmk (latexmkrc) – meistens wird also ausgewählt und nicht geschrieben.

Die Engine in .latexmkrc festlegen, nicht in settings.json

Eine in den Editor-Einstellungen notierte Engine-Wahl verlässt den eigenen Rechner nie. In einer .latexmkrc notiert, reist sie mit dem Projekt: das TeXstudio der Mitautorin, ein CI-Container und ein blankes latexmk main.tex kommen zum selben Ergebnis. Genau dafür gibt es das mitgelieferte Rezept latexmk (latexmkrc): Es führt latexmk %DOC% aus und fügt kein eigenes Argument hinzu. Es folgt eine Konfiguration für upLaTeX + dvipdfmx, lange die Standardkombination japanischer Fachaufsätze:

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;

Der entscheidende Punkt ist $pdf_mode. 3 bedeutet „erst ein DVI erzeugen, dann mit $dvipdf in ein PDF verwandeln“; 1 steht für pdfLaTeX direkt, 4 für LuaLaTeX. Den Index übernimmt upmendex, das japanisch sortieren kann, die Literatur upbibtex. %S, %O, %D und %B sind latexmks eigene Platzhalter – Quelle, zusätzliche Optionen, Ausgabeziel und Basisname ohne Endung – und gehören zu einer anderen Familie als %DOC% der Erweiterung; sie dürfen nicht vermischt werden. Unauffällig, aber wichtig: $latex trägt -synctex=1. Fehlt es, funktioniert das weiter unten beschriebene Klick-Springen kommentarlos nicht mehr.

Wer umgekehrt alles in settings.json halten und auf die .latexmkrc verzichten will, schreibt Tool und Rezept selbst und stellt das Rezept an die erste Stelle. Hier ein in sich geschlossenes Beispiel für LuaLaTeX, das Japanisch über luatexja und die ltjsclasses setzt und daher keinen Umweg über dvipdfmx braucht:

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"] }
  ]
}

Die drei Einstellungen, die zuerst feststehen sollten

Einstellungen stehen in settings.json. Über Ctrl/Cmd+, die Einstellungsansicht öffnen und rechts oben „Einstellungen (JSON) öffnen“ wählen; bearbeitbar ist entweder die globale Benutzerdatei oder eine .vscode/settings.json im Projekt. Was Mitautoren oder ein Build-Server teilen sollen, gehört zwingend in die zweite. Von den mehreren Dutzend Einstellungen lohnt es sich, drei gleich zu Beginn festzulegen:

  • latex-workshop.latex.outDir – wohin Zwischendateien und PDF gehen. Vorgabe ist %DIR%, also neben die .tex. Mit %DIR%/out verstreuen sich .aux, .log und .fls nicht im Quellordner, und die .gitignore schrumpft auf eine Zeile.
  • latex-workshop.latex.autoBuild.run – was einen automatischen Build auslöst. Vorgabe ist onFileChange; dabei werden Abhängigkeiten auf der Platte beobachtet, sodass auch Änderungen außerhalb des Editors reagieren. Alternativen sind onSave (nur beim Speichern) und never (nur manuell). Wer den Überblick verliert, wodurch ein Build ausgelöst wurde, fährt mit onSave lesbarer.
  • latex-workshop.view.pdf.viewer – wo das PDF erscheint: tab (Vorgabe, eine Registerkarte in VS Code), browser (Standardbrowser) oder external (anderes Programm, als experimentell gekennzeichnet). Für reibungsloses 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"
}

Ein getrenntes Ausgabeverzeichnis birgt eine Falle: Wer outDir ändert, ändert zugleich, wo die Erweiterung nach .aux und .fls sucht. Passt das nicht mehr zu dem Ort, an den der Build sie tatsächlich schreibt, existiert am Ende ein PDF, das die Erweiterung nicht findet, und Querverweise lösen sich nie auf. Beides muss übereinstimmen – besonders, wenn die .latexmkrc zusätzlich $out_dir setzt. Das Aufräumen der Zwischendateien übernimmt latex-workshop.latex.autoClean.run; liegt aber ohnehin alles in out/, genügt es, den Ordner zu löschen.

main.tex bauen, während man ein Kapitel bearbeitet: % !TEX root

In die erste Zeile der Unterdatei gehört % !TEX root = ../main.tex. Allein damit beginnt ein Build beim Hauptdokument, auch wenn nur das Kapitel geöffnet ist. Es wirkt, weil LaTeX Workshop die Wurzeldatei in fünf Stufen sucht und dieser magische Kommentar die erste ist: (1) % !TEX root; (2) enthält die geöffnete Datei selbst \documentclass oder \begin{document}; (3) die .tex-Dateien im obersten Verzeichnis des Arbeitsbereichs nach einer Klassendeklaration durchsehen; (4) der Aufbau mit dem Paket subfiles; (5) Auswertung der .fls-Dateien. Oft trifft die Vermutung zu – doch in einer Abschlussarbeit mit Dutzenden Kapiteldateien ist gerade das Vermuten die Gefahr.

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

\section{Method}
% Building from inside this chapter still starts at main.tex.
  • Neben % !TEX root liest die Erweiterung auch % !TEX program, % !TEX options und % !BIB program. Die ganze Familie lässt sich mit latex-workshop.latex.build.enableMagicComments auf false abschalten.
  • Den Arbeitsbereich im Projektstammverzeichnis öffnen, in dem main.tex liegt. Wird nur der Kapitelordner geöffnet, erreicht Stufe (3) der Suche das Hauptdokument gar nicht.
  • Der Pfad in % !TEX root ist relativ zu der Datei, die ihn trägt; verschiebt man ein Kapitel in einen anderen Ordner, muss die Zeile angepasst werden.
  • Eine Rezeptwahl, die % !TEX program widerspricht, führt zuverlässig in die Irre. Im Team gehört die Entscheidung in die .latexmkrc, und als Rezept dient einheitlich latexmk (latexmkrc).

Der eingebaute PDF-Betrachter und SyncTeX per Strg-Klick

Der PDF-Betrachter, den tab öffnet, ist eine Webseite rund um Mozillas PDF.js, ausgeliefert von einem kleinen Server, den die Erweiterung lokal betreibt. Deshalb liefert browser exakt denselben Betrachter, und deshalb hängt die Darstellung nicht vom Betriebssystem oder einem installierten PDF-Programm ab. Nur external fällt heraus: Es übergibt die Datei an ein anderes Programm und gilt daher als experimentell – die Vorwärtssuche mit einem externen Betrachter muss über Schlüssel wie latex-workshop.view.pdf.external.synctex.command eigens eingerichtet werden.

Festzuhalten ist hier: SyncTeX ist keine Funktion des Editors. Die Zuordnung zwischen Quellzeilen und Positionen im PDF schreibt die TeX-Engine, und ihr Schalter heißt -synctex=1. Dass die Erweiterung eine .synctex.gz lesen kann, liegt allein daran, dass das Rezept dieses Flag übergeben hat. Wer ein eigenes Tool definiert und das Flag vergisst, erhält einen erfolgreichen Build und ein PDF – nur das Klick-Springen hört kommentarlos auf, ohne jede Fehlermeldung. Ging es gestern noch und heute nicht, sind zuerst die Argumente des Rezepts verdächtig.

Zwei Gesten genügen. Vorwärtssuche (Quelle → PDF) springt von der Schreibmarke an die passende Stelle im PDF: Ctrl+Alt+J, auf dem Mac Cmd+Alt+J; in der Befehlspalette heißt sie „LaTeX Workshop: SyncTeX from cursor“. Für den automatischen Sprung nach jedem Build latex-workshop.synctex.afterBuild.enabled auf true setzen. Rückwärtssuche (PDF → Quelle) ist ein Ctrl-Klick (auf dem Mac Cmd-Klick) im eingebauten Betrachter; die Geste bestimmt latex-workshop.view.pdf.internal.synctex.keybinding mit ctrl-click (Vorgabe) oder double-click. Nebenbei: Bauen ist Ctrl+Alt+B, das PDF öffnen Ctrl+Alt+V.

Auch über den DVI-Weg bleibt SyncTeX erhalten. Übergibt man $latex wie in der obigen .latexmkrc ein -synctex=1, wandert die von upLaTeX geschriebene Zuordnung durch dvipdfmx bis ins PDF; man muss nicht mit pdfLaTeX direkt zum PDF, um springen zu können. Die Maschinerie selbst – was in einer .synctex.gz steht und dass ein negativer Wert stattdessen eine unkomprimierte, lesbare Textdatei erzeugt – gehört auf die SyncTeX-Seite.