

TeXForge
Code listings
Last updated
Write a code environment and texforge highlights it at build time:
\begin{code}[lang=python, numbers=true]
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
\end{code}
Highlighting is resolved outside LaTeX, in Rust (via syntect): the build
rewrites each block into plain LaTeX that needs only color.sty — no
listings.sty, no minted, no -shell-escape, no Python. Your sources are
never modified; the rewrite happens on the build copy only, and the required
\usepackage{color} guard plus colour definitions are injected into the
build copy’s preamble automatically (skipped when you already load color
or xcolor yourself, and safe when a class such as beamer loads xcolor
behind the scenes).
Without a code block — and without the lstlisting opt-in below — the
pass writes nothing and documents compile exactly as before.
Options
| Option | Values | Default |
|---|---|---|
lang |
language name or alias (table below); empty means a deliberate plain monospace block | plain block, no warning |
numbers |
true shows a line-number gutter |
false, unless [highlight] numbers = true sets the document default (the block option wins) |
caption |
caption text (LaTeX; wrap in braces if it contains a comma) | (none) — without it the block has no number, no label and no list entry |
label |
label for \ref/\pageref (e.g. lst:fib); only meaningful with caption |
(none) — without a caption it warns (label without caption is ignored) |
pos |
H (inline, the default), h, t, b, p |
H — the listing stays where it is written and may break across pages |
size |
scriptsize, footnotesize, small, normalsize |
small (today’s size); any other value warns and uses small |
style |
light, light-mono, dark, dark-mono (see Styles) |
[highlight.by_lang] for that language, else [highlight] style, else light; any other value fails the build |
An unknown lang never fails the build: the block renders monochrome and
texforge build warns, naming the language and pointing at
docs/listings.md. An unknown option warns and is ignored.
Project-wide defaults live in project.toml:
[highlight]
theme = "github" # "github" (default) or "one-light"
style = "light" # "light" (default), "light-mono", "dark", "dark-mono"
lstlisting = true # also rewrite \begin{lstlisting} blocks (default false)
numbers = true # number every block unless it says otherwise
font = "inconsolata" # typewriter family for code blocks (default "document")
# caption_name = "Listing" # override the language's listing name
# list_name = "List of Listings" # override the list-of-listings heading
[highlight.by_lang]
bash = "dark" # shell commands in a terminal-dark frame
Font
The [highlight] font key sets the document’s typewriter family (\ttfamily) so
inline \texttt{} matches the code blocks. The value must be one of the monospace
families shipped in the Tectonic bundle:
| value | family |
|---|---|
document (default) |
whatever the preamble sets — Latin Modern Mono in the bundled templates |
inconsolata |
Inconsolata (with varqu/varl for straight quotes and distinguishable l) |
source-code-pro |
Source Code Pro |
dejavu-sans-mono |
DejaVu Sans Mono |
plex-mono |
IBM Plex Mono |
fira-mono |
Fira Mono |
The chosen package is loaded once, guarded by \@ifpackageloaded, so a document
that already loads the same package is not loaded twice. Because the injection
happens after the author’s preamble, the chosen family wins over a mono
package the author loaded earlier. Unknown values fail the build, naming the
value and listing the six valid ones.
Caveat — fontspec preambles. A preamble that loads fontspec (directly,
or through an OpenType package such as FiraMono) switches the document to TU
encoding, where the Type 1–based values (inconsolata, dejavu-sans-mono)
have no font files: typewriter text then falls back to the document’s serif
font instead of the chosen family. In such documents pick one of the
OpenType-backed values (source-code-pro, plex-mono, fira-mono), which
apply in both encodings, or leave the key at document.
Captions, labels and the list of listings
A block with caption= gains a numbered caption line directly above the
frame — kept on the same page as the frame’s first line — reading
“Listing N: …” (bold name, counter tfxlisting, numbered per chapter
as chapter.N when the class defines \chapter, else plain N):
\begin{code}[lang=rust, numbers=true, caption={Fibonacci}, label={lst:fib}, pos=t, size=footnotesize]
fn fib(n: u64) -> u64 { if n < 2 { n } else { fib(n-1) + fib(n-2) } }
\end{code}
With label= (placed right after the counter step), reference it from the
text with \ref{lst:fib} (prints N) or \pageref{lst:fib}. Every
captioned listing adds an entry to the list of listings; print it with
\listoflistings (a \chapter*/\section* heading plus the entries). A
document that never calls \listoflistings is unaffected.
The name follows the document language — resolved exactly as the
spell checker resolves it (babel/polyglossia option, else the
configured fallback language): english → “Listing” / “List of Listings”;
spanish → “Listado” / “Índice de listados”; any other language → the
English names. project.toml [highlight] caption_name and list_name
override both.
Behaviour notes:
- By default a listing is not a float: it stays where it is written
and may break across pages. With
pos=other thanHthe block is wrapped in afigureof that placement (non-breaking), caption or not —His never emitted asfigure[H], so nofloatpackage is needed.pos=Hmeans inline (the default), accepted for symmetry with diagrams. - A floated block longer than the text height cannot fit: the build warns
(
floated listing is too tall …) and renders it inline instead. - Line numbers scale with
size=. The caption machinery (counter, names,\listoflistings) is injected only when some block actually carries acaption, so a document without captions compiles exactly as before.
Languages
Names match by syntax name or file extension (case-insensitive); the aliases are conveniences for the spellings authors actually type.
lang= |
Highlights as | Aliases |
|---|---|---|
python |
Python | py |
rust |
Rust | rs |
javascript |
JavaScript | js |
typescript |
TypeScript | ts |
bash |
Bash | sh, zsh, shell |
c, c++ |
C / C++ | cpp, cc |
json |
JSON | — |
yaml |
YAML | yml |
toml |
TOML | — |
markdown |
Markdown | md |
latex |
LaTeX | tex |
r |
R | — |
sql |
SQL (also MySQL, PostgreSQL by name) |
— |
clojure, css, diff, erlang, go, haskell, java, lua, matlab, ocaml, php, ruby, xml |
as named | rb (ruby) |
make |
Makefile | mk |
dot |
Graphviz DOT | graphviz, gv |
git |
Git diff output | — |
jsx, tsx |
JSX / TSX | — |
text, txt, plaintext, (empty) |
deliberate plain block — no warning | — |
Styles
Four styles, and you can mix them in one document — Python in the light palette, shell commands in a terminal-dark one, a monochrome block for the print edition:
| Style | Palette | Frame background |
|---|---|---|
light (default) |
the theme’s own (github or one-light) |
github #f6f8fa, one-light #fafafa |
light-mono |
greys only — keywords bold, comments italic | #f6f6f6 |
dark |
the dark twin of the theme: GitHub Dark Dimmed or One Dark | #22272e, #282c34 |
dark-mono |
greys only, on a dark frame | #2b2b2b |
theme still picks the palette family (github / one-light) and style
picks light vs dark vs monochrome; a dark block takes its colours from the
dark twin of the theme. The two -mono styles ignore theme: with hue gone
there is nothing left for the family to decide. An unknown style name fails
the build, naming the value and listing the valid ones.
The style of a block is resolved most-specific-first:
- the block’s own
style=option - the
[highlight.by_lang]table, keyed bylang=name or alias (bash,sh,shellandzshare one entry) [highlight] stylelight
[highlight]
style = "light"
[highlight.by_lang]
bash = "dark" # every shell block, whatever spelling its lang= uses
\begin{code}[lang=python, style=light-mono] % …or one block on its own
def fib(n):
return n
\end{code}
lstlisting blocks take their style from by_lang or the document default —
they have no style= option, so one there warns like any other unknown
listings option.
Printing. The dark styles lay down a good deal more ink and are meant for
screen and for colour printers; the -mono styles are the black-and-white
choice, where weight and slant carry the structure instead of hue. Every
token in the dark, light-mono and dark-mono styles meets WCAG AA
contrast (4.5:1) against its own background; light is the long-standing
palette and is byte-identical by design, so its contrast is unchanged.
The frame
Every rewritten block (code, or an opted-in lstlisting) renders inside
a frame: a background tint from its style (light uses the theme’s #f6f8fa
/ #fafafa, every other style its own), a 0.4pt hairline border in the
palette’s comment colour, 4pt inner padding left and right, 3pt top and
bottom, full text width — plain LaTeX (color.sty rules only, no
tcolorbox/mdframed). The colours travel with the block, which is what
lets one document mix styles.
The frame is painted per line with zero-size overlays, so page breaking works exactly as without it and a block split across pages stays open at the break: the first fragment has no bottom border, the second no top border, and both keep their side borders and tint.
With numbers, the gutter sits inside the frame: numbers right-aligned in
the palette’s comment colour, separated from the code by a 0.3pt rule in the
border colour.
Vertical rhythm is \medskip before and after every inline block; a floated
one (pos= other than H) has left the text flow, so it adds none. The
paragraph right after an inline block is not indented — unless you left a blank
line after \end{code}, in which case the normal paragraph indent applies. A
block never starts at the very bottom of a page with fewer than two lines on
it: the first two and the last two lines are glued together.
Behaviour notes
- Every source line becomes its own paragraph, so TeX may break the page between any two lines — a 60-line block flows across pages with no extra markup.
- Lines never wrap: spaces become non-breaking (
\tfxsp{}, a\nobreakspace— never a bare~, which misfires under spanishbabelbefore}or-) and tabs expand to 4, so indentation survives. A line wider than ~90 columns warns (code line is {n} chars wide …) — split the line; TeX still reports its own overfull boxes as well. - Special characters (
\ { } $ & # _ % ~ ^ < > " ' `) are escaped at the character level, so code can never be misread as LaTeX ("uses\char34{}, which stays safe underbabelshorthands such as spanish;%uses\char37{}so spanishbabelcannot drop the preceding space;</>use\char60{}/\char62{}so they print one exact monospace cell instead of a wider math glyph;'/`use\textquotesingle{}/\textasciigrave{}so the listing keeps straight, copy-pasteable quotes).|prints as-is. - The overfull-line and unknown-language warnings name the line numbers of
the build copy as the listing pass received it — before the blocks
are rewritten and before the preamble is injected. Without a diagram
block in front of them those are exactly your source file’s line
numbers; the diagram pass (and, later, the rewrite and injection here)
move the finished build copy. Engine (Tectonic) warnings and errors in
a file the pass rewrote — or in the entry, where the preamble is
injected even when its own blocks live in an
\input— are remapped back through the build-copy line map, sotexforge buildpoints them at those same pass-input coordinates (same caveat: with a diagram block in front they are build-copy lines, not source lines; files reached via\inputmay be reported with or without.tex, and the map normalises both). (An unknown option warns without a line: the option parser is shared with the diagram blocks.) - Only a real block counts: a
\begin{code}behind a%, or quoted insideverbatim/lstlisting, is text — the pass ignores it and the document stays byte-identical. codeis a verbatim environment fortexforge check,fmtandwc(likelstlistingandminted): code never triggers prose linter rules, never counts as words, andtexforge fmtpasses its body through untouched.- The
codeenvironment name and the\tfxcommand prefix are reserved: defining them yourself fails the build with a file:line error telling you to rename.
lstlisting opt-in
Existing listings.sty documents are left alone unless you ask:
[highlight]
lstlisting = true
Then \begin{lstlisting}[language=Python, numbers=left]…\end{lstlisting}
blocks are rewritten the same way: language= (case-insensitive, [ISO]
dialect prefixes stripped) carries over, numbers=left|right turns the
gutter on (and numbers=none turns it off, even against a document-wide
[highlight] numbers = true), and every other listings option is dropped
with the usual unknown-option warning — except the caption vocabulary,
which maps onto the same behaviour: caption=, label=,
float=/placement= (first present wins) and a
basicstyle=\footnotesize-style font size (\scriptsize, \footnotesize,
small, \normalsize; \smallskip is not mistaken for \small).
Why not minted
minted shells out to Pygments, which needs -shell-escape and a Python
installation — both outside what texforge enables. That is why
texforge check warns on \usepackage{minted} and suggests the code
environment instead.
Worked example
\documentclass{article}
\begin{document}
\section{Setup}
\begin{code}[lang=bash]
# install dependencies
cargo build --release
\end{code}
\begin{code}[lang=rust, numbers=true]
fn main() {
println!("fib(10) = {}", fib(10));
}
\end{code}
\end{document}
With [highlight] theme = "one-light" in project.toml, both blocks render
in the One Light palette; the Rust block gains line numbers. Add
[highlight.by_lang] bash = "dark" and every shell block moves to a
terminal-dark frame while the rest of the document is untouched.