Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
d09689f
feat: add unified quarto-code-block wrapper to Typst definitions
mcanouil Mar 6, 2026
245a4c2
feat: pass syntax-highlighting boolean to Typst filter params
mcanouil Mar 6, 2026
6a84f11
feat: add Typst code annotation processing to Lua filter
mcanouil Mar 6, 2026
684645a
feat: extend skylightingPostProcessor for code annotations
mcanouil Mar 6, 2026
a581426
feat: add Typst renderer for DecoratedCodeBlock filename bar
mcanouil Mar 6, 2026
3a1e874
test: add Typst code annotation and filename bar test documents
mcanouil Mar 6, 2026
60b50b2
refactor: split quarto-code-block into quarto-code-filename and quart…
mcanouil Mar 6, 2026
83414ac
test: expand Typst code annotation and filename test coverage
mcanouil Mar 6, 2026
7426ff8
fix: harden Typst code annotation and filename escaping
mcanouil Mar 6, 2026
5d07770
test: tweak test files
mcanouil Mar 6, 2026
93703a8
fix: merge parent block from code cell with annotation marker regex f…
mcanouil Mar 6, 2026
2cc985c
fix: improve semantic structure by linking code and annotation
mcanouil Mar 7, 2026
ab732fc
test: update tests with semantic links
mcanouil Mar 7, 2026
15cafe9
fix: ensure back-labels are emitted only once
mcanouil Mar 7, 2026
2db3c73
test: update block styling to include stroke in monospace tests
mcanouil Mar 7, 2026
3e23264
test: update block styling to include stroke in brand monospace block
mcanouil Mar 8, 2026
0db9894
fix: escape newline, carriage return, and tab characters in filename
mcanouil Mar 9, 2026
f7fe23e
fix: improve filename escaping in DecoratedCodeBlock renderer
mcanouil Mar 24, 2026
0acb096
refactor: extract Typst annotation helpers into require() module
mcanouil Mar 24, 2026
3a007fa
fix: move Typst annotation require() inside function scope
mcanouil Mar 24, 2026
967cc03
fix(typst): qualify constants refs left bare after main merge
cderv Jul 23, 2026
549b9a1
revert: remove strok for consistency with other formats
mcanouil Jul 23, 2026
9783ec6
fix(typst): make code annotations robust to id, offset, and label mis…
mcanouil Jul 27, 2026
6573854
fix(typst): remove leftover stroke on native code blocks
mcanouil Jul 27, 2026
99f9e28
fix(typst): support code annotations inside crossref listings
mcanouil Jul 27, 2026
c598d77
fix(typst): keep Skylighting path for light/dark highlighting themes
mcanouil Jul 27, 2026
b98ed25
fix(typst): adjust Skylighting annotation keys by startFrom only
mcanouil Jul 27, 2026
cab6d4a
test(typst): cover annotation and filename edge cases
mcanouil Jul 27, 2026
40052de
docs: move changelog entry to 1.11
mcanouil Jul 27, 2026
c69eaf1
fix: keep none and idiomatic distinct for Typst code annotations
mcanouil Oct 26, 2026
9a2631b
fix: apply the brand code background to bgcolor only
mcanouil Oct 26, 2026
0901308
fix: warn when a Typst annotation patch or marker cannot apply
mcanouil Oct 26, 2026
ca7d201
fix: wrap native fenced echo blocks after escaping
mcanouil Oct 26, 2026
b41711d
fix: fit annotated rows inside numbered code blocks
mcanouil Oct 26, 2026
edd1daf
style: format the Typst Skylighting patch code
mcanouil Oct 26, 2026
c3014c6
fix: resolve cross-references in Typst annotation items
mcanouil Oct 26, 2026
ae1def8
fix: add annotation text alternatives and escape cell ids
mcanouil Oct 26, 2026
bbf67a5
fix: keep annotated row height with the annotation text alternative
mcanouil Oct 26, 2026
4e0eb49
fix: keep Typst annotation circles and filename bars readable on dark…
mcanouil Oct 27, 2026
bfc26e7
refactor: clear pending annotation state with clearPending
mcanouil Oct 27, 2026
797f45a
fix: accept spaces in Typst annotation cell ids and normalise line en…
mcanouil Oct 27, 2026
d487b18
test: tie the annotation text alternative test to a rendered annotation
mcanouil Oct 27, 2026
3ddfb29
docs: describe the Typst annotation style in the changelog
mcanouil Oct 27, 2026
221ef44
docs: describe the Typst code annotation pipeline
mcanouil Oct 27, 2026
f328459
feat: add a stroke to Typst code blocks
mcanouil Oct 27, 2026
7332699
fix: keep annotation circles in layout cells and in cells without a list
mcanouil Oct 27, 2026
c9a2a87
fix: let long Typst code filenames wrap instead of clipping
mcanouil Oct 27, 2026
cb7a2ce
test: cover annotation edge cases in Typst output
mcanouil Oct 27, 2026
e79f3e2
fix: keep Typst annotation labels unique for repeated cell ids
mcanouil Oct 27, 2026
ff02107
docs: list the figure div limit for Typst code annotations
mcanouil Oct 27, 2026
a7c72cc
test: document figure divs as a known limit of Typst annotations
mcanouil Oct 27, 2026
3645540
docs: merge the duplicate figure div limit
mcanouil Oct 27, 2026
63764d3
fix: keep annotated Typst code blocks inside figure floats
mcanouil Oct 27, 2026
4a04914
fix: keep Typst annotation cell ids unique across containers
mcanouil Oct 27, 2026
96147ff
docs: mention the Typst code block outline in the changelog
mcanouil Oct 27, 2026
a5aa93c
fix: use a 3:1 contrast outline for Typst code blocks
mcanouil Oct 27, 2026
b4ee05f
fix: stop the annotation circle from adding row height in Typst
mcanouil Oct 27, 2026
fe00834
fix: centre the Typst annotation circles on their text lines
mcanouil Oct 27, 2026
a2a0589
fix: define missing annotation helpers for Typst extensions that repl…
mcanouil Oct 27, 2026
61ba0e1
test: add the partials of the extension that replaces definitions.typ
mcanouil Oct 27, 2026
9d9c05b
fix: remove the extra height the annotation circle added to later ann…
mcanouil Oct 27, 2026
9e6e8fd
docs: explain the zero-height box of the annotation circle
mcanouil Oct 27, 2026
b9771d0
chore: remove build output that was committed by mistake
mcanouil Oct 27, 2026
1a41a4c
fix: break long Typst filenames at separators first
mcanouil Oct 27, 2026
01b76fc
fix: centre the annotation circles on the cap height of their text
mcanouil Oct 27, 2026
241c41f
fix: keep the extension of long Typst filenames attached
mcanouil Oct 27, 2026
b2b9fea
fix: avoid a backslash-dot sequence in the Typst filename helper
mcanouil Oct 27, 2026
2685540
docs: correct the filename breaking thresholds in the Typst annotatio…
mcanouil Oct 27, 2026
543a134
fix: inject the Typst annotation helpers at the top level of the file
mcanouil Oct 27, 2026
3f6374e
fix: warn about duplicate cell ids only for annotated Typst blocks
mcanouil Oct 27, 2026
e1f8b9b
fix: shift annotated lines past the fence of a fenced echo block with…
mcanouil Oct 27, 2026
2bd9f23
Merge branch 'main' into feat/typst-annotation-filename
mcanouil Oct 27, 2026
8c0a078
Merge branch 'main' into feat/typst-annotation-filename
mcanouil Nov 2, 2026
85e0ced
docs: move changelog entry to 1.12
mcanouil Nov 2, 2026
506dd37
test: import the shared asserts in the Typst skylighting patch test
mcanouil Nov 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions llm-docs/typst-code-annotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
main_commit: 405161424
analyzed_date: 2026-10-27
key_files:
- src/format/typst/format-typst.ts
- src/command/render/filters.ts
- src/resources/filters/quarto-pre/code-annotation.lua
- src/resources/filters/modules/typst-code-annotations.lua
- src/resources/formats/typst/pandoc/quarto/definitions.typ
---

# Typst code annotations and filenames

This document describes how Quarto draws code annotations (the circled numbers) and filename bars in Typst output.
Read it before you change the Pandoc version, the line offsets, or the marker format.

## Pipeline

1. `quarto-pre/code-annotation.lua` finds `# <1>` comments in a code block, removes them, and records the line of each annotation.
2. The filter writes the result for the active highlighting mode (next section).
3. The ordered list that follows the code becomes one `#quarto-annotation-item(cell-id, n, [...])` call per item.
The item's own blocks sit between a raw open and a raw close, so `crossref` and citations still run on them.
4. After Pandoc writes the `.typ` file, `src/format/typst/format-typst.ts` patches and merges the output.
5. `src/resources/formats/typst/pandoc/quarto/definitions.typ` holds the Typst helpers.

## Highlighting modes

The filter param `syntax-highlighting-mode` (set in `extractTypstFilterParams`) has three values.

- `skylighting`: Pandoc writes a generated `Skylighting()` function. The Lua filter emits a marker comment before the code block.
- `idiomatic`: Pandoc writes a native Typst `raw` block. The Lua filter wraps the block in `#quarto-code-annotation(annotations, cell-id: ...)[...]`.
- `none`: like `idiomatic`, with no language tag on the block.

## Marker protocol (`skylighting` mode)

The Lua filter writes `// quarto-code-annotations: <cell-id> ("<line>": <n>, ...)` as a raw Typst block.
The post-processor merges a marker into the next `#Skylighting(` call, which becomes `#Skylighting(cell-id: "...", annotations: (...), ...)`.
Only these wrappers may sit between the marker and the call: `#block[`, `#figure([`, and `#quarto-code-filename(...)[`.
A marker that does not merge is removed, and Quarto prints a warning with the cell id.

## Patches to Pandoc's `Skylighting()` function

`patchSkylightingFunction` changes the generated function with named patches: `signature`, `line counter`, `position counter` and `line rendering`.
The block styling and the brand background (`bgcolor` initialiser only) are applied first.
If one named patch does not apply, the function keeps only the block styling, annotations are off for the document, and Quarto prints a warning with the Pandoc version.
After a Pandoc update, run the `typst/code-annotations/` smoke-all documents and `tests/unit/typst-skylighting-patch.test.ts`.

## Line offsets

Two numbers shift the recorded lines, and they differ on purpose.

- `pendingStartFrom` is the display start from a `startFrom` attribute. Typst numbers the displayed lines.
- `pendingOffset` is the first source line. The generated function and `raw.line` both count source lines from 1.

`adjustAnnotations` converts the recorded lines to source lines with `lineNo - offset + 1`.
A fenced echo block (`{{python}}`) gets one extra line, because the filter writes the fence line the way `engine_escape` would, and the offset moves by one.

## Rendering helpers (`definitions.typ`)

- `quarto-circled-number` draws the circle. It sits inside a `math.equation` that carries the text alternative "Annotation N", because `box`, `text` and `link` do not accept `alt`. The circle sits in a zero-height box, so it overflows its row and adds no height. That box needs `baseline: 0.55em` with a `move(dy: -0.63em)`, which centres the circle on the cap height of the text. With the default baseline, a zero-height box still raises the top of its line by about 0.8pt, and the second annotated row ends up taller than a plain row.
- `quarto-annote-link` links only when the target label exists, so a missing list degrades to an unlinked circle.
- `quarto-code-annotation` is the native-mode renderer. The Skylighting patch contains its own per-line copy of the same layout. Change both together.
- With `.number-lines`, annotated rows use `width: 100% - annote-ln-width` so the circle stays on the code line.
- The default code fill is a `set block(fill: ...)` rule, so a brand or user `set block(fill: ...)` wins.

## Known limits

- An extension that replaces `definitions.typ` lacks the annotation helpers. The post-processor defines only the missing ones, before the first block that uses them.
- Code inside a figure with `layout-ncol` (`::: {#fig-a layout-ncol=2}` with a caption) cannot receive its circles, because the layout puts the marker in a grid cell of its own. Quarto prints a warning with the cell id and removes the marker. The list items still render, and the document compiles.
- `hover` and `select` annotation styles use the gutter style. `select` also prints a warning.
- Per-cell `code-annotations` options are not read for any format; only the document-level value counts.
- A filename longer than 40 characters may break after `/`, `_`, `.` and `-`, using zero-width spaces, and a run of more than 30 characters without those may break at any character. The last `.` keeps the extension attached.
- Two copies of the gutter layout exist (the Skylighting patch and `quarto-code-annotation`).

## Tests

`tests/docs/smoke-all/typst/code-annotations/`, `tests/docs/smoke-all/typst/code-filename/`, and `tests/unit/typst-skylighting-patch.test.ts`.
The knitr cell documents need a working R graphics device.
4 changes: 4 additions & 0 deletions news/changelog-1.12.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
All changes included in 1.12:

## Formats

### `typst`

- ([#14170](https://github.com/quarto-dev/quarto-cli/pull/14170)): Add support for code annotations and filename features in Typst output. Typst renders annotations in the gutter, and `hover` and `select` fall back to that style. Typst code blocks now have a thin outline. (author: @mcanouil)
15 changes: 15 additions & 0 deletions src/command/render/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
kFigResponsive,
kFormatIdentifier,
kHeaderIncludes,
kHighlightStyle,
kHtmlMathMethod,
kHtmlPreTagProcessing,
kHtmlTableProcessing,
Expand All @@ -50,11 +51,13 @@ import {
kRemoveHidden,
kResourcePath,
kShortcodes,
kSyntaxHighlighting,
kTblColwidths,
kTocTitleDocument,
kUnrollMarkdownCells,
kUseRsvgConvert,
} from "../../config/constants.ts";
import { kDefaultHighlightStyle } from "./constants.ts";
import { PandocOptions } from "./types.ts";
import {
Format,
Expand Down Expand Up @@ -944,12 +947,24 @@ async function resolveFilterExtension(
return results.flat();
}

const kSyntaxHighlightingMode = "syntax-highlighting-mode";

const extractTypstFilterParams = (format: Format) => {
const theme = format.pandoc[kSyntaxHighlighting] ||
format.pandoc[kHighlightStyle] ||
kDefaultHighlightStyle;
// Object themes (light/dark) resolve to a single .theme file for Typst
// (resolveTextHighlightStyle), so Skylighting is active for them too.
const highlightingMode = theme === "none" || theme === "idiomatic"
? theme
: "skylighting";

return {
[kTocIndent]: format.metadata[kTocIndent],
[kLogo]: format.metadata[kLogo],
[kCssPropertyProcessing]: format.metadata[kCssPropertyProcessing],
[kBrandMode]: format.metadata[kBrandMode],
[kHtmlPreTagProcessing]: format.metadata[kHtmlPreTagProcessing],
[kSyntaxHighlightingMode]: highlightingMode,
};
};
Loading
Loading