SyncTeX (búsqueda directa/inversa)

Componga un artículo de dieciocho páginas: el PDF sale con 76.974 bytes y el .synctex.gz que aterriza a su lado ocupa 159.347, más del doble que aquello que describe. Ese mapa desmesurado es SyncTeX, y hace exactamente una cosa: recordar qué línea de la fuente LaTeX se convirtió en qué rectángulo de qué página. Esta página abre el archivo, ejecuta ambos sentidos a mano con synctex view y synctex edit, explica por qué un clic aterriza en una línea y no en la palabra a la que apuntaba, y termina con la lista de comprobación para cuando la búsqueda directa no hace absolutamente nada.

Qué produce realmente -synctex=1

Con -synctex=1 el motor escribe un archivo adicional junto al PDF, con el mismo nombre base: main.synctex.gz. Sin la opción no se escribe nada, y esa es con diferencia la omisión más frecuente al configurar SyncTeX. El valor no es un booleano sino un conjunto de bits, detallado literalmente en man synctex: 0 o ausente, ningún archivo; valor positivo, gzip; valor negativo, texto plano sin comprimir; el bit 2 mantiene la compresión pero quita el .gz del nombre; 4 activa el soporte de formularios de pdfTeX; y 8 comprime más. Todo junto es -synctex=15. Solo LuaTeX exige la forma de dos guiones, --synctex=1. El mecanismo viene igual en TeX Live y en MiKTeX, y pdfLaTeX, XeLaTeX y LuaLaTeX producen el mismo tipo de mapa.

terminal
pdflatex -synctex=1  main.tex     # writes main.synctex.gz
xelatex  -synctex=1  main.tex
lualatex --synctex=1 main.tex     # LuaTeX wants two dashes

pdflatex -synctex=-1 main.tex     # writes main.synctex, plain text
pdflatex -synctex=2  main.tex     # writes main.synctex -- still gzip inside!

Ese bit 2 esconde una pequeña trampa. El archivo que produce -synctex=2 se llama main.synctex, pero file detecta datos gzip dentro. Fiarse de la extensión y abrirlo con less da ruido binario y la impresión de que SyncTeX escribió un archivo corrupto. Si solo quiere leerlo, use -synctex=-1. Donde la línea de órdenes queda fuera de alcance —una interfaz gráfica que compila con un botón—, la primitiva TeX \synctex=1 al principio de la fuente también lo activa. Pero esa vía solo entrega la forma comprimida: incluso \synctex=-1 produjo aquí main.synctex.gz en TeX Live 2024. Para texto plano, la línea de órdenes es la única puerta.

ValorArchivo escritoContenido
(none)no se escribe nada; ningún sentido funciona
-synctex=0igual que ausente; la desactivación explícita
-synctex=1main.synctex.gzcomprimido con gzip; la opción de a diario
-synctex=-1main.synctextexto plano; la forma de depuración
-synctex=2main.synctexnombre de aspecto plano, contenido gzip: confunde
-synctex=15main.synctexbits 1+2+4+8: soporte de formularios y compresión reforzada

Descomprimir el .synctex.gz y leer lo que hay dentro

El contenido es texto orientado a líneas; con gunzip -c main.synctex.gz se lee directamente. Hay cuatro secciones: preámbulo, contenido, postámbulo y post scriptum. El preámbulo lleva la versión y la tabla Input:, que numera desde 1 cada archivo que TeX abrió. No solo su main.tex, sino también article.cls, size10.clo, cada .sty y main.aux reciben una etiqueta, y ahí está la mitad de la explicación de lo abultado del mapa. Después, Magnification, Unit, X Offset e Y Offset definen el sistema de coordenadas: Unit:1 significa que todos los números siguientes van en sp (scaled points, una 65536ª parte de un punto), y X Offset:4736287 es exactamente una pulgada, el margen que TeX toma desde siempre en la esquina superior izquierda del papel.

terminal
$ gunzip -c main.synctex.gz        # abridged; ... marks omitted lines
SyncTeX Version:1
Input:1:/home/you/doc/./main.tex
Input:2:/usr/local/texlive/2024/texmf-dist/tex/latex/base/article.cls
Input:3:/usr/local/texlive/2024/texmf-dist/tex/latex/base/size10.clo
...
Input:10:/home/you/doc/./chap.tex
Output:pdf
Magnification:1000
Unit:1
X Offset:4736287
Y Offset:4736287
Content:
!962
{1
[1,17:4736286,46220574:26673152,41484288,0
(1,4:8799518,8865054:22609920,655359,0
x1,4:9330359,8865054
k1,4:31409438,8865054:11586343
...
)
]
}1

La sección de contenido es un registro de cajas anidadas. {1}1 es un pliego, es decir una página; los corchetes [] son una caja vertical y los paréntesis () una horizontal. Cada apertura tiene la forma etiqueta,línea:x,y:ancho,alto,profundidad, de modo que (1,4:8799518,8865054:22609920,655359,0 dice «una caja horizontal nacida en la línea 4 de la etiqueta 1, o sea main.tex». En el ejemplo anterior, la línea 4 de main.tex era \section{Forward and inverse}. El primer carácter de una línea nombra el tipo de registro: x la posición actual, k un kern, g espacio elástico, $ matemáticas, f una referencia de formulario de pdfTeX, v y h cajas verticales y horizontales vacías, y ! un desplazamiento en bytes para saltar al medio del archivo.

Registrar cada página con esa finura engorda el archivo. Para el artículo de dieciocho páginas del comienzo, el mapa comprimido pesaba 159.347 bytes y, descomprimido, 638.962 bytes: más de ocho veces el PDF, repartidos en 24.717 líneas. Por eso .synctex.gz no es un entregable sino un archivo de trabajo regenerable: métalo en el .gitignore y añádalo a los @generated_exts de latexmk para que la limpieza se lo lleve. La página de manual synctex(5) es tajante en un punto vecino: la estructura no debe considerarse pública, y nadie salvo la orden synctex y la biblioteca synctex_parser necesita analizarla. Leerla para entender un problema está bien; cimentar en ella una herramienta propia, no.

perl
# .latexmkrc -- keep -synctex=1 on every build, and clean the map up afterwards
$pdf_mode  = 1;
$pdflatex  = 'pdflatex -synctex=1 -interaction=nonstopmode -file-line-error %O %S';
@generated_exts = (@generated_exts, 'synctex.gz');

Ejecutar a mano la búsqueda directa y la inversa

La búsqueda directa (fuente → PDF) es synctex view; la búsqueda inversa (PDF → fuente) es synctex edit. Lo que su editor y su visor invocan tras sus botones son estas dos órdenes o un equivalente, así que cuando la búsqueda inversa en LaTeX falla, ejecutarlas directamente separa de golpe a los dos culpables posibles: un mapa defectuoso o un mal apretón de manos entre editor y visor. La búsqueda directa toma -i línea:columna:archivo y -o pdf, y responde con un número de página y un rectángulo.

terminal
$ synctex view -i 5:1:main.tex -o main.pdf
This is SyncTeX command line utility, version 1.5
SyncTeX result begin
Output:main.pdf
Page:1
x:153.195526
y:156.585541
h:133.768356
v:158.522720
W:343.711060
H:8.855677
before:
offset:-1
middle:
after:
SyncTeX result end

El par x e y es el punto que hay que mostrar; h, v, W, H son el borde izquierdo, la línea base, el ancho y el alto del rectángulo a resaltar. La unidad es el punto PDF, de modo que v:158.52 significa 158,52 pt por debajo del borde superior de la página. El visor toma esos números, se desplaza y hace destellar una banda de W por H. El sentido contrario se limita a devolver una coordenada.

terminal
$ synctex edit -o 1:153.19:156.58:main.pdf
This is SyncTeX command line utility, version 1.5
SyncTeX result begin
Output:main.pdf
Input:/home/you/doc/./main.tex
Line:5
Column:-1
Offset:0
Context:
SyncTeX result end

El argumento es -o página:x:y:pdf, y lo que vuelve es una ruta absoluta de archivo y un número de línea. El visor inserta ese Input: y ese Line: en la orden que lanza el editor. Lo llamativo es Column:-1. El formato podría representar una columna, pero los motores no escriben ninguna, así que la búsqueda inversa es en la práctica siempre de granularidad de línea. Por eso el editor deja el cursor al principio de la línea: no es un fallo de configuración.

Por qué el salto aterriza en una línea y no en la palabra que pulsó

Porque la unidad de correspondencia es una caja compuesta. TeX convierte un párrafo en una sola lista horizontal larga y solo al final la corta en líneas. Lo que SyncTeX recuerda son las cajas resultantes y la línea de origen que produjo cada una: ni palabras ni caracteres. Al medirlo, la asimetría salta a la vista: doce palabras cortas repartidas en doce líneas consecutivas sin línea en blanco se reducen a solo dos cajas de línea. Pregunte a la búsqueda directa por las líneas fuente 5 a 12 una tras otra: todas responden la misma coordenada.

terminal
# twelve short source lines 5..16, no blank line between them
$ for l in 5 6 7 8 9 10 11 12 13 14 15 16; do
>   printf "src %-3s " $l; synctex view -i $l:1:res.tex -o res.pdf | grep "^v:"
> done
src 5   v:230.405960
src 6   v:230.405960
src 7   v:230.405960
src 8   v:230.405960
src 9   v:230.405960
src 10  v:230.405960
src 11  v:230.405960
src 12  v:230.405960
src 13  v:242.361130
src 14  v:242.361130
src 15  v:242.361130
src 16  v:242.361130

Lo interesante es que el sentido inverso es algo más fino. Recorra synctex edit de izquierda a derecha sobre esa misma caja de línea: devuelve líneas fuente distintas según la posición horizontal, y a menudo varios candidatos por punto, de los que el visor suele quedarse con el primero. Así que la búsqueda directa es tosca y la inversa es fina. Al revés: en un párrafo donde una única línea fuente larga se plegó en ocho líneas compuestas, pulsar cualquiera de las ocho devolvía siempre la línea 3, porque nunca hubo más que una línea fuente que recordar. Caer a una palabra del objetivo dentro de una figura TikZ, en la expansión de una macro enrevesada o dentro de una tabla es la misma historia de granularidad de caja, no un fallo.

terminal
# same typeset line (baseline v=230.4), scanning left to right
$ for x in 135 185 235 310 360 435 460; do
>   printf "x=%-4s " $x; synctex edit -o 1:$x:229:res.pdf | grep "^Line:" | tr "\n" " "; echo
> done
x=135  Line:5
x=185  Line:5
x=235  Line:6 Line:7
x=310  Line:7 Line:8
x=360  Line:9 Line:10
x=435  Line:10 Line:11
x=460  Line:11 Line:12

De ahí se sigue una consecuencia práctica. Escriba la fuente como una única línea enorme y la resolución de SyncTeX se desploma a un solo punto para todo el párrafo. Corte por oraciones, o al menos en las fronteras de las cláusulas, y la búsqueda inversa vuelve a acertar. La forma de escribir que hace legibles los diffs del control de versiones y la que hace preciso a SyncTeX resultan ser la misma.

Por qué los números de línea se descuadran al usar \input

La respuesta corta es que \input no es en sí causa de ningún descuadre. Cada registro lleva una etiqueta además del número de línea, y la etiqueta indexa la tabla Input:. Un archivo hijo recibe su propia etiqueta, y sus números de línea son los de ese hijo. Medido: pulsar dentro de un capítulo traído con \input{chap} devolvía chap.tex como Input: y el número de línea interno como Line:. Encadene veinte capítulos y nada se suma.

Hay dos causas reales. La primera es un mapa caducado. Un .synctex.gz es la fotografía de una compilación: añada tres líneas al principio de chap.tex y lance la búsqueda inversa sin recompilar, y el mapa seguirá respondiendo Line:3 aunque el texto ya esté en la línea 6. Si el desfase es exactamente el número de líneas insertadas, casi con seguridad es eso. La segunda es la ruta absoluta. Lo que va a Input: es la ruta completa tal como estaba al compilar, así que mover el proyecto, abrirlo a través de un enlace simbólico o compilar dentro de un contenedor y verlo fuera apuntan al visor a una ruta que ya no existe. Cuando se abre el archivo equivocado —o no se abre nada— en vez de la línea equivocada, sospeche de esto.

La orden de búsqueda inversa, visor por visor

La búsqueda inversa se configura del lado del visor. Se le entrega una plantilla: cuando alguien pulse, rellena este número de línea y este nombre de archivo, y ejecuta esta orden. La molestia es que la sintaxis de los marcadores cambia según el visor. zathura usa llaves —%{line} e %{input}; Skim usa %line y %file; SumatraPDF y Okular usan %l y %f. La mayoría de las configuraciones copiadas de otro sitio fallan justo por eso: la orden es correcta y solo los marcadores no encajan.

VisorPlataformaMarcadores de línea y archivo
zathuraLinux / BSD%{line} e %{input}, con set synctex-editor-command
SkimmacOS%line y %file, en Preferences ▸ Sync ▸ Preset: Custom
SumatraPDFWindows%l y %f, en el campo inverse search de Settings ▸ Options
OkularLinux / Windows%l y %f, en Preferencias ▸ Editor (para Kile, kile --line %l)
Adobe Acrobat / Readertodassin soporte de SyncTeX; la búsqueda inversa no está disponible
ini
# zathura (~/.config/zathura/zathurarc)
set synctex true
set synctex-editor-command "nvim --headless -c 'VimtexInverseSearch %{line} %{input}'"

# Skim  -- Preferences > Sync > Preset: Custom
Command:   nvim
Arguments: --headless -c "VimtexInverseSearch %line '%file'"

# SumatraPDF -- Settings > Options > inverse search command-line
cmd /c start /min "" nvim --headless -c "VimtexInverseSearch %l '%f'"

# Okular -- Settings > Configure Okular > Editor
kile --line %l

Disparar la búsqueda directa desde el editor es sencillo: en TeXShop con Skim es Cmd-clic en el PDF, y Mayús-Cmd-clic para el sentido contrario. TeXstudio usa Ctrl-clic, o las entradas de menú «Ir al PDF» y «Saltar a la fuente». VS Code con LaTeX Workshop usa Ctrl/Cmd+Alt+J. Conviene nombrar aquí una trampa propia de macOS: el /usr/bin/vim que viene con macOS está compilado como -clientserver, de modo que no existe canal alguno para que algo externo llame de vuelta al editor, y el fragmento habitual de búsqueda inversa no hace absolutamente nada, en silencio. El remedio es MacVim, el Vim de Homebrew o Neovim.

Qué ocurre en la ruta DVI (pLaTeX / upLaTeX → dvipdfmx)

Primero la conclusión: con los ajustes por omisión no hay nada que hacer, y las coordenadas coinciden con las de la ruta directa a PDF. -synctex=1 se le pasa al motor (platex o uplatex), no al conversor. El motor escribe el .synctex.gz junto al DVI, y su preámbulo dice Output:dvi en lugar de Output:pdf. Ejecutar después dvipdfmx no toca ese mapa en absoluto: comparar el archivo antes y después con cmp lo mostró aquí idéntico byte a byte, y dvipdfmx ni siquiera tiene una opción -synctex. De paso, el dvipdfmx de TeX Live 2024 es un enlace simbólico a xdvipdfmx: el mismo binario que el conversor usado para XeTeX.

terminal
# the same source, both routes, same forward-search question
$ pdflatex -synctex=1 cmp.tex && synctex view -i 3:1:cmp.tex -o cmp.pdf
h:133.768356   v:137.554138

$ latex -synctex=1 cmp.tex && dvipdfmx cmp.dvi
$ synctex view -i 3:1:cmp.tex -o cmp.pdf
h:133.768372   v:137.554153

# the two agree to about 2e-5 pt -- nothing needs reconciling

¿Para qué sirve entonces synctex update? Para exactamente lo que dice su manual —actualizar el archivo SyncTeX una vez aplicado un filtro de dvi/xdv a pdf— y solo hace falta cuando esa conversión recibió una ampliación o un desplazamiento. A -m, -x e -y se les dan los mismos valores que al filtro. Lo interesante es la implementación: synctex update no reescribe el mapa. Ejecutarlo con -x 20mm y comparar el archivo byte a byte antes y después mostró que se limita a añadir un bloque gzip después de la línea final Post scriptum:. Descomprimido, ese bloque contiene una sola línea: X Offset:20mm. Es decir, la cuarta sección del formato es el lugar donde un conversor posterior pega una corrección del sistema de coordenadas, como quien pone una nota adhesiva. En el trabajo diario ptex2pdf o latexmk ejecutan esta cadena por usted y el tema no llega a plantearse.

Cuando SyncTeX no hace nada: qué mirar, y en qué orden

Empiece comprobando que hay un .synctex.gz en la misma carpeta que el PDF. Si no lo hay, a la compilación le falta -synctex=1. Lo fácil de pasar por alto aquí es la orden de compilación que trae el editor. La herramienta PDFLaTeX que Kile incluye, por ejemplo, no lleva -synctex=1 entre sus opciones por omisión, y esa omisión es la mayor causa de «lo configuré y no se sincroniza nada». Marcar una casilla de SyncTeX en las preferencias de un editor no siempre cambia la orden que realmente se ejecuta.

  • ¿Hay mapa? Busque el .synctex.gz con ls. Si falta, añada -synctex=1 a la orden de compilación, y considere sospechosos los ajustes por omisión del editor.
  • ¿Se han separado el PDF y el mapa? -output-directory no da problemas, porque ambos caen juntos en la carpeta de salida, pero copiar el PDF solo deja atrás el mapa y no ocurre nada. Medido aquí: tras copiar únicamente main.pdf fuera de build/, synctex view terminó en silencio.
  • ¿Está caducado el mapa? ¿Recompiló después de guardar? Si el desfase iguala el número de líneas recién insertadas, queda zanjado. Ejecutar latexmk con -pvc, de modo que cada guardado recompile, elimina casi por completo este fallo.
  • ¿Compuso realmente el documento que está mirando? Compilar un archivo de capítulo por su cuenta da un mapa que describe el PDF de ese capítulo, no el del libro. Compruebe que el ajuste de archivo maestro o documento raíz de su editor apunte donde usted cree.
  • ¿Soporta SyncTeX el visor? Adobe Acrobat / Reader no puede hacer búsqueda inversa en absoluto. Cambie a Skim (macOS), SumatraPDF (Windows) u Okular y zathura (Linux).
  • ¿Son correctos los marcadores? Confundir %{line}, %line y %l cuesta de detectar precisamente porque el resto de la orden es correcto.
  • Parta el problema en la línea de órdenes. Ejecute synctex view y synctex edit directamente. Si responden bien, el mapa está sano y el fallo está en el apretón de manos entre editor y visor. Tenga en cuenta que synctex devuelve 0 aunque no encuentre nada, así que un guion debe examinar la salida y no el código de retorno.

Para terminar, el bucle que convierte a SyncTeX de ajuste en hábito de corrección. Leer el PDF, pulsar una palabra que molesta, aterrizar en la fuente, corregirla, guardar, recompilar y volver con la búsqueda directa al punto recién corregido. Cuando ese bucle gira con soltura, el tiempo dedicado a rastrear una fuente larga en busca del lugar que hay que tocar cae a cero. El nombre que Jérôme Laurens dio a su obra —Synchronize TeXnology— suena grandilocuente, pero lo que de verdad entrega es solo eso: no tener que buscar nunca más.