feat(typst): add code annotation and filename support#14170
Conversation
Add quarto-circled-number, quarto-code-block, and quarto-annotation-item functions for code annotation support and filename bar in Typst output. Route all native raw code blocks through the unified wrapper.
Lua filters need to know whether Skylighting is active to choose between emitting annotation markers (Skylighting) or wrapping CodeBlocks directly (native/none highlighting).
Add helper functions for Typst annotation data (typstAnnotationsDict, typstAnnotationMarker, wrapTypstAnnotatedCode), register Typst annotation processor, and handle standalone CodeBlock, DecoratedCodeBlock, and OL paths for Typst output. Skylighting mode emits a comment marker for the TS post-processor. Native mode wraps CodeBlocks in quarto-code-block with annotations. OL items become quarto-annotation-item raw Typst blocks.
Patch Skylighting function to accept annotations parameter, track line position unconditionally, render circled annotation numbers per line, and route output through quarto-code-block wrapper. Merge Lua-emitted annotation comment markers into Skylighting call sites.
Wrap code blocks with filename attribute in quarto-code-block(filename: ...) for Typst output, rendering a simple filename bar above the code.
Add smoke-all tests for: - Code annotations with Skylighting (default) - Code annotations with native highlighting - Code annotations disabled (none) - Filename bar on code blocks
…o-code-annotation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
…or Skylighting Update the annotation marker regex to support optional #block[ and #quarto-code-filename(...) wrappers in Skylighting call sites. This improves compatibility with various annotation formats.
Add cell-id generation and Typst label/link support so that clicking a circled annotation number in code jumps to its description and vice versa. Add upstream-compatible Quarto functions (quarto-circled-number, quarto-annote-color, quarto-code-filename, quarto-code-annotation, quarto-annotation-item) to definitions.typ for forward compatibility with quarto-dev/quarto-cli#14170.
Update configuration examples to use nested `hotfix` key. Add hotfix options table and a dedicated "Temporary Hot-fixes (Typst)" section explaining that code-annotations and skylighting fixes will be removed once quarto-dev/quarto-cli#14170 lands. Add callout note in example.qmd about the temporary nature of Typst code-annotations support.
Move typstAnnotationsDict, typstAnnotationMarker, and wrapTypstAnnotatedCode from code-annotation.lua into a separate modules/typst-code-annotations.lua loaded via require(). Modules loaded via require() have their own Lua scope and are not inlined into the bundled main.lua, avoiding the 200 local variable limit that the import()-inlined helpers were exceeding.
The bundled main.lua is at exactly 200 top-level locals (the Lua
compiler limit). Move the require("modules/typst-code-annotations")
from file scope into the code_annotations() function body so it
does not add to the top-level local count.
|
It appears the Lua limit was already at 200, so anything added at "top-level" was leading the bundle Note that futur changes in the Lua processing chain is very likely to hit again as there are currently no room for any more locals.
Currently working with ---
format:
typst:
keep-typ: true
syntax-highlighting: idiomatic
---
::: {#exm-one}
## `devtools`
A useful package for R developers.
```{.r filename="R"}
install.packages("devtools")
```
```{.r filename="R"}
install.packages("devtools") # <1>
```
1. Install the `devtools` package.
```r
install.packages("devtools") # <1>
```
1. Install the `devtools` package.
::: |
Add entry for support of code annotations and filename features in Typst output in version 1.10.
|
|
|
Bad merging conflict resolution it seems... oups. I'll fix this |
The merge of main into this branch removed the file-local `constants = require(...)` binding (main migrated this file to the fully-qualified `_quarto.modules.constants` registry). The new Typst code-annotation lines added on this branch still referenced the bare `constants` global, so every Typst render with code annotations or a code filename crashed with "attempt to index a nil value (global 'constants')" — the cause of the CI failures across all test buckets and the feature-format matrix. Qualify the 10 remaining bare references to match the rest of the file.
|
OK this should fix CI @mcanouil one thing I notice, the change will add a 'stroke' line across all code blocks Is this expected style change for default style (when filename not used) ? Thanks |
|
I need to check, because it's a workaround around skylighting destructing code blocks. |
cderv
left a comment
There was a problem hiding this comment.
Thanks for this, nice feature. We don't have a lot in v1.10, I was aiming for a improvement and bug fix only. So maybe this should go to v1.11 early as first feature and first pre-release 🤔
Two edge cases found with 🤖 review help (both Skylighting-path only, native path is fine) that end in a hard Typst compile error rather than a graceful fallback. we should probably see if we can cover for them.
|
|
||
| // Annotation markers emitted by the Lua filter as Typst comments | ||
| const annotationMarkerRe = | ||
| /\/\/ quarto-code-annotations: ([\w-]*) (\([^)]*\))\n(\s*(?:#block\[\s*)*(?:#quarto-code-filename\([^\n]*\)\[\s*)?)#Skylighting\(/g; |
There was a problem hiding this comment.
This could be edge case, but 🤖 had it verified
The cell-id capture here is [\w-]*, but Pandoc identifiers can also contain . and : so a code block whose id has one of those (e.g. a listing id like {#lst-a.b}) never matches, the marker isn't merged into #Skylighting(...), and the …-annote-N-back label is never emitted — while the annotation list below still links to it. Typst then fails to compile:
```{#lst-a.b .python}
x = 1 # <1>
```
1. first
throws something like error: label <lst-a.b-annote-1-back> does not exist in the document
The same document with syntax-highlighting: idiomatic compiles fine, so it's specific to this Skylighting-merge path.
Probably testing this edge case helps covers it.
| local function typstAnnotationMarker(annotations, cellId) | ||
| local dict = typstAnnotationsDict(annotations) | ||
| return pandoc.RawBlock("typst", "// quarto-code-annotations: " .. (cellId or "") .. " " .. dict) | ||
| end |
There was a problem hiding this comment.
Another edge case probably:
typstAnnotationMarker serializes the annotation line numbers as-collected, but wrapTypstAnnotatedCode just below adjusts them for startFrom (lineNo - startFrom + 1). The Skylighting function renders lines starting at 1, so with startFrom set the marker keys point at lines Skylighting never emits → the circle and its …-back label are never produced, and the annotation item links to a missing label:
```{.python startFrom="5"}
x = 1 # <1>
```
1. firstthrows error: label <annotated-cell-1-annote-1-back> does not exist in the document
I can't check the edge cases now, so yes let's put this for 1.11. |
To be clear, this could be a good idea to add this stroke. Maybe it helps with contrast and accessibility too. However, if we do that, we probably want to do it in all formats - unless this is a problem with Typst only. I did not check. |
The "missing stroke" is global, so if we want to address this, it should be in a follow-up PR and keep this PR's scope narrow to bringing the missing feature "as is". The screenshot in #14170 (comment) is from HTML format. |



Introduce a unified wrapper for code blocks and skylighting transformed code blocks in Typst.
Add filename processing (lua/Typst) and code-annotation processing (lua/Typst).
To-Do:
changelog-1.10.mdand add entrycloses #9768