Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

LTX

An extremely fast LaTeX project manager, written in Rust.

LTX scaffolds structured projects, lints your source, drives tectonic (and pdflatex/xelatex/lualatex), and gets out of your way — built for people who’d rather be writing than babysitting a build.

Status: pre-1.0. The CLI surface is stabilizing; expect minor breaking changes between minor versions until 1.0.


Why LTX?

Most LaTeX workflows are stitched together from shell scripts, latexmk, and muscle memory. LTX treats your document the way a modern build tool treats a codebase:

  • Correct by construction — one ltx.toml describes your project; builds behave the same on your machine, your co-author’s machine, and in CI.
  • Fast — a Rust core and a native engine mean large multi-file papers compile quickly, without a full TeX distribution installed.
  • Predictable — the diagnostics pipeline gives you one consistent, source-spanning report for syntax and configuration problems.

Architecture

LTX is a Rust workspace composed of seven crates. Each crate owns its domain, and errors are defined in the crate that produces them — never centralized.

CrateRole
ltx_utilsLow-level filesystem helpers (create_dir, write_file, resolve_main_file)
ltx_diagnosticsPure diagnostic infrastructure — spans, source maps, miette rendering (defines no domain errors)
ltx_lexerByte-level tokenizer — converts .tex source into a stream of typed tokens
ltx_parserRecursive-descent parser — consumes the token stream and produces an AST
ltx_configManifest model + validation + scaffolding — reads ltx.toml, generates project layouts
ltx_compilerCompilation orchestration — dispatches to an engine (currently tectonic)
ltx_cliBinary entry point — subcommand dispatch, user-facing output, exit codes

The pipeline

Source text flows through the toolchain in this order:

  .tex source
       │
       ▼
   LtxLexer          ──► TokenStream (LtxToken + LtxSpan)
       │
       ▼
   LtxParser          ──► AST (Document, Command, Environment, …)
       │
       ▼
   ParserErrorHandler ──► LtxDiagnosticSink (absorbs lexer + parser diagnostics)
       │
       ▼
   miette renderer    ──► Terminal output
  1. Lexer scans raw bytes, applies TeX catcode rules, and emits LtxTokens with source spans. Problems are recorded by its own LexerErrorHandler.
  2. Parser consumes the token stream via TokenStream (peek/bump/checkpoint/rewind), builds the AST, and reports through its own ParserErrorHandler, which absorbs the lexer’s diagnostics on construction.
  3. Diagnostics accumulate in an LtxDiagnosticSink and render with miette for rich terminal output or serialize to JSON.

Error codes

Every diagnostic code is namespaced by the phase that owns it — 30 total:

NamespaceCountRange
LTX::LEXER::E0xx11Tokenization
LTX::PARSER::E0xx6Structural parsing
LTX::CONFIG::E0xx8Manifest / scaffolding
LTX::COMPILER::E0xx / W0xx5Compilation

Run ltx code to list them, filtered by phase (--lexer, --parser, --config, --compiler) or severity (-e, -w).

Quick start

# Scaffold a new project
ltx new my-paper
cd my-paper

# Check for errors
ltx check main.tex

# Build a PDF (tectonic is bundled; no TeX distribution needed)
ltx build

# List diagnostic codes
ltx code

Documentation pages

Installation

Prerequisites

  • Rust 1.86+ — install via rustup

That’s it. LTX compiles with Tectonic, a self-contained, native-Rust TeX engine — no separate TeX distribution is required. The ltx build default uses tectonic and downloads its TeX Live bundle on first use.

Optional: if you’d rather use pdflatex, xelatex, or lualatex (or need a package not yet supported by Tectonic), install a traditional distribution and make sure its binaries are on your PATH:

Install via Cargo

cargo install ltx

This downloads the latest published version from crates.io and compiles a release binary.

Install from source

git clone https://github.com/Abdogouhmad/ltx.git
cd ltx
cargo install --path .

This builds and installs the current main branch.

Verify installation

ltx --help
ltx --version
ltx code --lexer   # list the lexer diagnostic codes

You should see the CLI help text, the installed version, and the code table.

Platform notes

Linux

No special steps. If you use a traditional TeX distribution alongside Tectonic, ensure its binaries are on your PATH (TeX Live installs to /usr/local/texlive/… by default).

macOS

If you installed MacTeX, the binaries are at /Library/TeX/texbin/. Add this to your PATH if it isn’t already:

export PATH="/Library/TeX/texbin:$PATH"

Windows

MiKTeX or TeX Live should register themselves on the system PATH during installation.

Next steps

CLI Usage

LTX provides five subcommands. Run ltx --help for the full reference.

ltx [COMMAND]

ltx new

Create a new LTX project with starter files.

ltx new <name> [OPTIONS]
FlagShortDescriptionDefault
--engine-eLaTeX compiler to use (pdflatex, xelatex, lualatex, tectonic)tectonic
--srcUse src/ directory layout for source filesoff
--bibInclude bibliography support (bib/ directory)off

Examples

# Flat layout (main.tex in project root)
ltx new my-paper

# With src/ directory
ltx new my-paper --src

# With bib/ directory and xelatex engine
ltx new my-paper --bib --engine xelatex

# Full layout: src/ + bib/ + tectonic
ltx new my-paper --src --bib --engine tectonic

Scaffolded layout

Without flags:

my-paper/
├── main.tex
├── references.bib
├── ltx.toml
└── .gitignore

With --src:

my-paper/
├── src/
│   ├── main.tex
│   └── sections/
├── references.bib
├── ltx.toml
└── .gitignore

With --bib:

my-paper/
├── main.tex
├── bib/
│   └── references.bib
├── ltx.toml
└── .gitignore

ltx check

Check a .tex file for syntax errors by running the full lex → parse → diagnostics pipeline.

ltx check <path>
ArgumentDescription
pathPath to a .tex file to check

Exit codes

CodeMeaning
0Check passed (no errors; warnings are OK)
1Failed to read the file (missing, not UTF-8, etc.)
4Diagnostics with errors were found

Example

$ ltx check main.tex
Check passed — no issues found.

If errors are found, LTX renders them with source locations and help messages:

LTX::LEXER::E003

  × unmatched brace detected: `{`
   ╭─[main.tex:5:12]
 4 │ \section{Introduction
 5 │ % missing closing brace
   ·            ────┬────
   ·                ╰── here
 6 │
   ╰────
  help: Verify that every opening brace `{` has a matching closing brace `}`.

ltx build

Compile the project described in ltx.toml into a PDF.

ltx build

The manifest is validated first — missing keys, typos, and a missing main file fail fast with a source-spanning LTX::CONFIG::E0xx diagnostic. The selected engine then compiles the document:

  • tectonic — compiled in-process by the tectonic crate (the default).
  • pdflatex / xelatex / lualatex — not wired up yet; a LTX::COMPILER::W001 warning is printed and the build exits successfully.

Output is always written to the project’s target/ directory.

ltx clean

Remove the target/ directory and print a summary of deleted files and total size, akin to cargo clean.

ltx clean
FlagDescription
-vVerbose output
-vvMore verbose output

ltx code

List all registered diagnostic error codes with their descriptions, severities, and owning phase.

ltx code [FILTER]
FlagDescription
--lexerShow only lexer codes (LTX::LEXER::E0xx)
--parserShow only parser codes (LTX::PARSER::E0xx)
--configShow only config codes (LTX::CONFIG::E0xx)
--compilerShow only compiler codes (LTX::COMPILER::E0xx / W0xx)
--allShow all codes (default)
-e / --errorsShow only error-severity codes
-w / --warningsShow only warning-severity codes

Output:

CODE                     DESCRIPTION                                SEVERITY  PHASE
--------------------------------------------------------------------------------
LTX::LEXER::E001         Unexpected Token                           error     lexer
LTX::LEXER::E002         Unexpected End of File                     error     lexer
LTX::LEXER::E003         Unmatched Brace                            error     lexer
...
LTX::COMPILER::W001      Engine Not Implemented                     warning   compiler

30 total codes

Global flags

FlagDescription
--manifest-path <path>Path to a manifest file (ltx.toml); relative paths resolve against its directory.
--message-format <human|json>Output format for status messages.
-vVerbosity level (repeatable).
--helpShow help information
--versionShow version number

Engine options

LTX supports four LaTeX compilers, set via ltx new --engine or in the [build] section of ltx.toml:

EngineBinaryNotes
tectonictectonicDefault. Self-contained, Cargo-like LaTeX toolchain.
pdflatexpdflatexMost common engine.
xelatexxelatexUnicode and system-font support.
lualatexlualatexLua-extensible engine.

Next steps

Configuration

Every LTX project is described by an ltx.toml at its root. The ltx new command generates this file automatically. Only two sections exist: [project] for metadata and [build] for compilation settings.

Full reference

[project]
name = "my-paper"          # Required. Project name.
version = "0.1.0"          # Optional. Semver version string.
author = ["Author Name"]   # Optional. List of authors.
main = "src/main.tex"      # Required (validation). Path to the main .tex file.

[build]
name = "my-paper"          # Required (validation). Output PDF name, no extension.
engine = "tectonic"        # Required. pdflatex | xelatex | lualatex | tectonic
engine_args = ["-synctex=1"]   # Optional. Extra compiler arguments.

[build.options]            # Optional. Tectonic compilation options.
keep_logs = true           # Optional. Keep the .log file.   Default: true
keep_intermediates = false # Optional. Keep .aux/.synctex.gz. Default: false
synctex = true             # Optional. Emit SyncTeX data.     Default: true
only_cached = false        # Optional. Never hit the network. Default: false

The compiled PDF is always written to the project’s target/ directory — there is no output-directory option.

Sections

[project]

FieldTypeRequiredDescription
namestringYesProject name.
versionstringNoSemver version string.
authorlist of stringsNoProject authors.
mainstringYes*Path to the main .tex file relative to the project root.

* main is optional in the data model but required by validationltx build and ltx check refuse to run without it. See LTX::CONFIG::E001 in the Config Errors table.

[build]

FieldTypeRequiredDescription
namestringYes*Output PDF filename (without .pdf extension).
enginestringYesLaTeX engine: pdflatex, xelatex, lualatex, or tectonic.
engine_argslist of stringsNoExtra command-line arguments passed to the compiler.

* name is required by validation (LTX::CONFIG::E003).

[build.options]

Tectonic compilation options. Every field defaults to a sensible value when omitted, so a project only sets the options it wants to override.

FieldTypeDefaultDescription
keep_logsbooltrueKeep the .log file produced by the compiler.
keep_intermediatesboolfalseKeep intermediate build artifacts (.aux, .synctex.gz).
synctexbooltrueEmit SyncTeX data for editor / PDF synchronization.
only_cachedboolfalseIf true, never hit the network — fail if the bundle isn’t cached.

Validation rules

ltx build validates the manifest before compiling. Malformed TOML and unknown keys (e.g. a typo like [build.option]) are rejected loudly instead of silently ignored. The structural rules enforced by validate_manifest():

  1. [project].main is set and points to an existing file on disk (LTX::CONFIG::E001, LTX::CONFIG::E004).
  2. A [build] section is present (LTX::CONFIG::E002).
  3. [build].name is non-empty (LTX::CONFIG::E003).

Scaffolding options

When you run ltx new, the directory layout is controlled by flags that map to internal options:

SrcLayout — source file placement

FlagLayoutResult
(none)Flatmain.tex in project root
--srcWithSrcDirsrc/main.tex + src/sections/

BibLayout — bibliography placement

FlagLayoutResult
(none)Flatreferences.bib in project root
--bibWithBibDirbib/references.bib

These flags combine freely. For example, ltx new paper --src --bib creates:

paper/
├── src/
│   ├── main.tex
│   └── sections/
├── bib/
│   └── references.bib
├── ltx.toml
└── .gitignore

Minimal config

A project with just the essentials (generated by ltx new my-paper):

[project]
name = "my-paper"
main = "main.tex"

[build]
name = "my-paper"
engine = "tectonic"

Example: bibliography project

[project]
name = "thesis"
version = "0.1.0"
author = ["Jane Doe <jane@example.com>"]
main = "src/main.tex"

[build]
name = "thesis"
engine = "tectonic"

[build.options]
keep_logs = false
only_cached = true

API Overview

Reference for the LTX library crates, for Rust consumers embedding LTX as a library. The workspace is seven crates; each owns its domain and its errors. Errors flow through the shared ltx_diagnostics infrastructure to render, but are defined in the crate that produces them.

Crate map

CrateResponsibilityErrors owned
ltx_diagnosticsPure infrastructure — spans, source maps, sinks, miette rendering, ErrorCode registrynone (infra only)
ltx_lexerByte-level tokenizer; catcode handlingLexerError (LTX::LEXER::E001E011)
ltx_parserRecursive-descent parser; AST constructionParserError (LTX::PARSER::E001E006)
ltx_configltx.toml model, validation, scaffoldingConfigError (LTX::CONFIG::E001E008)
ltx_compilerCompilation orchestration; engine dispatchCompilerError (LTX::COMPILER::E001E004, W001)
ltx_utilsLow-level filesystem helpersnone
ltx_cliBinary entry point; subcommand dispatchCliError (CLI-only)

Dependency direction

Dependencies point inward toward reusable infrastructure. ltx_diagnostics never depends on the other crates, so the graph stays acyclic:

ltx_utils ──► ltx_config ──► ltx_compiler ──► ltx_cli
                    │              │
                    ▼              ▼
ltx_lexer ──► ltx_parser ──► ltx_diagnostics (shared rendering)

The diagnostic pipeline

Any error type implements LtxDiagnosticSource and is wrapped in an LtxDiagnostic together with the LtxSourceMap needed to render it. Diagnostics accumulate in an LtxDiagnosticSink, then render with miette or serialize to JSON — the diagnostics crate never needs to know which phase produced an error.

Lexer ──► LexerError ──┐
                       ├─► LtxDiagnostic ──► LtxDiagnosticSink ──► miette renderer
Parser ──► ParserError ┘                                  └─────► JSON

Code registry

Each crate exports a pub const ALL_CODES: &[ErrorCode] table. The CLI aggregates all of them for ltx code:

NamespaceCountSeverities
LTX::LEXER::E0xx11errors
LTX::PARSER::E0xx6errors
LTX::CONFIG::E0xx8errors
LTX::COMPILER::E0xx, W0xx5errors + 1 warning

See the Error codes section for full tables.

ltx_diagnostics

Pure diagnostic infrastructure for the LTX toolchain. This crate defines no domain errors — it only provides the building blocks other crates plug into to report, render, and serialize diagnostics.

Responsibilities

  • source file management (LtxSourceMap, LtxSourceFile)
  • byte-range span utilities (LtxSpan, LtxFileId)
  • severity classification (LtxSeverity: Error / Warning / Hint)
  • miette integration and rendering
  • batch diagnostic reporting (LtxDiagnosticSink)
  • the LtxDiagnosticSource helper trait that every crate’s error enum implements
  • the ErrorCode registry metadata struct

Key types

TypeRole
LtxDiagnosticWraps Arc<dyn LtxDiagnosticSource> + Arc<LtxSourceMap> so any phase error can be rendered.
LtxDiagnosticSinkAccumulates diagnostics across phases for batch reporting (never panics).
LtxSourceMap / LtxSourceFileSource-text registry; byte-offset → line:column resolution.
LtxSpan / LtxFileIdByte-range location in a specific file.
LtxSeverityError / Warning / Hint classification.
ErrorCodeRegistry entry describing one diagnostic code (code, description, severity, phase).
LtxDiagnosticSourceTrait implemented by every phase-owned error; exposes its primary span.

Error ownership

Domain errors live in the crates that produce them, not here:

CrateError enumCodes
ltx_lexerLexerErrorLTX::LEXER::E001E011
ltx_parserParserErrorLTX::PARSER::E001E006
ltx_configConfigErrorLTX::CONFIG::E001E008
ltx_compilerCompilerErrorLTX::COMPILER::E001E004, W001

Each of those crates exports a pub const ALL_CODES: &[ErrorCode] registry that the CLI aggregates for ltx code.

Rendering

  • render_pretty(diagnostic) / render_pretty_into(...) — miette graphical output
  • render_json_into(sink, writer) — JSON-serializable diagnostics for tooling

Usage

#![allow(unused)]
fn main() {
use ltx_diagnostics::{LtxSourceMap, LtxDiagnosticSink, LtxDiagnostic, LtxDiagnosticSource};

let mut source_map = LtxSourceMap::new();
let file_id = source_map.add_file("main.tex")?;
// ... produce an error implementing LtxDiagnosticSource ...
let diagnostic = LtxDiagnostic::new(error, source_map.into());
}

Design constraints (see AGENT.md)

  • Never define domain errors here.
  • Never depend on lexer / parser / config / compiler crates (keeps the graph acyclic).

ltx_lexer

Byte-level tokenizer for LaTeX source files. Converts raw .tex text into a stream of LtxTokens, each carrying an LtxTokenKind, an LtxSpan, and the source slice it was parsed from. Mode-aware (Normal / Math) with catcode state tracking per the TeX specification.

Responsibilities

  • lexical analysis and token generation
  • category code handling (LtxCatCode, LtxCatCodeState)
  • lexer-specific diagnostics (owned by this crate)

Key types

TypeRole
LtxLexerStreaming iterator — .next() / .next_token() yields one token at a time.
TokenStreamEagerly drains LtxLexer; cursor API (peek, bump, checkpoint/rewind) for the parser.
LtxToken / LtxTokenKindA single token and its classification.
LtxModeNormal or Math operating mode.
LtxCatCode / LtxCatCodeStateTeX category codes and the current lookup table.
LexerErrorAll 11 lexer diagnostics (LTX::LEXER::E001E011).
LexerErrorHandlerCollects LtxDiagnostics during lexing; wraps them with the source map.

Error ownership

LexerError (in src/error.rs) owns every lexer diagnostic: unexpected token, unexpected EOF, unmatched brace, invalid math delimiter, unterminated argument, invalid escape sequence, invalid unicode, illegal parameter char, unterminated verbatim, invalid character, and mismatched environment. Codes are registered in ALL_CODES under the LTX::LEXER::E0xx namespace. See the Lexer Errors table.

Usage

#![allow(unused)]
fn main() {
use ltx_lexer::{LtxLexer, TokenStream};
use ltx_diagnostics::LtxSourceMap;

let mut source_map = LtxSourceMap::new();
let file_id = source_map.add_file("main.tex")?;
let stream = TokenStream::new(LtxLexer::new(source, file_id, source_map.into()));

// Hand the stream to the parser, or drive LtxLexer directly.
}

Design notes

  • Zero string allocations for tokens — tokens borrow from the source text.
  • Most consumers create a TokenStream and pass it to ltx_parser; only streaming consumers need LtxLexer directly.

ltx_parser

Recursive-descent parser for LaTeX. Consumes a ltx_lexer::TokenStream and produces an AST.

Responsibilities

  • syntax parsing and AST generation
  • parser-specific diagnostics (owned by this crate)

Architecture

  • parser::LtxParser wraps a TokenStream and exposes cursor methods (peek, bump, checkpoint/rewind, skip_ws).
  • parser_traits::Parse is the trait every AST node implements.
  • ast contains the node types: Document, Command, Environment, Math, Group, Text, Comment, UsePackage, DocumentClassDecl, Arg, …
  • parse_document is the top-level convenience entry point.

AST nodes

NodeDescription
DocumentTop-level root containing preamble and body.
PreambleItemPreamble items (\documentclass, \usepackage, …).
DocumentClassDecl\documentclass declaration.
UsePackage\usepackage declaration.
CommandControl sequence with its arguments.
Arg / OptionalArgRequired and optional argument variants.
Environment\begin{...}...\end{...} block.
GroupBalanced {...} group.
MathMath expression.
TextPlain text run.
CommentLaTeX comment.

Error ownership

ParserError (in src/error.rs) owns all 6 parser diagnostics (LTX::PARSER::E001E006):

CodeVariant
E001ExpectedToken
E002UnexpectedEOF
E003UnclosedEnvironment
E004MismatchedEnvironment
E005MissingClosingBrace
E006UnexpectedEOFWhileParsing

ParserErrorHandler collects these. When LtxParser::new is constructed it drains the lexer’s diagnostics into the same handler, so a single sink reports both phases. See the Parser Errors table.

Usage

#![allow(unused)]
fn main() {
use ltx_parser::{LtxParser, parse_document};

let mut parser = LtxParser::new(stream);
let doc = parse_document(&mut parser);

let handler = parser.error_handler_mut();
if handler.has_errors() {
    eprintln!("{}", handler.render_pretty());
}
}

Design notes

  • Zero token clones: AST nodes store Range<usize> token spans or zero-copy &'src str slices.
  • Diagnostics flow through ltx_diagnostics for rendering — the parser owns the errors, the diagnostics crate renders them.

ltx_config

Configuration and project scaffolding. Provides the data model for ltx.toml, manifest validation, and project generation.

Responsibilities

  • manifest parsing (LtxManifest)
  • configuration validation (validate_manifest)
  • project scaffolding (scaffold)
  • config-specific diagnostics (owned by this crate)

Key types

TypeRole
LtxManifestTop-level ltx.toml structure; LtxManifest::from_file() reads + parses + validates in one step.
Project[project] table — name, version, author, main file.
Build[build] table — output name, engine, compile options.
CompileOptions[build.options]keep_logs, keep_intermediates, synctex, only_cached (all with sensible defaults).
CompilerEngineEnum: PdfLaTeX, XeLaTeX, LuaLaTeX, Tectonic.
ScaffoldOptionsOptions for ltx new (name, engine, src, bib).
SrcLayout / BibLayoutFlat vs src/ / bib/ directory layouts.
ConfigErrorAll 8 config diagnostics (LTX::CONFIG::E001E008).

Error ownership

ConfigError (in src/error.rs) owns every manifest/scaffold diagnostic, including the merged former ManifestDiagnostic + ScaffoldError:

CodeVariant
E001MissingMain — missing main in [project]
E002MissingBuild — missing [build] section
E003MissingBuildName — missing name in [build]
E004MainFileNotFound[project].main points to a missing file
E005InvalidToml — malformed TOML / unknown keys
E006ReadFailedltx.toml unreadable
E007Io — scaffold I/O error
E008AlreadyExists — project directory already exists

Validation errors embed the raw source text and a byte span so they render with miette snippets pointing at the offending table or key. See the Config Errors table.

Usage

#![allow(unused)]
fn main() {
use ltx_config::{LtxManifest, Project, Build, CompilerEngine, scaffold, ScaffoldOptions};

// Read + validate in one step
let manifest = LtxManifest::from_file("ltx.toml")?;

// Scaffold a new project
scaffold(&project_dir, &ScaffoldOptions::new("my-paper", CompilerEngine::Tectonic))?;
}

Design notes

  • Unknown keys in ltx.toml are rejected loudly (serde deny_unknown_fields) instead of silently ignored.
  • No dependency on lexer/parser — the config layer only talks to ltx_diagnostics (for ErrorCode) and ltx_utils (filesystem helpers).

ltx_compiler

Compilation orchestration. Turns a validated manifest into a compiled PDF by dispatching to a configured engine. Currently only the tectonic engine is wired up; the other engines emit a warning.

Responsibilities

  • compilation pipeline (build)
  • engine abstraction (CompilerConfig, engine dispatch)
  • tectonic integration (tectonic_compile)
  • file watching for rebuild-on-save (watch)
  • compiler-specific diagnostics (owned by this crate)

Key types

TypeRole
CompilerConfigEngine, output name, main file, and compile options resolved from a manifest.
CompilerErrorAll compiler diagnostics (LTX::COMPILER::E001E006, W001).
build::buildEntry point: resolves the main file, then dispatches to the engine.
tectonic::tectonic_compileDrives tectonic’s ProcessingSessionBuilder to produce a PDF.
watch::WatchConfigDebounced, recursive watcher that rebuilds on relevant changes.

Error ownership

CompilerError (in src/error.rs):

CodeVariant
E001MissingMain — no [project].main at compile time
E002MissingBuild — no [build] section
E003MainFileNotFound — main file missing on disk
E004TectonicError — bundle fetch / session creation / compilation failed
E005Init — file watcher failed to start
E006ChannelClosed — watch event channel disconnected
W001EngineNotImplemented — engine not wired up yet (warning)

CompilerError implements miette::Diagnostic so it converts into miette::Report and can be returned directly from miette::Result functions. See the Compiler Errors table.

Engine behavior

  • tectonic — real compilation via the tectonic crate.
  • pdflatex / xelatex / lualatex — emit LTX::COMPILER::W001 through miette to stderr and return Ok(()) (a warning never fails the build).

Usage

#![allow(unused)]
fn main() {
use ltx_compiler::{CompilerConfig, build};

let manifest = ltx_config::LtxManifest::from_file("ltx.toml")?;
let config = CompilerConfig::from_manifest(&manifest)?;
build::build(&config, project_root)?;
}

Design notes

  • Input is a CompilerConfig; output always goes to target/.
  • watch.rs watches only the src/ root (structured ltx new --src projects) or the main file itself (single-structure ltx new projects), plus the manifest, and rebuilds through build::build on every relevant change (.tex, .sty, .cls, .bib). The compiler’s own target/ output is never watched, so builds cannot feed back into a rebuild loop. Driven by the ltx watch CLI command.

ltx_utils

Low-level filesystem helpers. Small, dependency-free utilities shared by the other crates for common I/O operations.

Functions

FunctionDescription
create_dir(path)Creates a directory, including all missing parents.
create_file(path)Creates a file, including its parent directories.
write_file(path, contents)Writes contents to a file, creating parent dirs as needed.
resolve_main_file(path)Returns the main entry path, defaulting to main.tex; errors with NotFound if the given path doesn’t exist.

Usage

#![allow(unused)]
fn main() {
use ltx_utils::{create_dir, write_file};

create_dir(Path::new("my-paper/src/sections"))?;
write_file(Path::new("my-paper/main.tex"), r"\documentclass{article}")?;
}

Design notes

  • No dependencies on other ltx_* crates — usable from anywhere in the workspace.
  • All functions return std::io::Result; no panicking variants.

Lexer Errors (LTX::LEXER::E001-E011)

The lexer transforms source text into a token stream during the initial compilation phase. This section enumerates all lexer-related errors, their causes, and recommended resolutions.

Error Reference Table

CodeVariantDiagnostic MessageRemediation
LTX::LEXER::E001UnexpectedTokenUnexpected token encounteredValidate input for invalid characters, malformed commands, or unsupported syntax at the reported position.
LTX::LEXER::E002UnexpectedEOFUnexpected end of fileConfirm all environments, brace pairs, and command arguments are properly terminated.
LTX::LEXER::E003UnmatchedBraceUnmatched brace detectedEnsure every opening { has a corresponding closing } and braces are correctly nested.
LTX::LEXER::E004InvalidMathDelimiterInvalid math delimiter detectedValidate usage of math delimiters: $, $$, \(, \), \[, and \].
LTX::LEXER::E005UnterminatedArgumentCommand argument not terminatedAppend missing closing brace } to the command argument.
LTX::LEXER::E006InvalidEscapeSequenceInvalid escape sequenceVerify the command name following the backslash \ is valid.
LTX::LEXER::E007InvalidUnicodeInvalid UTF-8 sequence detectedRe-save the source file with UTF-8 encoding.
LTX::LEXER::E008IllegalParameterCharIllegal parameter character usageEnsure # is used only in macro definitions and follows correct syntax.
LTX::LEXER::E009UnterminatedVerbatimVerbatim environment not terminatedClose the verbatim environment with appropriate termination markers.
LTX::LEXER::E010InvalidCharacterInvalid character encounteredRemove or replace unsupported character at the reported position.
LTX::LEXER::E011MismatchedEnvironmentEnvironment close tag mismatchClose each \begin{env} with an \end{env} of the same name.

Error Categories

Syntax Errors (LTX::LEXER::E001-E006)

These errors occur when the input violates lexical grammar rules.

LTX::LEXER::E001 - UnexpectedToken
The lexer encountered a sequence that does not form a valid token in the current state. Check for:

  • Special characters in invalid contexts
  • Malformed commands or arguments
  • Syntax that does not conform to the language specification

LTX::LEXER::E002 - UnexpectedEOF
The source input terminated prematurely. Verify that:

  • All environment blocks are closed
  • Every opening brace has a matching closure
  • Command argument lists are complete

LTX::LEXER::E003 - UnmatchedBrace
Brace matching failed during tokenization. Inspect:

  • Brace pair count (each { requires a })
  • Brace nesting order
  • Arguments enclosed within braces

LTX::LEXER::E004 - InvalidMathDelimiter
Math mode delimiters are incorrectly specified. Valid delimiters:

  • Inline: $...$ or \(...\)
  • Display: $$...$$ or \[...\]

LTX::LEXER::E005 - UnterminatedArgument
A command argument lacks its closing brace. Each { that starts an argument must have a matching }.

LTX::LEXER::E006 - InvalidEscapeSequence
The sequence following a backslash does not form a valid command. Ensure the command name consists of valid characters and exists in the context.

Encoding and Character Errors (LTX::LEXER::E007-E010)

These errors relate to character encoding and invalid character handling.

LTX::LEXER::E007 - InvalidUnicode
The source file contains bytes that do not form valid UTF-8 sequences. Re-encode the file using UTF-8.

LTX::LEXER::E008 - IllegalParameterChar
The # character is used in an invalid context. Proper usage:

  • Macro parameter references: #1, #2, etc.
  • Macro definitions only

LTX::LEXER::E009 - UnterminatedVerbatim
A verbatim environment was opened but not closed. Ensure the verbatim block terminates correctly.

LTX::LEXER::E010 - InvalidCharacter
The lexer encountered a character not permitted in the current context. Remove or replace the offending character.

Environment Errors (LTX::LEXER::E011)

LTX::LEXER::E011 - MismatchedEnvironment
An \end{...} names a different environment than the one opened by the matching \begin{...}, or an \end appears with no corresponding \begin. Every environment must be closed with the same name it was opened with.

Diagnostic Example

🔍 Found 1 issue(s):

LTX::LEXER::E001

  × unexpected token `@`
   ╭─[main.tex:3:19]
 2 │ % E001: Unexpected Token
 3 │ \newcommand{\foo} @invalid
   ·                   ────┬───
   ·                       ╰── here
 4 │
   ╰────
  help: Check for invalid characters, malformed commands, or unsupported syntax near the highlighted position.

Parser Errors (LTX::PARSER::E001-E006)

The parser analyzes the token stream produced by the lexer and constructs an Abstract Syntax Tree (AST) according to the language grammar. This section lists all parser-related errors, their causes, and recommended fixes.

Error Reference Table

CodeVariantDiagnostic MessageRemediation
LTX::PARSER::E001ExpectedTokenExpected token not foundCheck the syntax near the highlighted position — a token is missing or misplaced.
LTX::PARSER::E002UnexpectedEOFUnexpected end of fileClose the construct with its matching delimiter before the file end.
LTX::PARSER::E003UnclosedEnvironmentEnvironment was not closedAdd a matching \end{...} for every \begin{...}.
LTX::PARSER::E004MismatchedEnvironmentEnvironment closing tag mismatchEnsure \end{...} matches the corresponding \begin{...} name.
LTX::PARSER::E005MissingClosingBraceMissing closing braceAdd the matching closing brace } to terminate the group.
LTX::PARSER::E006UnexpectedEOFWhileParsingEnd of file reached while parsing a structureEnsure required structures (e.g. \begin{document}) are present.

Error Categories

Token Errors (LTX::PARSER::E001-E002)

LTX::PARSER::E001 - ExpectedToken

The parser expected a specific token — such as {, }, or an environment — but found something else or reached the end of the stream.

% ❌ Incorrect (missing closing brace)
\newcommand{\foo}{Hello

% ✅ Correct
\newcommand{\foo}{Hello}

LTX::PARSER::E002 - UnexpectedEOF

A construct being parsed (for example inline math) ran to the end of the file without its closing delimiter.

% ❌ Incorrect
A sentence with $x + y = z$

% ✅ Correct
A sentence with $x + y = z$.

Environment Errors (LTX::PARSER::E003-E004)

LTX::PARSER::E003 - UnclosedEnvironment

An environment was opened with \begin{...} but never closed with \end{...}.

% ❌ Incorrect
\begin{itemize}
\item First item
\item Second item

% ✅ Correct
\begin{itemize}
\item First item
\item Second item
\end{itemize}

LTX::PARSER::E004 - MismatchedEnvironment

The environment closing tag doesn’t match the opening tag.

% ❌ Incorrect
\begin{itemize}
\item First item
\end{enumerate}

% ✅ Correct
\begin{itemize}
\item First item
\end{itemize}

Group Errors (LTX::PARSER::E005)

LTX::PARSER::E005 - MissingClosingBrace

A braced group { ... reached the end of the token stream without a matching }.

% ❌ Incorrect
\section{Introduction

% ✅ Correct
\section{Introduction}

Document Structure Errors (LTX::PARSER::E006)

LTX::PARSER::E006 - UnexpectedEOFWhileParsing

The top-level document parsing ended before a required structure — such as \begin{document} — was found.

% ❌ Incorrect
\documentclass{article}
Hello world

% ✅ Correct
\documentclass{article}
\begin{document}
Hello world
\end{document}

Diagnostic Example

🔍 Found 1 issue(s):

LTX::PARSER::E004

  × mismatched environment: expected `\end{itemize}`, found `\end{enumerate}`
    ╭─[main.tex:18:1]
 17 │ \begin{itemize}
 18 │ \end{enumerate}
    · ────────┬───────
    ·         ╰── here
 19 │
    ╰────
  help: Environments must be closed with the same name they were opened with.

Best Practices

  1. Always use matching \begin and \end pairs - Keep environments properly nested
  2. Verify command spelling - Use autocompletion or reference documentation
  3. Load required packages - Ensure all necessary packages are imported
  4. Close every brace and argument - Each { needs a matching }
  5. Structure the document correctly - Include \begin{document} after the preamble

Config Errors (LTX::CONFIG::E001-E008)

The config crate owns every error it can produce while reading or validating ltx.toml and while scaffolding a new project. Manifest-validation errors embed the raw source text and a byte span so they render with a miette snippet pointing at the offending table or key; read failures and scaffold I/O errors have no source snippet.

Error Reference Table

CodeVariantDiagnostic MessageRemediation
LTX::CONFIG::E001MissingMainmissing main in the [project] sectionAdd main = "src/main.tex" (or the path to your main file).
LTX::CONFIG::E002MissingBuildmissing [build] sectionAdd a [build] section with name = "..." and engine = "tectonic".
LTX::CONFIG::E003MissingBuildNamemissing name in the [build] sectionAdd name = "output" (without the .pdf extension).
LTX::CONFIG::E004MainFileNotFoundmain file not found: {path}Create the file or fix the main value in the [project] section.
LTX::CONFIG::E005InvalidTomlinvalid ltx.toml: {reason}Fix the TOML syntax or remove the unknown key.
LTX::CONFIG::E006ReadFailedfailed to read {path}: {error}Make sure the file exists and is readable.
LTX::CONFIG::E007Io(transparent I/O error)Fix the underlying filesystem failure.
LTX::CONFIG::E008AlreadyExistsproject directory {0} already existsRemove the directory or choose a different project name.

Validation rules

validate_manifest() (called by ltx build and LtxManifest::from_file()) rejects a manifest when:

  1. [project].main is missing (E001) or points to a file that does not exist (E004).
  2. The [build] section is absent (E002).
  3. [build].name is missing (E003).

Malformed TOML and unknown keys (e.g. a typo like [build.option]) are reported as E005 before any semantic checks run.

Diagnostic Example

LTX::CONFIG::E001

  × missing `main` in the `[project]` section
   ╭─[ltx.toml:1:1]
 1 │ [project]
   · ────┬────
   ·     ╰── the `main` key is required here
 2 │ name = "demo"
   ╰────
  help: add `main = "src/main.tex"` (or the path to your main file)

Compiler Errors (LTX::COMPILER::E001-E004, W001)

The compiler crate owns every error it can produce while turning a manifest into a compiled PDF: missing configuration, missing input files, and engine (tectonic) failures. CompilerError implements miette::Diagnostic, so it converts into a miette::Report and is returned directly from miette::Result functions.

Error Reference Table

CodeVariantDiagnostic MessageRemediation
LTX::COMPILER::E001MissingMainno main file set in ltx.tomlAdd main = "main.tex" under [project].
LTX::COMPILER::E002MissingBuildno [build] section in ltx.tomlAdd a [build] section with engine and name.
LTX::COMPILER::E003MainFileNotFoundmain file {path} not foundCreate the file or fix the main value in [project].
LTX::COMPILER::E004TectonicErrorengine failure {message}Check the engine logs; verify your network connection for bundle issues.
LTX::COMPILER::W001EngineNotImplemented{engine} engine is not implemented yetUse engine = "tectonic", or wait for this engine to land.

Error Categories

Configuration Errors (E001-E002)

These mirror the config-layer validation but occur at compile time, when the resolved CompilerConfig is missing information it needs.

E001 - MissingMain
The [project] section has no main entry, so the compiler doesn’t know which file to compile.

E002 - MissingBuild
The manifest has no [build] section, so the engine and output name are unknown.

Input Errors (E003)

E003 - MainFileNotFound
The main input file resolved from [project].main does not exist on disk at build time. Note the config layer catches this earlier during validation (LTX::CONFIG::E004); this variant covers paths resolved at compile time.

Engine Errors (E004)

E004 - TectonicError
The selected engine failed to fetch its support bundle, create its processing session, or finish the compilation. The message carries the underlying engine error.

Warnings (W001)

W001 - EngineNotImplemented
The selected engine is not wired up yet — only tectonic is available. The warning is rendered through miette and the build still exits successfully.

Diagnostic Example

LTX::COMPILER::W001

  ⚠ `pdflatex` engine is not implemented yet — only `tectonic` is available
  help: use `engine = "tectonic"` in `ltx.toml`, or wait for this engine to
        land