No description
  • Rust 71.4%
  • Lua 26.7%
  • Nix 1.9%
Find a file
Kasper J. Seweryn 60b5a02ebf
All checks were successful
Test / test (push) Successful in 21m50s
build: only texpresso's XeTeX, without the viewer's mupdf and SDL
The plugin's closure is 274 MiB instead of 1.13 GiB, and building it no
longer fetches mupdf and SDL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 15:22:36 +02:00
.forgejo/workflows feat: nixvim module, plugins.tex 2026-10-19 01:19:02 +02:00
bench bench: keys that change nothing on the page; :Tex build once settled 2026-10-18 22:07:13 +02:00
dev feat: the page in the theme's colours; the preview follows the cursor 2026-10-18 19:03:49 +02:00
docs docs: new demo and screenshots; benchmarks again 2026-10-18 22:23:44 +02:00
examples docs: new demo and screenshots; benchmarks again 2026-10-18 22:23:44 +02:00
lua/tex fix: fonts after typing; pages marked changed; the preview's bar 2026-10-18 22:23:43 +02:00
nix build: only texpresso's XeTeX, without the viewer's mupdf and SDL 2026-10-19 15:22:36 +02:00
plugin feat: the page in the theme's colours; the preview follows the cursor 2026-10-18 19:03:49 +02:00
src fix: :Tex build again keeps the \include'd files 2026-10-18 23:52:51 +02:00
tests fix: fonts after typing; pages marked changed; the preview's bar 2026-10-18 22:23:43 +02:00
.envrc feat: tectonic.nvim — LaTeX with tectonic in Neovim 2026-10-18 01:51:13 +02:00
.gitignore feat: tectonic.nvim — LaTeX with tectonic in Neovim 2026-10-18 01:51:13 +02:00
Cargo.lock feat: texlab next to tex.nvim, which drops what texlab does 2026-10-18 18:57:06 +02:00
Cargo.toml build: a smaller server, 9.7 MiB instead of 14.5 2026-10-19 15:22:31 +02:00
devenv.lock feat: tectonic.nvim — LaTeX with tectonic in Neovim 2026-10-18 01:51:13 +02:00
devenv.nix feat: texlab next to tex.nvim, which drops what texlab does 2026-10-18 18:57:06 +02:00
devenv.yaml feat: tectonic.nvim — LaTeX with tectonic in Neovim 2026-10-18 01:51:13 +02:00
flake.lock feat: tectonic.nvim — LaTeX with tectonic in Neovim 2026-10-18 01:51:13 +02:00
flake.nix feat: nixvim module, plugins.tex 2026-10-19 01:19:02 +02:00
LICENSE feat: tectonic.nvim — LaTeX with tectonic in Neovim 2026-10-18 01:51:13 +02:00
README.md feat: nixvim module, plugins.tex 2026-10-19 01:19:02 +02:00
stylua.toml feat: tectonic.nvim — LaTeX with tectonic in Neovim 2026-10-18 01:51:13 +02:00

tex.nvim

Write LaTeX in Neovim and see the page as you type. Every keystroke is typeset at once — only from where you typed, with TeXpresso's XeTeX — and the page you're on is redrawn next to your source, with what changed since your last save marked. Errors and warnings show up as diagnostics; jump from the source to the page and back with SyncTeX. :Tex build writes the PDF with Tectonic.

Warning

This project is vibe-coded. I need LaTeX that works, every day, in my own editor — and I don't have the time to implement it, maintain it and fix its issues myself. Vibe-coding is what makes it exist at all. If that's something that's triggering you, then in words of Edsel Dope: I don't give a fuck about you, or your point of view, or your stupid selfish attittude. Cause it seems, to be, that you never really gave a fuck about me.

Typing with the page following, the change marked, pages, search, links, the page in the colour scheme's colours, the build log and label completion

What you get

  • The page as you type. The editor's text is typeset as it changes, saved or not: a key typed shows up on the page in about 20 ms, on a 10-page document as on a 100-page one (see Performance). TeX doesn't start over: it goes on from just before what changed.
  • :Tex build writes the PDF with tectonic, next to the main file (or in build/ for a Tectonic.toml project, built with tectonic -X build).
  • Diagnostics from the build: TeX errors, overfull and underfull boxes, and LaTeX warnings such as undefined references and citations, each on the file and line it comes from — included files too. A problem in an included file also shows on the \input (\include, \subfile, \import…) line that pulls it in, all the way up to the main file.
  • A preview of the pages in a vertical split, drawn to fill the window, shown while you're in a TeX file (and hidden when you're not), on the page you're typing on, with how long the last change took to typeset and draw in its bar, in your colour scheme's colours (c: as printed). j/k (and h/l) change pages. Search its text with /, and follow its links (click, or <Tab> and <CR>).
  • The log in the preview when you save a document with errors: the problems (<CR> goes to one) and what TeX printed.
  • Changes marked. The lines that changed since your last save are tinted, with a bar in the margin, and removed lines get a marker. Text that only moved down the page isn't marked.
  • SyncTeX both ways. The preview follows your cursor: it shows the line you're on; a whole environment on its \begin or \end; where a file begins on its \input or \include line; and, in a .bib file, the entry under the cursor in the bibliography. Double-click (or <CR>) in the preview to jump to the source.
  • References and citations settle by themselves: the .aux is read back, and BibTeX runs when the citations or the .bib files change (as they are in the editor).
  • texlab next to it, set up for you when it's installed (recommended): completion of labels, citations, commands, environments and file names, hover, go to definition, references, rename, symbols and formatting (with tex-fmt when it's installed). tex.nvim leaves all that to texlab. TeX buffers are indented with 2 spaces.
  • Progress of :Tex build through the language server, so fidget.nvim and other progress plugins show it, a spinner in the preview, and a statusline component.

Screenshots

Taken in Rio with Neovim 0.13 and the ayu colorscheme — the dev setup of this repository, recorded by media (see Development).

Overview Search
Typed, not saved: the new sentence is marked in green Searching the PDF
Following the cursor A .bib entry
The cursor on \begin{itemize}: all of the list In the .bib file: the entry in the bibliography
Links The page as printed
A link selected with <Tab> (it goes to page 3) c: the page as printed (it's in the colour scheme's colours otherwise)
Build log Completion
A mistake, saved: the problem, and what TeX printed Labels from the whole document (texlab)

Getting started

You need Neovim 0.12 or newer (0.13 draws the preview with the built-in vim.ui.img), TeXpresso's texpresso-xetex (it typesets), tectonic (its packages, BibTeX and :Tex build), Linux or macOS, and a terminal that shows images with the kitty graphics protocol (kitty, Ghostty, Rio, WezTerm). The language server is written in Rust and built with the plugin. If you install with Nix, all of that except the terminal is included.

We recommend installing texlab too (and tex-fmt, which it formats with): tex.nvim registers and enables it by itself, with settings that fit (see texlab), so completion, hover, rename and formatting work without more configuration.

With Nix (flake):

{
  inputs.tex-nvim.url = "git+https://forge.wvffle.net/wvffle/tex.nvim";

  # Either add the overlay and use pkgs.vimPlugins.tex-nvim …
  nixpkgs.overlays = [ inputs.tex-nvim.overlays.default ];

  # … or add the package directly, e.g. with home-manager
  programs.neovim.plugins = [
    inputs.tex-nvim.packages.${pkgs.stdenv.hostPlatform.system}.default
  ];
}

With nixvim:

{
  imports = [ inputs.tex-nvim.nixvimModules.default ];

  plugins.tex = {
    enable = true;
    # passed to require("tex").setup(), see Configuration
    settings = {
      indent = 4;
      outdir = "build";
    };
    # texlab and tex-fmt are installed too; `texlab = false;` leaves them out
  };
}

With lazy.nvim (needs cargo to build the server):

{
  url = "https://forge.wvffle.net/wvffle/tex.nvim",
  build = "cargo build --release",
  ft = { "tex", "plaintex", "bib" },
  opts = {},
}

Then open a .tex file and the preview (<localleader>lp). Run :checkhealth tex if something doesn't work — it checks the server, texpresso-xetex, tectonic, texlab and image support.

Using it

The keys below are local to TeX buffers and to the preview, and can be changed (see Configuration). <localleader> is your maplocalleader, \ unless you've set it.

In the source

key what it does
<localleader>lb build now (saving builds too)
<localleader>lp open / close the preview
<localleader>lf show the cursor's line in the preview

With texlab, the usual language server keys work too: completion, K, grn (rename everywhere), grr (where it's used), <C-]> (where it's defined), gO (symbols).

In the preview

key what it does
j / l next page
k / h previous page
gg / G first / last page
]c / [c next / previous page with changes
<CR>, double-click go to the source of what's under the cursor / mouse
click follow the link under the mouse
<Tab> / <S-Tab> select the next / previous link on the page (<CR> follows it)
<C-o> back to where you followed a link from
/ search the text of the PDF
n / N next / previous match
<Esc> clear the search and the selected link
+ / - / = zoom in / out / fit the page again
w fit the page width (scroll for the rest) / the whole page
<C-d> / <C-u> scroll; at the bottom or top, go to the next or previous page
d show / hide changes
p pin: compare against the version shown now until you unpin
c the page in your colour scheme's colours / as printed
e the build log / the PDF (after a failed build)
q close

The bar above the preview shows the page, the last build (✓ 1.2s, ⠹ building, ✗ 2 errors), the pages with changes (Δ 3,4), whether the comparison is pinned, the search (/needle 2/5) and where the selected link goes.

When a build fails, the preview shows what went wrong instead of the page: the problems first (<CR> on one goes there), then tectonic's output. e switches to the page and back; saving the fixed document brings the page back.

Search is case-insensitive unless the search has capitals, and finds text across line breaks and hyphenated words.

The page is drawn in your colour scheme's colours: the paper in Normal's background and the ink in its foreground, exactly; other colours keep their hue. c (or :Tex colors) shows it as printed and back. To use other colours, give them: preview.page_colors = { bg = "#0c0e13" }. Some terminals draw text cells a shade off from the colour asked for (Rio, for instance), while images are shown as they are: the page then looks a shade off from the editor around it, and giving the shade the terminal shows matches them.

The preview follows you: it shows the page with the line under your cursor, marked in blue, as you move (preview.follow_cursor) and after each save (preview.forward_on_save). On an \input or \include line it shows where that file begins, on \begin{…} or \end{…} all of the environment, on \bibliography the bibliography, and in a .bib file the entry the cursor is in (when it's cited). It closes when the last window showing the document closes.

Changes

The page is compared with what it was when you last saved (or when the document was first typeset), so the marks show what you changed since. To see everything that changed since some point instead, pin the comparison with p (or :Tex pin) and unpin it when you're done.

The comparison works on the rendered page, line by line: text that moved down because something was inserted above it isn't marked, a changed word in an otherwise unchanged line is marked on its own, and pages whose content didn't change at all are skipped.

Commands

:Tex <command>: build, preview, close, toggle, forward, diff, colors, log, search [text], pin, unpin, format (texlab) and info.

texlab

tex.nvim typesets, previews, reports TeX's errors and builds; everything a language server does with the source itself is texlab's. When texlab is installed, tex.nvim registers it with vim.lsp.config("texlab") and enables it, with these settings:

  • formatting with tex-fmt (latexFormatter and bibtexFormatter), when tex-fmt is installed; a tex-fmt.toml next to your document (or in a parent directory, or ~/.config/tex-fmt/tex-fmt.toml) sets how it formats, e.g. tabsize = 4 (texlab doesn't pass the buffer's indent on);
  • no build of its own (build.onSave = false, forwardSearchAfter = false): tex.nvim typesets as you type and :Tex build writes the PDF.

Settings you give texlab yourself, before or after setup() (or that nvim-lspconfig gives it), win. texlab = false leaves texlab alone; a table is more configuration for it, e.g. texlab = { settings = { texlab = { chktex = { onEdit = true } } } }.

vim.lsp.buf.format() (or :Tex format) formats with texlab, and so does conform.nvim when it falls back to language servers (default_format_opts = { lsp_format = "fallback" }).

Configuration

Call setup only for what you want to change. These are the defaults:

require("tex").setup({
  server = nil,              -- the tex-nvim server (Nix and `cargo build` set this up)
  engine = nil,              -- texpresso-xetex (default: on $PATH; Nix sets it up)
  tectonic = nil,            -- the tectonic executable (default: tectonic on $PATH)
  fsync = nil,               -- false: don't fsync TeX files on write (see Good to know)
  fast_bundle = true,        -- :Tex build reads tectonic's cached files directly (~70 ms a build)
  cache_dir = nil,           -- where :Tex build and BibTeX keep their files (default: stdpath("cache")/tex.nvim)
  extra_args = {},           -- more arguments for tectonic -X compile / -X build (:Tex build)
  outdir = nil,              -- where :Tex build puts PDFs of single documents, relative to the main file
  main = nil,                -- main file of single documents (default: detected, see below)
  output = nil,              -- which [[output]] of Tectonic.toml to build (default: the first PDF)
  keep_logs = true,          -- keep tectonic's .log, for LaTeX warnings
  enable_synctex = true,     -- add `synctex = true` to Tectonic.toml when it's missing
  filetypes = { "tex", "plaintex", "bib" },
  indent = 2,                -- spaces in TeX buffers (false: leave your settings)
  texlab = true,             -- register and enable texlab when it's installed (see texlab)
  keymaps = {                -- set any of these to false to turn it off
    build = "<localleader>lb",
    preview = "<localleader>lp",
    forward = "<localleader>lf",
  },
  preview = {
    width = 0.5,             -- columns, or a fraction of the editor width
    fit = "page",            -- "page" or "width"
    diff = true,             -- mark what changed since the last save
    auto_open = true,        -- shown while you're in a TeX file, hidden when not (until you close it)
    forward_on_save = true,  -- after a save, show the cursor's line
    follow_cursor = true,    -- … and as the cursor moves (an \input line: where that file begins)
    focus = false,           -- move into the preview when it opens
    page_colors = "theme",   -- "theme": Normal's colours, "original": as printed, or { bg = …, fg = … }
    show_log = true,         -- show the log when you save a document with errors
    backend = "auto",        -- "auto", "ui.img" or "kitty"
    colors = {
      added = "#3fb950",
      removed = "#f85149",
      mark = "#58a6ff",      -- the cursor's line, the selected link, the current match
      search = "#d29922",    -- the other matches
      alpha = 0.22,          -- opacity of the tint over changed lines
    },
  },
  preview_keymaps = {        -- a key, a list of keys, or false
    next_page = { "j", "l" },
    prev_page = { "k", "h" },
    first_page = "gg",
    last_page = "G",
    next_change = "]c",
    prev_change = "[c",
    zoom_in = "+",
    zoom_out = "-",
    zoom_reset = "=",
    fit_width = "w",
    scroll_down = "<C-d>",
    scroll_up = "<C-u>",
    toggle_diff = "d",
    pin = "p",
    colors = "c",
    log = "e",
    search = "/",
    next_match = "n",
    prev_match = "N",
    clear = "<Esc>",
    next_link = "<Tab>",
    prev_link = "<S-Tab>",
    back = "<C-o>",
    backward = { "<CR>", "<2-LeftMouse>" }, -- follows a selected link first
    click = "<LeftMouse>",
    close = "q",
  },
})

The server is registered with vim.lsp.config() under the name tex, so you can also change how it starts there, e.g. vim.lsp.config("tex", { root_markers = { ".git" } }).

To show the state of the document in your statusline:

vim.o.statusline = "%f %m%=%{v:lua.require'tex'.status()} "
-- shows e.g. "✓ 12 ms · 3 warnings", "⟳ typesetting", "✗ 1 error"

Good to know

Main files. Without a Tectonic.toml, the document a file belongs to is the file itself if it has a \documentclass; the file named in a % !TEX root = ../main.tex comment at its top; or a .tex file with a \documentclass in the same directory (or up to two above) that mentions it. Set main to choose yourself.

SyncTeX in Tectonic.toml projects works in the preview always; for the PDF :Tex build writes, it has to be turned on in the project, with synctex = true in the [[output]]. When it isn't there, the first build adds it (and says so); the rest of the file is left as it is. Set enable_synctex = false to keep your file untouched.

Images need a terminal that speaks the kitty graphics protocol. Inside tmux, enable set -g allow-passthrough on. Other terminals get a message in the preview instead. A floating window over the preview hides the image while it's there (with vim.ui.img), so the float stays readable.

Errors don't stop the page. TeX goes on after an error, as with -interaction=nonstopmode, so the page keeps updating while what you're typing is half-written; the errors are diagnostics. Saving a document with errors shows the log in the preview (e goes back to the page).

Saving doesn't typeset anything — that happened as you typed — it only marks the changes from there on, and shows the cursor's line (or the log). By default Neovim fsyncs the file it writes, which takes 10–25 ms on some file systems (btrfs); fsync = false skips that for TeX files only (your 'fsync' is put back right after the write), at the price of what fsync protects against: losing the file in a power cut right after saving.

Where the files go. Nowhere, while you type: TeX reads the editor's text through the server, and what it writes (.aux, .log, the pages) stays in memory. Your directory only gets the PDF and its SyncTeX file, when you run :Tex build: next to the main file, or in build/ for Tectonic.toml projects.

Packages come from tectonic's bundle. The first time a document uses a package tectonic hasn't downloaded yet, TeX waits while it does.

Completion comes from the language server, so it shows up in whatever you use for completion: Neovim's own (vim.lsp.completion.enable()), blink.cmp, nvim-cmp… Labels and citation keys are collected from the whole document — the files the main file includes, and its .bib files — using the text of open buffers, so new labels can be referenced before saving.

How it works

 Neovim                                       tex-nvim (Rust)
 ┌────────────────────────┐   LSP (stdio)    ┌─────────────────────────────────┐
 │ .tex buffers           │ ── didChange ──▶ │ session: texpresso-xetex,       │
 │   diagnostics          │ ◀─────────────── │   snapshots, rollback           │
 │                        │                  │ XDV page → image (vello_cpu)    │
 │ preview split          │ tex/render ────▶ │   + diff with the saved version │
 │   kitty placeholders / │ ◀─ buildFinished │ SyncTeX, search, links          │
 │   vim.ui.img           │                  │ tectonic (:Tex build)           │
 └────────────────────────┘                  └─────────────────────────────────┘
  • The server is a language server written in Rust (src/). Neovim starts it like any other (vim.lsp.enable("tex")), and sends it the text of every change.
  • TeX runs as you type, from where you typed. Each document has a session running TeXpresso's XeTeX (texpresso-xetex), which reads every file through the server and writes everything to it (src/engine, a port of TeXpresso's own driver): the server serves the editor's text, and keeps the pages, the log and the .aux in memory. When asked, the engine forks: the parent stops where it is — a snapshot — and the child goes on. The server knows how far each snapshot had read each file, so a change goes on from the last snapshot that hadn't read the changed bytes yet. A change on page 50 doesn't typeset pages 1 to 49 again.
  • Snapshots are placed for typing. A fork costs a few milliseconds (the engine maps ~150 MB), so there's none on the way to the page you're waiting for, except one right before the change — the next key there starts from it. There are snapshots before the main file and at \begin{document} (a change in the body never goes through the preamble again), a few spaced in time over the rest, and when the cursor moves to another line, the server makes one right before it while you're not typing, so the first key there is as quick as the next ones.
  • The page you're on comes first. As soon as the page the preview shows is typeset again, it's drawn and sent to Neovim; the rest of the document follows (the preview keeps the previous version of the pages after it meanwhile). Then the .aux, .toc & co. are read back, BibTeX runs if the citations changed, and TeX goes on from where they were read until they settle; the diagnostics come from the log.
  • Pages are drawn from the XDV — what XeTeX writes, before it becomes a PDF — with vello_cpu: glyph runs of OpenType fonts, the Computer Modern & co. math fonts (TFM metrics and Type1 outlines through pdftex.map), rules, colours — and graphics: XeTeX's drivers write them as dvipdfmx specials, which the server follows like dvipdfmx would — PDF drawing operators (TikZ, pgf), with their transformations, clips, opacity and shadings, and images (\includegraphics of PNG, JPEG and PDF files; anything else shows as a grey box). A page of text is drawn in about 2 ms; it's drawn once at the window's size and kept, so scrolling and zooming only crop it again. The image is padded to whole terminal cells so the terminal never stretches it.
  • The diff splits both renders of a page into blocks of ink (usually lines of text) and lines them up, like diff does with lines. Blocks are compared by their column profile (ink per column), which barely changes when a line moves by a fraction of a pixel. Unmatched blocks are marked; matched blocks at the same place are compared column by column to mark just the changed words. Which pages changed comes from the pages' XDV, without drawing them.
  • SyncTeX data comes from the engine too: forward search finds the boxes typeset from a line (or the nearest line after it), backward search the innermost line box under the click, and the closest position in it.
  • Search reads the text back from the glyphs (the fonts' character maps and glyph names); TeX doesn't draw spaces, so they're put back from the gaps between glyphs, and a hyphen at the end of a line joins the word across it. Links are hyperref's annotations and destinations, read from the XDV.
  • :Tex build runs tectonic in a mirror of the project where the files open in Neovim are as they are in the editor, with its downloaded files read straight from its cache and a reduced font map (both save tectonic time on every run).
  • texlab runs next to it (completion, hover, rename, formatting…); the two don't offer the same things.
  • Images are drawn with vim.ui.img on Neovim 0.13, or with kitty's Unicode placeholders on 0.12.

Performance

bench (nvim --headless --clean -l bench/run.lua [pages]) generates documents (a section with text, an equation and a list per page), opens them with the plugin and times what you do, through the real server. It runs Neovim with a 240x70 UI attached, so the preview is 120 columns wide (cells of 9x19 px: an image of about 1080x1250 px) and images really go out to the UI, as they would to a terminal; the terminal decoding them isn't included. Times are medians, measured on a laptop (Ryzen 5 7640U, on mains power, the "balanced" profile, a browser running) with Neovim 0.13. The page is drawn in the colour scheme's colours, as by default.

10 pages 100 pages
keystroke → page on screen (same line) 24.3 ms 35.1 ms
keystroke → page typeset (same line) 10.1 ms 22.0 ms
keystroke → page on screen (first key elsewhere) 28.3 ms 41.3 ms
keystroke → page typeset (first key elsewhere) 5.8 ms 21.6 ms
typeset from scratch 258.8 ms 659.9 ms
open the preview 7.7 ms 6.0 ms
next page (not rendered yet) 8.0 ms 7.4 ms
previous page (rendered before) 6.1 ms 5.7 ms
scroll (fit width) 6.1 ms 6.2 ms
:Tex build (tectonic, the whole PDF) 198.1 ms 391.1 ms
forward search 0.3 ms 1.3 ms
backward search 0.1 ms 0.1 ms

The keystroke rows time a key typed in the middle of the document, with the preview on that page, until the new page is on screen with the changes marked — typesetting included; the "typeset" rows stop when the server has the page. A key that changes nothing on the page (a space at the end of a line) isn't counted: there's nothing to show. "Same line" is typing one key after another; "first key elsewhere" is the first key after the cursor moved to another page (and the preview followed). The size of the document doesn't matter: only what comes after the change, up to the end of its page, is typeset. Before this, a save cost a tectonic build and then a render: 188 ms (10 pages) and 399 ms (100 pages), and 517 ms and 897 ms before that work began.

What keeps these low:

  • TeX goes on from just before the change (see How it works), and stops caring about the rest of the document until the page you're on is shown.
  • No fork on the way. A snapshot costs a fork of a ~150 MB process (a few ms): the page you wait for gets at most one, right before the change. A rollback kills the snapshot's child first and the others once TeX runs again: killing them all at once made the resumed one wait ~35 ms for its child to exit.
  • Ready where the cursor is. When the cursor moves to another line, the server gets TeX there while you're not typing, and leaves it waiting: the first key goes on from there without a rollback.
  • Pages are drawn from the XDV, without making a PDF (~2 ms a page).
  • Pages are rendered once per size. Scrolling and zooming back crop a cached page; the comparison of a page with its previous version is cached too. While the preview is idle, the pages before and after the one shown are rendered ahead.
  • Rendering uses a few threads, with one rendering context kept for the life of the server (starting one is what costs most).
  • Little to send. The PNG is encoded with a fast filter and comes with the notification; Neovim sends it to the terminal in one write and only points the placeholder text at the new image.

What was tried and left out: a precompiled preamble (a format with the document's preamble dumped) can't work with XeTeX, which can't dump the OpenType fonts LaTeX loads in the preamble — and starting XeTeX and loading its format, not the preamble, was most of a build anyway; TeXpresso's own snapshot policy (a fork every page or so, 16 fences behind a change) spent most of its time forking.

To profile the server, run bench with TEX_NVIM_SERVER=<a wrapper running perf record … target/release/tex-nvim>; the render results carry a breakdown of their time (timings). TEX_NVIM_ENGINE_LOG=<file> keeps what the engine prints.

Development

The development environment comes from devenv: run devenv shell --no-tui or direnv allow. It has Neovim 0.13 nightly (and 0.12 as nvim-stable), Rust, texpresso-xetex, tectonic, texlab, tex-fmt, the formatters, the LaTeX treesitter parser and the ayu colorscheme.

dev [file]          # build the server, open the manual (copied to dev/workdir) with the dev config
unit [filter]       # Rust tests, then the Neovim tests on Neovim nightly
unit-stable         # … the Neovim tests on Neovim 0.12
bench [pages]       # time typing, the preview, SyncTeX and formatting (default: 10,100 pages)
media               # record the demo and the screenshots (Rio, in a headless compositor)
nix build           # build the plugin
nix flake check     # build it, with the Rust tests

dev copies examples/ to dev/workdir/ (examples it hasn't copied yet), so you can edit there without touching the repository; rm -rf dev/workdir to start over. It opens examples/manual: this plugin's manual, as a LaTeX project that uses what it describes (chapters \included from the main file, a bibliography, a screenshot, TikZ pictures, tables). examples/project is a Tectonic.toml project.

media runs Rio (with its config in docs/rio.toml: no title or tab bar) in cage with a headless 2560x1440 output at scale 2 (sharp when zoomed; nothing shows on your screen), where Neovim plays docs/record.lua on a copy of examples/demo: it types, saves, flips pages, searches… and takes the screenshots (grim) and the recording (wf-recorder, lossless), which end up in docs/screenshots as lossless WebP.

The Neovim tests are plain Lua files in tests/, run against a real server and tectonic. Tectonic downloads its bundle on first use, so they need the network. CI runs the format checks and both test suites on every push.

License

GPL-3.0-or-later