VS Code (LaTeX Workshop)

Abra un archivo .tex en un Visual Studio Code recién instalado y ya aparecerá coloreado, sin extensión alguna: VS Code incluye de fábrica una gramática de LaTeX, y esa gramática se extrajo de la extensión LaTeX Workshop. El color, sin embargo, es todo lo que VS Code sabe de LaTeX. Compilar, mostrar el PDF, saltar entre la fuente y el PDF: todo eso corre a cargo de LaTeX Workshop, y la composición misma la hace la distribución de TeX instalada en la máquina, lanzada como proceso hijo. Esta página trata el modelo de dos capas que describe una compilación —herramientas y recetas—, por qué la receta predeterminada llama a un motor distinto del que necesitan muchísimos documentos, y el visor de PDF integrado que convierte SyncTeX en un ctrl-clic.

Lo que VS Code sabe de LaTeX por sí solo

Un VS Code desnudo registra tres identificadores de lenguaje y una gramática para cada uno, y nada más: tex para .sty y .cls, latex para .tex, bibtex para .bib. Ningún comando de compilación, ningún visor de PDF, ninguna finalización, ningún salto a un \ref. Esos archivos de gramática proceden del repositorio jlelong/vscode-latex-basics, cuyo README indica que los archivos formaban originalmente parte de LaTeX Workshop; VS Code los distribuye desde su versión de enero de 2022. Los colores que aparecen nada más abrir un .tex son, pues, obra de la extensión meses antes de instalarla.

Todo lo demás lo aporta LaTeX Workshop (de James Yu; identificador en el Marketplace James-Yu.latex-workshop), todo salvo TeX. La extensión lanza ejecutables como latexmk, pdflatex o biber como procesos hijo y vuelve a leer su salida, de modo que nunca puede estar más sana que la distribución que hay detrás: TeX Live, MiKTeX o MacTeX. De ahí se deduce una regla de diagnóstico: compile el mismo proyecto una vez en una terminal antes de tocar un solo ajuste. Si latexmk falla allí, ninguna línea de settings.json lo salvará; y si allí funciona mientras la extensión sigue diciendo que no encuentra el comando, el sospechoso es el entorno que VS Code heredó, no la extensión.

Instalarla no tiene misterio: la vista de Extensiones (Ctrl/Cmd+Shift+X), buscar «LaTeX Workshop». Lo que llega con ella es casi toda la superficie de trabajo: comandos de compilación, vista previa del PDF, finalización, salto de un \ref o un \cite a su destino, un esquema del documento y un árbol de archivos del proyecto armado siguiendo \input e \include, que es además la lista de archivos que vigila la compilación automática. Si editó su PATH para instalar TeX, reinicie VS Code, idealmente cerrando y volviendo a iniciar sesión, para que se recoja el nuevo entorno. Después compruebe desde una terminal que la distribución responde.

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

Una vez que la compilación en la terminal sale bien, lo que queda vive del lado de VS Code y se reduce a tres preguntas: qué receta se ejecuta, qué archivo es la raíz y dónde aparece el PDF. El resto de esta página trata de esas tres.

Herramientas y recetas: leer latex-workshop.latex.recipes

Una compilación se escribe en dos capas. Una herramienta (latex-workshop.latex.tools) define un único comando que lanzar: un name, un command (el ejecutable) y un array args. Una receta (latex-workshop.latex.recipes) es una lista ordenada de nombres de herramientas. latexmk es una receta de una sola herramienta; pdflatex -> bibtex -> pdflatex * 2 es de cuatro. La división existe porque el mismo ejecutable se quiere con argumentos distintos según el caso: entre las herramientas de fábrica están latexmk, lualatexmk, xelatexmk, latexmk_rconly, pdflatex, bibtex y tectonic, y las recetas las recombinan en lugar de duplicar las definiciones de comandos.

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

Leer los argumentos uno a uno revela el diseño. -synctex=1 pide el mapa de SyncTeX del que se habla más abajo; -interaction=nonstopmode llega hasta el final en vez de detenerse ante un error a esperar entrada; -file-line-error imprime los errores con la forma main.tex:42: Undefined control sequence, y es esa última opción la que permite a la extensión saltar desde su panel de problemas directamente a la línea. -pdf le dice a latexmk que produzca el PDF directamente con pdfLaTeX: la opción que dará guerra más adelante. Los tokens %…% son marcadores de posición que la extensión sustituye justo antes de lanzar el proceso.

MarcadorSe sustituye por
%DOC%ruta del archivo raíz, sin su extensión
%DOC_EXT%ruta del archivo raíz, con su extensión
%DOCFILE%solo el nombre del archivo raíz, sin extensión
%DIR%el directorio del archivo raíz; valor por defecto de outDir
%OUTDIR%el directorio de salida fijado por latex-workshop.latex.outDir
%TMPDIR%un directorio temporal para los archivos auxiliares; deja limpia la fuente
%WORKSPACE_FOLDER%la ruta del espacio de trabajo abierto en ese momento

Qué receta se ejecuta lo decide latex-workshop.latex.recipe.default. Su valor por defecto es "first" —es decir, gana la primera entrada de la lista— y ponerlo en "lastUsed" hace que la extensión recuerde la receta elegida la última vez. Una compilación se lanza con Ctrl+Alt+B (Cmd+Alt+B en Mac). Para ejecutar una receta concreta una sola vez, use «LaTeX Workshop: Build with recipe» desde la paleta de comandos; para fijar una a un archivo, escriba %!LW recipe=latexmk (lualatex) en la primera línea. Esa directiva se ignora en cuanto se elige la receta a mano desde el panel.

Por qué la receta predeterminada llama a otro motor del que quiere

La respuesta está en la definición de herramienta anterior: -pdf es el argumento que le dice a latexmk que produzca el PDF directamente con pdfLaTeX. Esa sola palabra explica buena parte de los informes de «compila en la terminal pero no en VS Code». Si el preámbulo carga fontspec —todo lo que use fuentes OpenType, todo lo que use unicode-math, la mayoría de las plantillas modernas—, la compilación se detiene con ! Fatal Package fontspec Error: The fontspec package requires either XeTeX or LuaTeX. Y si escribe japonés, chino o coreano directamente, obtiene ! LaTeX Error: Unicode character こ (U+3053) not set up for use with LaTeX. Ninguno de los dos mensajes menciona VS Code, porque VS Code no es el problema.

El arreglo consiste sencillamente en elegir otra receta, y hay cuatro vías, de menor a mayor permanencia. Solo esta vez: «Build with recipe» desde la paleta de comandos. Solo este archivo: %!LW recipe=… en la primera línea. A partir de ahora, la última que elegí: poner latex-workshop.latex.recipe.default en "lastUsed". Fija para todo el proyecto: reordenar latex-workshop.latex.recipes en settings.json para que la receta deseada quede primera, ya que el valor por defecto es "first". La lista de fábrica ya contiene latexmk (lualatex), latexmk (xelatex) y latexmk (latexmkrc), así que la mayor parte del tiempo se elige en lugar de escribir.

Ponga el motor en .latexmkrc, no en settings.json

Una elección de motor escrita en los ajustes del editor no sale nunca de la máquina donde se escribió. Escrita en un .latexmkrc, viaja con el proyecto: el TeXstudio del coautor, un contenedor de integración continua y un escueto latexmk main.tex llegan al mismo resultado. La receta de fábrica latexmk (latexmkrc) existe justo para eso: ejecuta latexmk %DOC% y no añade ningún argumento propio. He aquí una configuración upLaTeX + dvipdfmx, durante mucho tiempo la combinación clásica de los artículos japoneses:

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;

La línea decisiva es $pdf_mode. 3 significa «hacer un DVI y luego convertirlo en PDF con $dvipdf»; 1 es pdfLaTeX directo; 4 es LuaLaTeX. El índice va a upmendex, que sabe ordenar japonés, y la bibliografía a upbibtex. %S, %O, %D y %B son los marcadores propios de latexmk —fuente, opciones extra, destino de salida y nombre base sin extensión—, de una familia distinta al %DOC% de la extensión: no los mezcle. Discreto pero importante: $latex lleva -synctex=1. Si se omite, el salto por clic descrito más abajo deja de funcionar sin aviso alguno.

A la inversa, si prefiere mantenerlo todo en settings.json y prescindir del .latexmkrc, escriba su propia herramienta y su propia receta, y coloque la receta en primer lugar. He aquí un ejemplo autónomo para LuaLaTeX, que compone japonés mediante luatexja y las ltjsclasses, sin rodeo por 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"] }
  ]
}

Los tres ajustes que fijar primero: salida, compilación automática, visor

Los ajustes viven en settings.json. Abra la pantalla de ajustes con Ctrl/Cmd+, y use «Abrir ajustes (JSON)» arriba a la derecha; puede editar el archivo de usuario global o un .vscode/settings.json dentro del proyecto. Todo lo que deban compartir los coautores o un servidor de compilación va obligatoriamente en el segundo. De las varias decenas de ajustes, tres merecen decidirse desde el principio:

  • latex-workshop.latex.outDir: adónde van los archivos intermedios y el PDF. El valor por defecto es %DIR%, junto al .tex. Poniendo %DIR%/out, los .aux, .log y .fls dejan de ensuciar la carpeta de fuentes y el .gitignore se reduce a una línea.
  • latex-workshop.latex.autoBuild.run: qué dispara una compilación automática. Por defecto onFileChange, que vigila las dependencias en disco y por tanto reacciona también a cambios hechos fuera del editor. Las alternativas son onSave (solo al guardar) y never (solo manual). Si alguna vez pierde la pista de qué provocó una compilación, onSave es la opción legible.
  • latex-workshop.view.pdf.viewer: dónde aparece el PDF: tab (por defecto, una pestaña dentro de VS Code), browser (su navegador predeterminado) o external (otro programa, considerado experimental). Para un SyncTeX sin fricción, 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"
}

Separar el directorio de salida tiene una trampa: cambiar outDir cambia a la vez dónde busca la extensión los .aux y .fls. Si eso deja de coincidir con el lugar donde la compilación los escribe de verdad, acaba con un PDF que existe pero que la extensión no encuentra, y con referencias cruzadas que nunca se resuelven. Mantenga ambos alineados, sobre todo si su .latexmkrc fija además $out_dir. La limpieza de los intermedios corre a cargo de latex-workshop.latex.autoClean.run, pero si todo cae en out/ basta con borrar la carpeta.

Compilar main.tex mientras edita un capítulo: % !TEX root

Ponga % !TEX root = ../main.tex en la primera línea del archivo hijo. Basta con eso para que la compilación arranque desde el documento principal aunque solo esté abierto el capítulo. Funciona porque LaTeX Workshop busca la raíz en cinco etapas y este comentario mágico es la primera: (1) % !TEX root; (2) ¿contiene el archivo abierto \documentclass o \begin{document}?; (3) recorrer los .tex del nivel superior del espacio de trabajo en busca de una declaración de clase; (4) la disposición del paquete subfiles; (5) el análisis de los archivos .fls. La conjetura acierta a menudo, pero en una tesis con decenas de archivos de capítulo el propio hecho de conjeturar es el peligro.

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

\section{Method}
% Building from inside this chapter still starts at main.tex.
  • Además de % !TEX root, la extensión también lee % !TEX program, % !TEX options y % !BIB program. Para desactivar toda la familia, ponga latex-workshop.latex.build.enableMagicComments en false.
  • Abra el espacio de trabajo en la raíz del proyecto que contiene main.tex. Si solo abre la carpeta del capítulo, la etapa (3) de la búsqueda nunca llega al documento principal.
  • La ruta de % !TEX root es relativa al archivo que la lleva, así que mover un capítulo a otra carpeta obliga a editar la línea.
  • Una receta que contradiga a % !TEX program es una forma segura de perderse. En equipo, lleve la decisión al .latexmkrc y estandarice la receta en latexmk (latexmkrc).

El visor de PDF integrado y SyncTeX con ctrl-clic

El visor de PDF que abre tab es una página web construida alrededor de PDF.js de Mozilla, servida por un pequeño servidor que la extensión levanta en local. Por eso cambiar a browser da exactamente el mismo visor, y por eso el renderizado no varía con el sistema operativo ni con el lector de PDF instalado. Solo external se sale de la norma: entrega el archivo a otro programa, de ahí su condición de experimental; la búsqueda directa con un visor externo hay que montarla aparte, mediante claves como latex-workshop.view.pdf.external.synctex.command.

Lo que hay que retener aquí es que SyncTeX no es una función del editor. Quien escribe la correspondencia entre las líneas de la fuente y las posiciones del PDF es el motor de TeX, y su interruptor es -synctex=1. Que la extensión pueda leer un .synctex.gz se debe únicamente a que la receta pasó esa opción. Defina su propia herramienta y olvídela: la compilación tiene éxito, el PDF aparece, y solo el salto por clic deja de funcionar en silencio, sin error en ninguna parte. Si ayer saltaba y hoy no, sospeche primero de los argumentos de la receta.

Bastan dos gestos. La búsqueda directa (fuente → PDF) salta del cursor al lugar correspondiente del PDF: Ctrl+Alt+J, o Cmd+Alt+J en Mac; en la paleta de comandos es «LaTeX Workshop: SyncTeX from cursor». Para saltar automáticamente tras cada compilación, ponga latex-workshop.synctex.afterBuild.enabled en true. La búsqueda inversa (PDF → fuente) es un Ctrl-clic (Cmd-clic en Mac) en el visor integrado; el gesto se elige con latex-workshop.view.pdf.internal.synctex.keybinding, ya sea ctrl-click (predeterminado) o double-click. De paso: compilar es Ctrl+Alt+B y abrir el PDF, Ctrl+Alt+V.

SyncTeX sobrevive también a la ruta por DVI. Pase -synctex=1 a $latex como en el .latexmkrc anterior y el mapa que escribe upLaTeX viaja a través de dvipdfmx hasta el PDF; no hace falta ir directo al PDF con pdfLaTeX para poder saltar. La maquinaria en sí —qué hay dentro de un .synctex.gz y el hecho de que un valor negativo produzca en su lugar un archivo de texto sin comprimir y legible— corresponde a la página de SyncTeX.