LaTeX Real-Time Preview
A VS Code extension that provides fast, approximate real-time preview of LaTeX documents without requiring a TeX installation. It treats LaTeX as structured markup (like Markdown) rather than a full typesetting language, delivering instant visual feedback as you type.
Motivation
Existing LaTeX previewers (e.g., LaTeX Workshop) require a full TeX distribution, take seconds to compile, and break entirely on syntax errors. This extension trades exact layout fidelity for speed, error tolerance, and immediate feedback -- you get a live, readable rendering of your document the moment you start typing.
Features
- Real-time preview -- side panel updates as you type, with sub-100 ms latency for prose
- No TeX installation required -- JS-based parser plus KaTeX for math
- Error tolerant -- continues rendering even when your LaTeX has syntax errors
- Incremental block rendering -- only the edited block is re-parsed and re-rendered; the rest of the preview is untouched, so a 10 000-line document stays snappy
- Math-aware typing -- when the cursor is inside
$...$, $$...$$, \(...\), \[...\], or a math environment, re-render is debounced longer (default 5 s) and flushes the moment the cursor leaves the math, so a single render covers an entire equation instead of one per keystroke
- Refresh-mode toggle -- switch between "on type" and "on save" from the toolbar; saving the file always triggers a refresh regardless of mode
- Refresh-now button -- forces a full re-render on demand
- Viewport-aware math -- KaTeX renders math when it scrolls into view; cached by source so re-typing the same expression is free
- Bidirectional scroll sync -- scroll in the editor or the preview, the other follows; preview snaps to top while the editor is in the preamble
- Double-click to jump -- double-click any element in the preview to jump to its source line (works across files)
- Theming -- light, dark, and sepia backgrounds; configurable font family and size
- Multi-file projects --
\input and \include are resolved and inlined recursively, with file watching for live updates
- Bibliography support -- parses
.bib files and resolves \cite, \citet, \citep to author-year format
- Cross-references --
\label, \ref, \eqref, \autoref, \cref resolved to numbered links
- Custom macros --
\newcommand, \renewcommand, \def, \DeclareMathOperator are expanded
- TikZ diagrams -- rendered asynchronously; uses local TeX if available, falls back to a built-in WASM renderer (node-tikzjax)
- Rich LaTeX support -- sections, math environments, lists, theorems, tables, figures, footnotes, verbatim blocks, and more
Usage
- Open a
.tex file in VS Code
- Press
Ctrl+K V (or Cmd+K V on macOS), or click the preview icon in the editor title bar
- A preview panel opens to the right and updates in real-time
The toolbar at the top of the preview panel lets you:
- switch background theme (Light / Dark / Sepia)
- adjust font size and family
- toggle refresh mode (On Type / On Save)
- force a full re-render (Refresh Now)
- open the compiled PDF
Settings
| Setting |
Type |
Default |
Description |
latexPreview.debounceMs |
number |
100 |
Delay (ms) before re-parsing after a non-math edit |
latexPreview.mathDebounceMs |
number |
5000 |
Delay (ms) before re-rendering when the cursor is inside math. Render also fires on save and when the cursor leaves the math span |
latexPreview.refreshMode |
string |
"onType" |
"onType" re-renders on every keystroke (after debounce); "onSave" re-renders only on save. Save always triggers a refresh in either mode |
latexPreview.scrollSync |
boolean |
true |
Enable bidirectional scroll sync |
latexPreview.mathRenderer |
string |
"katex" |
Math rendering engine (currently only KaTeX) |
latexPreview.tikzRenderer |
string |
"auto" |
TikZ backend: "auto" uses local TeX if available, falls back to WASM; "local" requires a TeX install; "wasm" uses the built-in renderer only |
latexPreview.tikzTimeout |
number |
30000 |
Timeout (ms) per TikZ diagram |
Architecture
User edits .tex file
|
v
[VS Code Extension Host]
onDidChangeTextDocument
| (debounce: debounceMs, or mathDebounceMs if cursor in math)
v
[Block Cache]
splitIntoBlocks --> content-stable block IDs (djb2 + ordinal)
diffBlocks --> insert / update / move / delete patches
|
v
[Webview Panel]
Receives full HTML (initial) or patches (incremental) via postMessage
Patch handler applies DOM ops, then KaTeX renders math via IntersectionObserver
IntersectionObserver also drives reverse scroll sync
Double-click handler jumps back to source
Incremental rendering pipeline
- Split the source into blocks at paragraph breaks, sectioning commands, and
\begin{...} / \end{...} boundaries. Long prose environments (proof, theorem, lemma, proposition, corollary, definition, example, remark, abstract) are further split on blank lines into sub-blocks so editing one paragraph of a long proof doesn't re-render the whole proof.
- ID each block with
djb2(source) + ordinal, so identical content keeps the same ID across edits. Insertion in the middle of the document doesn't renumber later blocks.
- Diff the new block list against the cached one. Output is a sequence of
insert, update, move, and delete patches. A move is detected via a max-old-position walk so blocks moved to the end are still recognised.
- Render each updated/inserted block to HTML once, then ship the patch list to the webview in 50-block batches with a generation counter so stale patches are dropped.
- Math is rendered lazily: each
<span class="math"> is observed; KaTeX runs when the span scrolls into view, with an LRU cache keyed by source so re-typing the same $...$ is free.
Math-aware debounce
mathCursorDetect.ts walks the document text up to the cursor offset, tracking line comments, backslash escapes, an env stack, and the four math delimiter pairs. If the cursor is inside math, edits use mathDebounceMs (default 5 s) instead of debounceMs (default 100 ms). The pending render flushes immediately on:
- save (always, even in
onSave mode)
- the cursor moving out of the math span
- pressing the Refresh Now button
Key components
| File |
Purpose |
src/extension.ts |
Entry point. Registers the preview command. |
src/preview/previewPanel.ts |
Webview lifecycle. Listens to document changes, picks the right debounce, ships full or patch updates, manages TikZ rendering and include-file watchers. |
src/preview/scrollSync.ts |
Bidirectional scroll sync with feedback-loop suppression. |
src/preview/mathCursorDetect.ts |
Forward-scanning detector that decides whether a given cursor offset is inside a math context. |
src/parser/latexParser.ts |
Thin wrapper around @unified-latex/unified-latex-util-parse. |
src/parser/astToHtml.ts |
AST-to-HTML transformation. Emits data-source-line for scroll sync. Handles 100+ LaTeX constructs, including the prose sub-block wrappers. |
src/parser/blockSplitter.ts |
Splits source into blocks; sub-splits prose environments on blank lines; assigns content-stable IDs. |
src/parser/blockDiffer.ts |
Diffs old vs. new block lists into insert/update/move/delete patches. |
src/parser/blockCache.ts |
Owns the block list across edits. Routes to fast-path (patches) or slow-path (full re-render) and exposes the KaTeX macro table. |
src/parser/macroExpander.ts |
Expands \newcommand, \renewcommand, \def, \DeclareMathOperator with argument substitution and recursion-depth limiting. |
src/parser/labelResolver.ts |
Tracks section/equation/figure/table/theorem numbering; resolves \ref/\eqref/etc. to numbered links. |
src/parser/bibParser.ts |
Regex-based BibTeX parser. Resolves \bibliography and \addbibresource. |
src/parser/fileResolver.ts |
Resolves \input/\include paths with cycle detection and caching. |
src/parser/tikzRenderManager.ts |
Routes TikZ rendering between local TeX and the WASM backend, with timeout and queuing. |
src/parser/tikzRenderer.ts |
WASM TikZ-to-SVG via node-tikzjax. Caches by content hash. |
src/webview/preview.js |
Browser-side: patch handler, viewport-aware KaTeX with source cache, scroll sync, toolbar controls, double-click-to-jump. |
src/webview/preview.css |
Theming and styling for all rendered LaTeX elements. |
LaTeX-to-HTML mapping
| LaTeX |
HTML |
\section{...} |
<h2> |
\textbf{...} |
<strong> |
\emph{...} |
<em> |
$...$ |
<span class="math"> rendered by KaTeX on demand |
\begin{itemize} |
<ul> with <li> |
\begin{enumerate} |
<ol> with <li> |
\begin{theorem} |
<div class="env-theorem"> with label (split into sub-blocks per paragraph) |
\begin{align} |
Display math block for KaTeX |
\begin{tabular} |
<table> with rows/cells |
\begin{figure} |
<figure> with <figcaption> |
\includegraphics{...} |
<img> (resolved to webview URI) |
\footnote{...} |
Superscript number + text at block bottom |
\href{url}{text} |
<a href="url"> |
\maketitle |
Renders collected title/author/date block |
\begin{tikzpicture} |
Async-rendered SVG |
| Unknown macros |
Content rendered, command name shown in dim gray |
- Editor to preview: listens to
onDidChangeTextEditorVisibleRanges, sends the top visible line to the webview, which finds the closest element with data-source-line <= line and scrolls to it. If no such element exists (editor is in the preamble), the preview snaps to the top.
- Preview to editor: an
IntersectionObserver in the webview tracks which elements are visible and reports the topmost line back; the extension calls editor.revealRange().
- Both directions suppress the reverse direction for 150 ms after acting to prevent oscillation. Content updates suppress reverse sync for 500 ms so an incoming patch doesn't make the editor jump.
Tech Stack
- Language: TypeScript (extension), JavaScript (webview)
- LaTeX Parsing: unified-latex -- PEG-based parser producing an AST
- Math Rendering: KaTeX -- fast client-side math rendering, viewport-driven
- TikZ Rendering: local TeX if available, otherwise node-tikzjax -- WASM-based minimal TeX for TikZ-to-SVG
- Bundler: esbuild
- Testing: Jest + ts-jest
Building from Source
cd latex-realtime-preview
npm install
npm run build # compile and bundle
npm run watch # watch mode for development
npm run test # run tests
To package as a .vsix:
npx @vscode/vsce package
Limitations
- No exact layout: page breaks, margins, column layouts, and float positioning are not replicated
- Approximate math: KaTeX covers most LaTeX math but not all packages
- TikZ: only packages supported by your TeX install (or by node-tikzjax in WASM mode) will render; complex diagrams may fail or hit the timeout
- No PDF output: this is a preview tool, not a compiler (the PDF button opens an existing PDF or runs
pdflatex)
| |