Skip to content

feat(typst): add code annotation and filename support#14170

Open
mcanouil wants to merge 29 commits into
quarto-dev:mainfrom
mcanouil:feat/typst-annotation-filename
Open

feat(typst): add code annotation and filename support#14170
mcanouil wants to merge 29 commits into
quarto-dev:mainfrom
mcanouil:feat/typst-annotation-filename

Conversation

@mcanouil

@mcanouil mcanouil commented Mar 6, 2026

Copy link
Copy Markdown
Collaborator

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).

A rendered PDF document titled "Code Annotations & Filename" showing three sections.
The first section, "Code Cell with Filename and Annotations", displays an R code block with a "hello.R" filename tab above it, containing `print("Hello, world!")` with a circled annotation marker (1) on the right, followed by output `[1] "Hello, world!"` and annotation text "This is a comment in R. It will not be executed as code."
The second section, "Code Cell with Annotations", shows the same R code and output but without the filename tab.
The third section, "Code Cell with Filename", shows the same R code and output with the "hello.R" filename tab but without any annotation marker.
A rendered PDF document showing three sections on page 2.
The first section, "Code Block with Filename and Annotations", displays a Python code block with a "hello.R" filename tab, containing `print("Hello, world!")` with a circled annotation marker (1), and annotation text "This is a comment in R. It will not be executed as code."
The second section, "Code Block with Annotations", shows two lines of Python code (`print("Hello, world!")` and `print("Bye, world!")`) each with a circled annotation marker (1), followed by annotation text "This is a comment in Python. It will not be executed as code."
The third section, "Code Block with Filename", shows a Typst code block with a "hello.typ" filename tab containing `#let hello = "Hello, world!"`.

---
title: "Code Annotations & Filename"
format:
  typst:
    keep-typ: true
    pdf-standard: ua-1
syntax-highlighting: github-dark
code-annotations: true
engine: knitr
---

## Code Cell with Filename and Annotations

```{r}
#| filename: "hello.R"
print("Hello, world!") # <1>
```

1. This is a comment in R. It will not be executed as code.

## Code Cell with Annotations

```{r}
print("Hello, world!") # <1>
```

1. This is a comment in R. It will not be executed as code.

## Code Cell with Filename

```{r}
#| filename: "hello.R"
print("Hello, world!")
```

{{< pagebreak >}}

## Code Block with Filename and Annotations

```{.r filename="hello.R"}
print("Hello, world!") # <1>
```

1. This is a comment in R. It will not be executed as code.

## Code Block with Annotations

```{.python}
print("Hello, world!") # <1>
print("Bye, world!") # <1>
```

1. This is a comment in Python. It will not be executed as code.

## Code Block with Filename

```{.typst filename="hello.typ"}
#let hello = "Hello, world!"
```

To-Do:

  • Investigate the "test-bundle" CI/CD failures
  • Create changelog-1.10.md and add entry

closes #9768

mcanouil added 9 commits March 6, 2026 10:15
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
@mcanouil mcanouil self-assigned this Mar 6, 2026
@mcanouil mcanouil changed the title feat(typst)= Add code annotation and filename support feat(typst): add code annotation and filename support Mar 6, 2026
@posit-snyk-bot

posit-snyk-bot commented Mar 6, 2026

Copy link
Copy Markdown
Collaborator

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@mcanouil
mcanouil marked this pull request as ready for review March 9, 2026 09:56
mcanouil added a commit to mcanouil/quarto-mcanouil that referenced this pull request Mar 23, 2026
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.
mcanouil added a commit to mcanouil/quarto-code-window that referenced this pull request Mar 23, 2026
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.
@mcanouil

mcanouil commented Mar 24, 2026

Copy link
Copy Markdown
Collaborator Author

It appears the Lua limit was already at 200, so anything added at "top-level" was leading the bundle main.lua to exceed the locals limit.
I refactored as a module which is load inside the annotation function, this way no locals are added by require or anything.

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 idiomatic

---
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.
@mcanouil

mcanouil commented Apr 10, 2026

Copy link
Copy Markdown
Collaborator Author

TO-DO: tests in layout structure such as

  • layout-*, callouts, cross-references.
  • list

@cderv cderv assigned cderv and unassigned mcanouil Jul 22, 2026
@cderv
cderv self-requested a review July 22, 2026 15:52
@cderv

cderv commented Jul 22, 2026

Copy link
Copy Markdown
Member

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.
@cderv

cderv commented Jul 23, 2026

Copy link
Copy Markdown
Member

OK this should fix CI

@mcanouil one thing I notice, the change will add a 'stroke' line across all code blocks

Main
image

This branch
image

Is this expected style change for default style (when filename not used) ?

Thanks

Comment thread src/resources/formats/typst/pandoc/quarto/definitions.typ
@mcanouil

Copy link
Copy Markdown
Collaborator Author

I need to check, because it's a workaround around skylighting destructing code blocks.

Comment thread src/format/typst/format-typst.ts Outdated

@cderv cderv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +31 to +34
local function typstAnnotationMarker(annotations, cellId)
local dict = typstAnnotationsDict(annotations)
return pandoc.RawBlock("typst", "// quarto-code-annotations: " .. (cellId or "") .. " " .. dict)
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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. first

throws error: label <annotated-cell-1-annote-1-back> does not exist in the document

@mcanouil

mcanouil commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

The stroke was added to ensure the code block could be distinguished from prose regardless of the theme used. For example, github-lighthas a white or near white background which means you no longer see the block and only the code.

One could argue that's fine, especially since that's the behaviour in HTML for example.
edit: I'll push a commit to make it consistent with HTML.
screenshot showing code blocks formatting in HTML where code block background is white with no stroke

@mcanouil

Copy link
Copy Markdown
Collaborator Author

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.

I can't check the edge cases now, so yes let's put this for 1.11.
I'll use Claude Code to target and find other cases like this to make the feature more robust.

@cderv

cderv commented Jul 23, 2026

Copy link
Copy Markdown
Member

The stroke was added to ensure the code block could be distinguished from prose regardless of the theme used. For example, github-lighthas a white or near white background which means you no longer see the block and only the code.

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.

@cderv cderv added the early-in-release An issue that should be worked on early in the release (likely due to risk) label Jul 23, 2026
@cderv cderv added this to the v1.11 milestone Jul 23, 2026
@mcanouil

mcanouil commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

early-in-release An issue that should be worked on early in the release (likely due to risk)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Typst] Add Code Filename support

3 participants