From d607d11fd9116b3f9c50e18c8c449cb6dd2c99e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:15:49 +0100 Subject: [PATCH 01/19] feat: add unified quarto-code-block wrapper to Typst definitions 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. --- .../typst/pandoc/quarto/definitions.typ | 48 ++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/resources/formats/typst/pandoc/quarto/definitions.typ b/src/resources/formats/typst/pandoc/quarto/definitions.typ index 40dc1a164b5..b3f1fcba368 100644 --- a/src/resources/formats/typst/pandoc/quarto/definitions.typ +++ b/src/resources/formats/typst/pandoc/quarto/definitions.typ @@ -24,12 +24,48 @@ // Some quarto-specific definitions. -#show raw.where(block: true): set block( - fill: luma(230), - width: 100%, - inset: 8pt, - radius: 2pt - ) +// Code annotation support +#let quarto-circled-number(n) = { + box(baseline: 15%, circle( + radius: 0.55em, + stroke: 0.5pt + text.fill, + )[#set text(size: 0.7em); #align(center + horizon, str(n))]) +} + +#let quarto-code-block(fill: luma(230), filename: none, annotations: (:), body) = { + // Prevent the outer show rule from re-wrapping inner raw blocks + show raw.where(block: true): it => it + // Handle annotations for native raw blocks (no-op when annotations is empty) + show raw.line: it => { + let annote-num = annotations.at(str(it.number), default: none) + if annote-num != none { + box(width: 100%)[#it #h(1fr) #quarto-circled-number(annote-num)] + } else { + it + } + } + block(fill: fill, width: 100%, inset: 0pt, radius: 2pt, clip: true)[ + #if filename != none { + block( + fill: fill.darken(10%), + width: 100%, + inset: (x: 8pt, y: 4pt), + )[#text(size: 0.85em, weight: "bold")[#filename]] + } + #block(inset: 8pt, width: 100%, body) + ] +} + +#let quarto-annotation-item(n, content) = { + block(above: 0.4em, below: 0.4em)[ + #quarto-circled-number(n) + #h(0.4em) + #content + ] +} + +// Route all native raw code blocks through the unified wrapper +#show raw.where(block: true): it => quarto-code-block(it) #let block_with_new_content(old_block, new_content) = { let fields = old_block.fields() From b3e04ce9d3c0b4dbbadfebba0772e9f3bede75db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:21:09 +0100 Subject: [PATCH 02/19] feat: pass syntax-highlighting boolean to Typst filter params Lua filters need to know whether Skylighting is active to choose between emitting annotation markers (Skylighting) or wrapping CodeBlocks directly (native/none highlighting). --- src/command/render/filters.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/command/render/filters.ts b/src/command/render/filters.ts index 863d3d9603a..ff2cfe685a8 100644 --- a/src/command/render/filters.ts +++ b/src/command/render/filters.ts @@ -51,10 +51,13 @@ import { kResourcePath, kShortcodes, kTblColwidths, + kHighlightStyle, + kSyntaxHighlighting, kTocTitleDocument, kUnrollMarkdownCells, kUseRsvgConvert, } from "../../config/constants.ts"; +import { kDefaultHighlightStyle } from "./constants.ts"; import { PandocOptions } from "./types.ts"; import { Format, @@ -945,11 +948,19 @@ async function resolveFilterExtension( } const extractTypstFilterParams = (format: Format) => { + const theme = + format.pandoc[kSyntaxHighlighting] || + format.pandoc[kHighlightStyle] || + kDefaultHighlightStyle; + const skylighting = + typeof theme === "string" && theme !== "none" && theme !== "idiomatic"; + return { [kTocIndent]: format.metadata[kTocIndent], [kLogo]: format.metadata[kLogo], [kCssPropertyProcessing]: format.metadata[kCssPropertyProcessing], [kBrandMode]: format.metadata[kBrandMode], [kHtmlPreTagProcessing]: format.metadata[kHtmlPreTagProcessing], + [kSyntaxHighlighting]: skylighting, }; }; From e50c0bd363ab7124ba4fdc6a70a44abe3a3cc89c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:27:00 +0100 Subject: [PATCH 03/19] feat: add Typst code annotation processing to Lua filter 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. --- src/resources/filters/modules/constants.lua | 2 + .../filters/quarto-pre/code-annotation.lua | 156 +++++++++++++++++- 2 files changed, 149 insertions(+), 9 deletions(-) diff --git a/src/resources/filters/modules/constants.lua b/src/resources/filters/modules/constants.lua index cb22ee3be64..0fd8d292152 100644 --- a/src/resources/filters/modules/constants.lua +++ b/src/resources/filters/modules/constants.lua @@ -33,6 +33,7 @@ local kAsciidocNativeCites = 'use-asciidoc-native-cites' local kShowNotes = 'showNotes' local kProjectResolverIgnore = 'project-resolve-ignore' +local kSyntaxHighlighting = 'syntax-highlighting' local kCodeAnnotationsParam = 'code-annotations' local kDataCodeCellTarget = 'data-code-cell' local kDataCodeCellLines = 'data-code-lines' @@ -200,6 +201,7 @@ return { kAsciidocNativeCites = kAsciidocNativeCites, kShowNotes = kShowNotes, kProjectResolverIgnore = kProjectResolverIgnore, + kSyntaxHighlighting = kSyntaxHighlighting, kCodeAnnotationsParam = kCodeAnnotationsParam, kDataCodeCellTarget = kDataCodeCellTarget, kDataCodeCellLines = kDataCodeCellLines, diff --git a/src/resources/filters/quarto-pre/code-annotation.lua b/src/resources/filters/quarto-pre/code-annotation.lua index e09e9f036fb..e472d219c3d 100644 --- a/src/resources/filters/quarto-pre/code-annotation.lua +++ b/src/resources/filters/quarto-pre/code-annotation.lua @@ -81,7 +81,60 @@ local function toAnnoteId(number) end local function latexListPlaceholder(number) - return '5CB6E08D-list-annote-' .. number + return '5CB6E08D-list-annote-' .. number +end + +-- Typst annotation helpers + +-- Convert annotations table to a flat Typst dictionary string. +-- Keys are line positions (as strings), values are annotation numbers. +local function typstAnnotationsDict(annotations) + local entries = {} + for annoteId, lineNumbers in pairs(annotations) do + local num = annoteId:match("annote%-(%d+)") + if num then + for _, lineNo in ipairs(lineNumbers) do + table.insert(entries, {pos = lineNo, annoteNum = tonumber(num)}) + end + end + end + table.sort(entries, function(a, b) return a.pos < b.pos end) + local parts = {} + for _, e in ipairs(entries) do + table.insert(parts, '"' .. tostring(e.pos) .. '": ' .. tostring(e.annoteNum)) + end + return '(' .. table.concat(parts, ', ') .. ')' +end + +-- Skylighting mode: emit a Typst comment that the TS post-processor +-- will merge into the Skylighting call site. +local function typstAnnotationMarker(annotations) + local dict = typstAnnotationsDict(annotations) + return pandoc.RawBlock("typst", "// quarto-code-annotations: " .. dict) +end + +-- Native/none mode: wrap a CodeBlock in #quarto-code-block(annotations: ...). +-- raw.line numbers always start at 1 regardless of startFrom, so adjust keys. +local function wrapTypstAnnotatedCode(codeBlock, annotations) + local startFrom = tonumber(codeBlock.attr.attributes['startFrom']) or 1 + local adjustedAnnotations = {} + for annoteId, lineNumbers in pairs(annotations) do + local adjusted = pandoc.List({}) + for _, lineNo in ipairs(lineNumbers) do + adjusted:insert(lineNo - startFrom + 1) + end + adjustedAnnotations[annoteId] = adjusted + end + local dict = typstAnnotationsDict(adjustedAnnotations) + local lang = codeBlock.attr.classes[1] or "" + local code = codeBlock.text + local maxBackticks = 2 + for seq in code:gmatch("`+") do + maxBackticks = math.max(maxBackticks, #seq) + end + local fence = string.rep("`", maxBackticks + 1) + local raw = "#quarto-code-block(annotations: " .. dict .. ")[" .. fence .. lang .. "\n" .. code .. "\n" .. fence .. "]" + return pandoc.RawBlock("typst", raw) end local function toLines(s) @@ -263,11 +316,15 @@ end function processAnnotation(line, annoteNumber, annotationProvider) -- For all other formats, just strip the annotation- the definition list is converted - -- to be based upon line numbers. + -- to be based upon line numbers. local stripped = annotationProvider.stripAnnotation(line, annoteNumber) return stripped end +function processTypstAnnotation(line, annoteNumber, annotationProvider) + return annotationProvider.stripAnnotation(line, annoteNumber) +end + function code_meta() return { Meta = function(meta) @@ -361,6 +418,8 @@ function code_annotations() annotationProcessor = processLaTeXAnnotation elseif _quarto.format.isAsciiDocOutput() then annotationProcessor = processAsciidocAnnotation + elseif _quarto.format.isTypstOutput() then + annotationProcessor = processTypstAnnotation end -- resolve annotations @@ -427,14 +486,26 @@ function code_annotations() if #block.content == 1 and #block.content[1].content == 1 then -- Find the code block and process that local codeblock = block.content[1].content[1] - + local cellId = resolveCellId(codeblock.attr.identifier) local codeCell = processCodeCell(codeblock, cellId) if codeCell then if codeAnnotations ~= constants.kCodeAnnotationStyleNone then codeCell.attr.identifier = cellId; end - block.content[1].content[1] = codeCell + -- Typst DecoratedCodeBlock: emit annotation data + if _quarto.format.isTypstOutput() + and codeAnnotations ~= constants.kCodeAnnotationStyleNone + and pendingAnnotations and next(pendingAnnotations) ~= nil then + if param(constants.kSyntaxHighlighting, true) then + outputBlock(typstAnnotationMarker(pendingAnnotations)) + block.content[1].content[1] = codeCell + else + block.content[1].content[1] = wrapTypstAnnotatedCode(codeCell, pendingAnnotations) + end + else + block.content[1].content[1] = codeCell + end outputBlock(block) else outputBlockClearPending(block) @@ -460,7 +531,20 @@ function code_annotations() if codeAnnotations ~= constants.kCodeAnnotationStyleNone then codeCell.attr.identifier = cellId; end - outputBlock(codeCell) + -- Typst standalone CodeBlock: emit annotation data alongside + -- the code block. The OL handler will emit annotation items. + if _quarto.format.isTypstOutput() + and codeAnnotations ~= constants.kCodeAnnotationStyleNone + and pendingAnnotations and next(pendingAnnotations) ~= nil then + if param(constants.kSyntaxHighlighting, true) then + outputBlock(typstAnnotationMarker(pendingAnnotations)) + outputBlock(codeCell) + else + outputBlock(wrapTypstAnnotatedCode(codeCell, pendingAnnotations)) + end + else + outputBlock(codeCell) + end else outputBlockClearPending(block) end @@ -468,6 +552,59 @@ function code_annotations() outputBlockClearPending(block) end elseif block.t == 'OrderedList' and pendingAnnotations ~= nil and next(pendingAnnotations) ~= nil then + + -- Typst: emit annotation items as raw Typst blocks + if _quarto.format.isTypstOutput() and codeAnnotations ~= constants.kCodeAnnotationStyleNone then + local annotationBlocks = pandoc.List() + for i, v in ipairs(block.content) do + local annotationNumber = block.start + i - 1 + local annoteId = toAnnoteId(annotationNumber) + if pendingAnnotations[annoteId] then + local content = pandoc.utils.stringify(v[1]) + annotationBlocks:insert(pandoc.RawBlock("typst", + "#quarto-annotation-item(" .. tostring(annotationNumber) .. ", [" .. content .. "])")) + end + end + + local useSkylighting = param(constants.kSyntaxHighlighting, true) + + if pendingCodeCell ~= nil then + local resolvedCell = _quarto.ast.walk(pendingCodeCell, { + CodeBlock = function(el) + if el.attr.classes:find('cell-code') or + el.attr.classes:find(constants.kDataCodeAnnonationClz) then + if useSkylighting then + return nil + else + return wrapTypstAnnotatedCode(el, pendingAnnotations) + end + end + end + }) + + if useSkylighting then + outputBlock(typstAnnotationMarker(pendingAnnotations)) + end + + local dlDiv = pandoc.Div(annotationBlocks, pandoc.Attr("", {constants.kCellAnnotationClass})) + if is_custom_node(resolvedCell) then + local custom = _quarto.ast.resolve_custom_data(resolvedCell) or pandoc.Div({}) + custom.content:insert(2, dlDiv) + else + resolvedCell.content:insert(2, dlDiv) + end + outputBlock(resolvedCell) + else + for _, ab in ipairs(annotationBlocks) do + outputBlock(ab) + end + end + pendingAnnotations = nil + pendingCellId = nil + pendingCodeCell = nil + + -- Generic handler for all other formats + else -- There are pending annotations, which means this OL is immediately after -- a code cell with annotations. Use to emit a DL describing the code local items = pandoc.List() @@ -496,7 +633,7 @@ function code_annotations() end -- compute the definition for the DD - local definitionContent = v[1].content + local definitionContent = v[1].content local annotationToken = tostring(annotationNumber); -- Only output span for certain formats (HTML) @@ -511,7 +648,7 @@ function code_annotations() {constants.kDataCodeCellAnnotation, annotationToken} } definition = pandoc.Span(definitionContent, pandoc.Attr(attribs)) - else + else definition = pandoc.Plain(definitionContent) end @@ -554,15 +691,16 @@ function code_annotations() flushPending() else if requireNonIncremental then - -- wrap in Non Incremental Div to prevent automatique + -- wrap in Non Incremental Div to prevent automatique outputBlockClearPending(pandoc.Div({dl}, pandoc.Attr("", {constants.kNonIncremental}))) - else + else outputBlockClearPending(dl) end end else flushPending() end + end -- end generic handler else outputBlockClearPending(block) end From 6ce0416f24797cb2643ab53b34ec35c0665c0d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:29:52 +0100 Subject: [PATCH 04/19] feat: extend skylightingPostProcessor for code annotations 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. --- src/format/typst/format-typst.ts | 93 +++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 18 deletions(-) diff --git a/src/format/typst/format-typst.ts b/src/format/typst/format-typst.ts index 71f06570226..d4c8f00bea7 100644 --- a/src/format/typst/format-typst.ts +++ b/src/format/typst/format-typst.ts @@ -187,40 +187,97 @@ export function typstFormat(): Format { // When brand provides a monospace-block background-color, also overrides the // bgcolor value. This is a temporary workaround until the fix is upstreamed // to the Skylighting library. +// +// Additionally patches the Skylighting function for code annotation support: +// adds an annotations parameter, moves line tracking outside the if-number +// block, adds per-line annotation rendering, and routes output through +// quarto-code-block(). Also merges annotation comment markers from the Lua +// filter into Skylighting call sites. +// +// Upstream compatibility: a PR to skylighting-format-typst +// (fix/typst-skylighting-block-style) adds block styling upstream. Once merged +// and picked up by Pandoc, the block styling patch becomes a no-op (the +// replace target won't match). The brand color regex targets rgb("...") which +// works with both current and future upstream bgcolor init patterns. function skylightingPostProcessor(brandBgColor?: string) { // Match the entire #let Skylighting(...) = { ... } function. // The signature is stable and generated by Skylighting's Typst backend. const skylightingFnRe = /(#let Skylighting\(fill: none, number: false, start: 1, sourcelines\) = \{[\s\S]*?\n\})/; + // Annotation markers emitted by the Lua filter as Typst comments + const annotationMarkerRe = + /\/\/ quarto-code-annotations: (\([^)]*\))\n\s*#Skylighting\(/g; + return async (output: string) => { - const content = Deno.readTextFileSync(output); + let content = Deno.readTextFileSync(output); + let changed = false; const match = skylightingFnRe.exec(content); - if (!match) { - // No Skylighting function found — document may not have code blocks, - // or upstream changed the function signature. Nothing to patch. - return; - } + if (match) { + let fn = match[1]; - let fn = match[1]; + // Fix block() call: add width, inset, radius + fn = fn.replace( + "block(fill: bgcolor, blocks)", + "block(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks)", + ); - // Fix block() call: add width, inset, radius - fn = fn.replace( - "block(fill: bgcolor, blocks)", - "block(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks)", - ); + // Override bgcolor with brand monospace-block background-color + if (brandBgColor) { + fn = fn.replace( + /rgb\("[^"]*"\)/, + `rgb("${brandBgColor}")`, + ); + } + + // Add annotations parameter to function signature + fn = fn.replace( + "start: 1, sourcelines)", + "start: 1, annotations: (:), sourcelines)", + ); + + // Move lnum increment outside if-number block (always track position) + fn = fn.replace( + /if number \{\n\s+lnum = lnum \+ 1\n/, + "lnum = lnum + 1\n if number {\n", + ); + + // Add annotation rendering per line + fn = fn.replace( + "blocks = blocks + ln + EndLine()", + `let annote-num = annotations.at(str(lnum), default: none) + if annote-num != none { + blocks = blocks + box(width: 100%)[#ln #h(1fr) #quarto-circled-number(annote-num)] + EndLine() + } else { + blocks = blocks + ln + EndLine() + }`, + ); - // Override bgcolor with brand monospace-block background-color - if (brandBgColor) { + // Route through unified quarto-code-block wrapper fn = fn.replace( - /let bgcolor = rgb\("[^"]*"\)/, - `let bgcolor = rgb("${brandBgColor}")`, + /block\(fill: bgcolor,[^)]*blocks\)/, + "quarto-code-block(fill: bgcolor, blocks)", ); + + if (fn !== match[1]) { + content = content.replace(match[1], fn); + changed = true; + } + } + + // Merge annotation markers into Skylighting call sites + const merged = content.replace( + annotationMarkerRe, + "#Skylighting(annotations: $1, (", + ); + if (merged !== content) { + content = merged; + changed = true; } - if (fn !== match[1]) { - Deno.writeTextFileSync(output, content.replace(match[1], fn)); + if (changed) { + Deno.writeTextFileSync(output, content); } }; } From 80b2fcfa253dbc665ecb42156d30292fc5ac12a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:30:42 +0100 Subject: [PATCH 05/19] feat: add Typst renderer for DecoratedCodeBlock filename bar Wrap code blocks with filename attribute in quarto-code-block(filename: ...) for Typst output, rendering a simple filename bar above the code. --- .../customnodes/decoratedcodeblock.lua | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/resources/filters/customnodes/decoratedcodeblock.lua b/src/resources/filters/customnodes/decoratedcodeblock.lua index 5aeefb0a55b..7de4513ed19 100644 --- a/src/resources/filters/customnodes/decoratedcodeblock.lua +++ b/src/resources/filters/customnodes/decoratedcodeblock.lua @@ -180,3 +180,26 @@ _quarto.ast.add_renderer("DecoratedCodeBlock", return pandoc.Div(blocks, pandoc.Attr("", classes)) end) + +-- typst renderer +_quarto.ast.add_renderer("DecoratedCodeBlock", + function(_) + return _quarto.format.isTypstOutput() + end, + function(node) + if node.filename == nil then + return _quarto.ast.walk(quarto.utils.as_blocks(node.code_block), { + CodeBlock = render_folded_block + }) + end + local el = node.code_block + local rendered = _quarto.ast.walk(quarto.utils.as_blocks(el), { + CodeBlock = render_folded_block + }) or pandoc.Blocks({}) + local blocks = pandoc.Blocks({}) + blocks:insert(pandoc.RawBlock("typst", + '#quarto-code-block(filename: "' .. node.filename .. '")[')) + blocks:extend(rendered) + blocks:insert(pandoc.RawBlock("typst", "]")) + return pandoc.Div(blocks) + end) From 651075ed4fd5551058628a14ac994f60ba9a4caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:36:30 +0100 Subject: [PATCH 06/19] test: add Typst code annotation and filename bar test documents Add smoke-all tests for: - Code annotations with Skylighting (default) - Code annotations with native highlighting - Code annotations disabled (none) - Filename bar on code blocks --- .../code-annotations-native.qmd | 25 +++++++++++++++++++ .../code-annotations-none.qmd | 24 ++++++++++++++++++ .../code-annotations/code-annotations.qmd | 25 +++++++++++++++++++ .../typst/code-filename/code-filename.qmd | 18 +++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 tests/docs/smoke-all/typst/code-annotations/code-annotations-native.qmd create mode 100644 tests/docs/smoke-all/typst/code-annotations/code-annotations-none.qmd create mode 100644 tests/docs/smoke-all/typst/code-annotations/code-annotations.qmd create mode 100644 tests/docs/smoke-all/typst/code-filename/code-filename.qmd diff --git a/tests/docs/smoke-all/typst/code-annotations/code-annotations-native.qmd b/tests/docs/smoke-all/typst/code-annotations/code-annotations-native.qmd new file mode 100644 index 00000000000..bc2a59e1749 --- /dev/null +++ b/tests/docs/smoke-all/typst/code-annotations/code-annotations-native.qmd @@ -0,0 +1,25 @@ +--- +title: Code Annotations (Native) +format: + typst: + keep-typ: true + code-annotations: true + syntax-highlighting: idiomatic +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - "quarto-code-block" + - "quarto-annotation-item" + - [] +--- + +```python +x = 1 # <1> +y = 2 +z = x + y # <2> +``` + +1. Assign x. +2. Compute sum. diff --git a/tests/docs/smoke-all/typst/code-annotations/code-annotations-none.qmd b/tests/docs/smoke-all/typst/code-annotations/code-annotations-none.qmd new file mode 100644 index 00000000000..eb28056cad8 --- /dev/null +++ b/tests/docs/smoke-all/typst/code-annotations/code-annotations-none.qmd @@ -0,0 +1,24 @@ +--- +title: Code Annotations (None) +format: + typst: + keep-typ: true + code-annotations: none +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - [] + - + - "quarto-annotation-item" + - "quarto-circled-number" +--- + +```python +x = 1 # <1> +y = 2 +z = x + y # <2> +``` + +1. Assign x. +2. Compute sum. diff --git a/tests/docs/smoke-all/typst/code-annotations/code-annotations.qmd b/tests/docs/smoke-all/typst/code-annotations/code-annotations.qmd new file mode 100644 index 00000000000..d8ff469d811 --- /dev/null +++ b/tests/docs/smoke-all/typst/code-annotations/code-annotations.qmd @@ -0,0 +1,25 @@ +--- +title: Code Annotations (Skylighting) +format: + typst: + keep-typ: true + code-annotations: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - "quarto-circled-number" + - "quarto-annotation-item" + - "annotations:" + - [] +--- + +```python +x = 1 # <1> +y = 2 +z = x + y # <2> +``` + +1. Assign x. +2. Compute sum. diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename.qmd new file mode 100644 index 00000000000..bef4754ca40 --- /dev/null +++ b/tests/docs/smoke-all/typst/code-filename/code-filename.qmd @@ -0,0 +1,18 @@ +--- +title: Code Filename Bar +format: + typst: + keep-typ: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - 'quarto-code-block\(filename:' + - [] +--- + +```{.python filename="example.py"} +def hello(): + print("Hello, world!") +``` From b5c6da87fab2ca83d6b3a363448c5d7e5ee82597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:36:11 +0100 Subject: [PATCH 07/19] refactor: split quarto-code-block into quarto-code-filename and quarto-code-annotation --- src/format/typst/format-typst.ts | 16 ++---- .../customnodes/decoratedcodeblock.lua | 2 +- .../filters/quarto-pre/code-annotation.lua | 30 ++++++----- .../typst/pandoc/quarto/definitions.typ | 54 ++++++++++++------- .../code-annotations-native.qmd | 2 +- .../typst/code-filename/code-filename.qmd | 2 +- 6 files changed, 61 insertions(+), 45 deletions(-) diff --git a/src/format/typst/format-typst.ts b/src/format/typst/format-typst.ts index d4c8f00bea7..40abe93e041 100644 --- a/src/format/typst/format-typst.ts +++ b/src/format/typst/format-typst.ts @@ -217,10 +217,10 @@ function skylightingPostProcessor(brandBgColor?: string) { if (match) { let fn = match[1]; - // Fix block() call: add width, inset, radius + // Fix block() call: add width, inset, radius, stroke fn = fn.replace( "block(fill: bgcolor, blocks)", - "block(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks)", + "block(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, stroke: 0.5pt + luma(200), blocks)", ); // Override bgcolor with brand monospace-block background-color @@ -243,23 +243,17 @@ function skylightingPostProcessor(brandBgColor?: string) { "lnum = lnum + 1\n if number {\n", ); - // Add annotation rendering per line + // Add annotation rendering per line (derive circle colour from bgcolor) fn = fn.replace( "blocks = blocks + ln + EndLine()", `let annote-num = annotations.at(str(lnum), default: none) if annote-num != none { - blocks = blocks + box(width: 100%)[#ln #h(1fr) #quarto-circled-number(annote-num)] + EndLine() + blocks = blocks + box(width: 100%)[#ln #h(1fr) #quarto-circled-number(annote-num, color: quarto-annote-color(bgcolor))] + EndLine() } else { blocks = blocks + ln + EndLine() }`, ); - // Route through unified quarto-code-block wrapper - fn = fn.replace( - /block\(fill: bgcolor,[^)]*blocks\)/, - "quarto-code-block(fill: bgcolor, blocks)", - ); - if (fn !== match[1]) { content = content.replace(match[1], fn); changed = true; @@ -269,7 +263,7 @@ function skylightingPostProcessor(brandBgColor?: string) { // Merge annotation markers into Skylighting call sites const merged = content.replace( annotationMarkerRe, - "#Skylighting(annotations: $1, (", + "#Skylighting(annotations: $1, ", ); if (merged !== content) { content = merged; diff --git a/src/resources/filters/customnodes/decoratedcodeblock.lua b/src/resources/filters/customnodes/decoratedcodeblock.lua index 7de4513ed19..f355f378342 100644 --- a/src/resources/filters/customnodes/decoratedcodeblock.lua +++ b/src/resources/filters/customnodes/decoratedcodeblock.lua @@ -198,7 +198,7 @@ _quarto.ast.add_renderer("DecoratedCodeBlock", }) or pandoc.Blocks({}) local blocks = pandoc.Blocks({}) blocks:insert(pandoc.RawBlock("typst", - '#quarto-code-block(filename: "' .. node.filename .. '")[')) + '#quarto-code-filename("' .. node.filename .. '")[')) blocks:extend(rendered) blocks:insert(pandoc.RawBlock("typst", "]")) return pandoc.Div(blocks) diff --git a/src/resources/filters/quarto-pre/code-annotation.lua b/src/resources/filters/quarto-pre/code-annotation.lua index e472d219c3d..19a41adecce 100644 --- a/src/resources/filters/quarto-pre/code-annotation.lua +++ b/src/resources/filters/quarto-pre/code-annotation.lua @@ -113,7 +113,7 @@ local function typstAnnotationMarker(annotations) return pandoc.RawBlock("typst", "// quarto-code-annotations: " .. dict) end --- Native/none mode: wrap a CodeBlock in #quarto-code-block(annotations: ...). +-- Native/none mode: wrap a CodeBlock in #quarto-code-annotation(annotations)[...]. -- raw.line numbers always start at 1 regardless of startFrom, so adjust keys. local function wrapTypstAnnotatedCode(codeBlock, annotations) local startFrom = tonumber(codeBlock.attr.attributes['startFrom']) or 1 @@ -133,7 +133,7 @@ local function wrapTypstAnnotatedCode(codeBlock, annotations) maxBackticks = math.max(maxBackticks, #seq) end local fence = string.rep("`", maxBackticks + 1) - local raw = "#quarto-code-block(annotations: " .. dict .. ")[" .. fence .. lang .. "\n" .. code .. "\n" .. fence .. "]" + local raw = "#quarto-code-annotation(" .. dict .. ")[" .. fence .. lang .. "\n" .. code .. "\n" .. fence .. "]" return pandoc.RawBlock("typst", raw) end @@ -322,7 +322,8 @@ function processAnnotation(line, annoteNumber, annotationProvider) end function processTypstAnnotation(line, annoteNumber, annotationProvider) - return annotationProvider.stripAnnotation(line, annoteNumber) + local stripped = annotationProvider.stripAnnotation(line, annoteNumber) + return stripped end function code_meta() @@ -483,7 +484,9 @@ function code_annotations() -- output the pending code cell and continue flushPending() - if #block.content == 1 and #block.content[1].content == 1 then + if #block.content == 1 and #block.content[1].content == 1 + and block.content[1].content[1] ~= nil + and block.content[1].content[1].t == "CodeBlock" then -- Find the code block and process that local codeblock = block.content[1].content[1] @@ -493,13 +496,14 @@ function code_annotations() if codeAnnotations ~= constants.kCodeAnnotationStyleNone then codeCell.attr.identifier = cellId; end - -- Typst DecoratedCodeBlock: emit annotation data + -- Typst DecoratedCodeBlock: embed annotation data inside the block + -- so the marker stays adjacent to the Skylighting call after rendering if _quarto.format.isTypstOutput() and codeAnnotations ~= constants.kCodeAnnotationStyleNone and pendingAnnotations and next(pendingAnnotations) ~= nil then if param(constants.kSyntaxHighlighting, true) then - outputBlock(typstAnnotationMarker(pendingAnnotations)) block.content[1].content[1] = codeCell + block.content[1].content:insert(1, typstAnnotationMarker(pendingAnnotations)) else block.content[1].content[1] = wrapTypstAnnotatedCode(codeCell, pendingAnnotations) end @@ -582,16 +586,18 @@ function code_annotations() end }) - if useSkylighting then - outputBlock(typstAnnotationMarker(pendingAnnotations)) - end - local dlDiv = pandoc.Div(annotationBlocks, pandoc.Attr("", {constants.kCellAnnotationClass})) if is_custom_node(resolvedCell) then local custom = _quarto.ast.resolve_custom_data(resolvedCell) or pandoc.Div({}) - custom.content:insert(2, dlDiv) + if useSkylighting then + custom.content:insert(1, typstAnnotationMarker(pendingAnnotations)) + end + custom.content:insert(dlDiv) else - resolvedCell.content:insert(2, dlDiv) + if useSkylighting then + resolvedCell.content:insert(1, typstAnnotationMarker(pendingAnnotations)) + end + resolvedCell.content:insert(dlDiv) end outputBlock(resolvedCell) else diff --git a/src/resources/formats/typst/pandoc/quarto/definitions.typ b/src/resources/formats/typst/pandoc/quarto/definitions.typ index b3f1fcba368..cefb9453a8d 100644 --- a/src/resources/formats/typst/pandoc/quarto/definitions.typ +++ b/src/resources/formats/typst/pandoc/quarto/definitions.typ @@ -25,35 +25,48 @@ // Some quarto-specific definitions. // Code annotation support -#let quarto-circled-number(n) = { +#let quarto-circled-number(n, color: none) = context { + let c = if color != none { color } else { text.fill } box(baseline: 15%, circle( radius: 0.55em, - stroke: 0.5pt + text.fill, - )[#set text(size: 0.7em); #align(center + horizon, str(n))]) + stroke: 0.5pt + c, + )[#set text(size: 0.7em, fill: c); #align(center + horizon, str(n))]) } -#let quarto-code-block(fill: luma(230), filename: none, annotations: (:), body) = { - // Prevent the outer show rule from re-wrapping inner raw blocks +// Derive a contrasting annotation colour from a background fill. +// Light backgrounds get dark circles; dark backgrounds get light circles. +// Uses relative luminance: 0.2126R + 0.7152G + 0.0722B. +#let quarto-annote-color(bg) = { + if type(bg) == color { + let (r, g, b, ..) = bg.components(alpha: false) + let lum = 0.2126 * r / 100% + 0.7152 * g / 100% + 0.0722 * b / 100% + if lum < 0.5 { luma(200) } else { luma(60) } + } else { + luma(60) + } +} + +#let quarto-code-filename(filename, body) = { + show raw.where(block: true): it => it + block(width: 100%, radius: 2pt, clip: true, stroke: 0.5pt + luma(200))[ + #set block(spacing: 0pt) + #block(fill: luma(220), width: 100%, inset: (x: 8pt, y: 4pt))[ + #text(size: 0.85em, weight: "bold")[#filename]] + #body + ] +} + +#let quarto-code-annotation(annotations, color: luma(60), body) = { show raw.where(block: true): it => it - // Handle annotations for native raw blocks (no-op when annotations is empty) show raw.line: it => { let annote-num = annotations.at(str(it.number), default: none) if annote-num != none { - box(width: 100%)[#it #h(1fr) #quarto-circled-number(annote-num)] + box(width: 100%)[#it #h(1fr) #quarto-circled-number(annote-num, color: color)] } else { it } } - block(fill: fill, width: 100%, inset: 0pt, radius: 2pt, clip: true)[ - #if filename != none { - block( - fill: fill.darken(10%), - width: 100%, - inset: (x: 8pt, y: 4pt), - )[#text(size: 0.85em, weight: "bold")[#filename]] - } - #block(inset: 8pt, width: 100%, body) - ] + body } #let quarto-annotation-item(n, content) = { @@ -64,8 +77,11 @@ ] } -// Route all native raw code blocks through the unified wrapper -#show raw.where(block: true): it => quarto-code-block(it) +// Style native raw code blocks with default inset, radius, and stroke +#show raw.where(block: true): it => block( + fill: luma(230), width: 100%, inset: 8pt, radius: 2pt, + stroke: 0.5pt + luma(200), it, +) #let block_with_new_content(old_block, new_content) = { let fields = old_block.fields() diff --git a/tests/docs/smoke-all/typst/code-annotations/code-annotations-native.qmd b/tests/docs/smoke-all/typst/code-annotations/code-annotations-native.qmd index bc2a59e1749..ab87ca92c11 100644 --- a/tests/docs/smoke-all/typst/code-annotations/code-annotations-native.qmd +++ b/tests/docs/smoke-all/typst/code-annotations/code-annotations-native.qmd @@ -10,7 +10,7 @@ _quarto: typst: ensureTypstFileRegexMatches: - - - "quarto-code-block" + - "quarto-code-annotation" - "quarto-annotation-item" - [] --- diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename.qmd index bef4754ca40..1941ca9b5de 100644 --- a/tests/docs/smoke-all/typst/code-filename/code-filename.qmd +++ b/tests/docs/smoke-all/typst/code-filename/code-filename.qmd @@ -8,7 +8,7 @@ _quarto: typst: ensureTypstFileRegexMatches: - - - 'quarto-code-block\(filename:' + - 'quarto-code-filename\(' - [] --- From 9e800ff1c414b825606afcfdabf6f7ab9353282c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:29:59 +0100 Subject: [PATCH 08/19] test: expand Typst code annotation and filename test coverage --- .../code-annotations-cell-jupyter.qmd | 25 ++++++++++++++++++ .../code-annotations-cell-knitr.qmd | 25 ++++++++++++++++++ .../code-annotations-none.qmd | 4 +-- .../code-filename-annotation-cell-jupyter.qmd | 26 +++++++++++++++++++ .../code-filename-annotation-cell-knitr.qmd | 26 +++++++++++++++++++ .../code-filename-annotation-native.qmd | 26 +++++++++++++++++++ .../code-filename-annotation.qmd | 25 ++++++++++++++++++ .../code-filename-cell-jupyter.qmd | 18 +++++++++++++ .../code-filename-cell-knitr.qmd | 18 +++++++++++++ .../code-filename/code-filename-native.qmd | 19 ++++++++++++++ .../typst/code-filename/code-filename.qmd | 2 +- 11 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-jupyter.qmd create mode 100644 tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-knitr.qmd create mode 100644 tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-jupyter.qmd create mode 100644 tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-knitr.qmd create mode 100644 tests/docs/smoke-all/typst/code-filename/code-filename-annotation-native.qmd create mode 100644 tests/docs/smoke-all/typst/code-filename/code-filename-annotation.qmd create mode 100644 tests/docs/smoke-all/typst/code-filename/code-filename-cell-jupyter.qmd create mode 100644 tests/docs/smoke-all/typst/code-filename/code-filename-cell-knitr.qmd create mode 100644 tests/docs/smoke-all/typst/code-filename/code-filename-native.qmd diff --git a/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-jupyter.qmd b/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-jupyter.qmd new file mode 100644 index 00000000000..9fa5d039935 --- /dev/null +++ b/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-jupyter.qmd @@ -0,0 +1,25 @@ +--- +title: Code Annotations (Cell, Jupyter) +format: + typst: + keep-typ: true + code-annotations: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - "quarto-circled-number" + - "quarto-annotation-item" + - "annotations:" + - [] +--- + +```{python} +x = 1 # <1> +y = 2 +z = x + y # <2> +``` + +1. Assign x. +2. Compute sum. diff --git a/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-knitr.qmd b/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-knitr.qmd new file mode 100644 index 00000000000..f7b6fc231f9 --- /dev/null +++ b/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-knitr.qmd @@ -0,0 +1,25 @@ +--- +title: Code Annotations (Cell, Knitr) +format: + typst: + keep-typ: true + code-annotations: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - "quarto-circled-number" + - "quarto-annotation-item" + - "annotations:" + - [] +--- + +```{r} +x <- 1 # <1> +y <- 2 +z <- x + y # <2> +``` + +1. Assign x. +2. Compute sum. diff --git a/tests/docs/smoke-all/typst/code-annotations/code-annotations-none.qmd b/tests/docs/smoke-all/typst/code-annotations/code-annotations-none.qmd index eb28056cad8..d5b39ac7358 100644 --- a/tests/docs/smoke-all/typst/code-annotations/code-annotations-none.qmd +++ b/tests/docs/smoke-all/typst/code-annotations/code-annotations-none.qmd @@ -10,8 +10,8 @@ _quarto: ensureTypstFileRegexMatches: - [] - - - "quarto-annotation-item" - - "quarto-circled-number" + - '#quarto-annotation-item\(' + - 'annotations: \("' --- ```python diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-jupyter.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-jupyter.qmd new file mode 100644 index 00000000000..36c58f83581 --- /dev/null +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-jupyter.qmd @@ -0,0 +1,26 @@ +--- +title: Code Filename + Annotation (Cell, Jupyter) +format: + typst: + keep-typ: true + code-annotations: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - 'quarto-code-filename\(' + - "quarto-annotation-item" + - "annotations:" + - [] +--- + +```{python} +#| filename: "hello.py" +x = 1 # <1> +y = 2 +z = x + y # <2> +``` + +1. Assign x. +2. Compute sum. diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-knitr.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-knitr.qmd new file mode 100644 index 00000000000..e8015b3e632 --- /dev/null +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-knitr.qmd @@ -0,0 +1,26 @@ +--- +title: Code Filename + Annotation (Cell, Knitr) +format: + typst: + keep-typ: true + code-annotations: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - 'quarto-code-filename\(' + - "quarto-annotation-item" + - "annotations:" + - [] +--- + +```{r} +#| filename: "hello.R" +x <- 1 # <1> +y <- 2 +z <- x + y # <2> +``` + +1. Assign x. +2. Compute sum. diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-native.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-native.qmd new file mode 100644 index 00000000000..fccf7d767ce --- /dev/null +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-native.qmd @@ -0,0 +1,26 @@ +--- +title: Code Filename + Annotation (Native) +format: + typst: + keep-typ: true + code-annotations: true + syntax-highlighting: idiomatic +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - 'quarto-code-filename\(' + - "quarto-code-annotation" + - "quarto-annotation-item" + - [] +--- + +```{.python filename="example.py"} +x = 1 # <1> +y = 2 +z = x + y # <2> +``` + +1. Assign x. +2. Compute sum. diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation.qmd new file mode 100644 index 00000000000..040f4c5a112 --- /dev/null +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation.qmd @@ -0,0 +1,25 @@ +--- +title: Code Filename + Annotation (Code Block) +format: + typst: + keep-typ: true + code-annotations: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - 'quarto-code-filename\(' + - "quarto-annotation-item" + - "annotations:" + - [] +--- + +```{.python filename="example.py"} +x = 1 # <1> +y = 2 +z = x + y # <2> +``` + +1. Assign x. +2. Compute sum. diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-cell-jupyter.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-cell-jupyter.qmd new file mode 100644 index 00000000000..fc839eef4f9 --- /dev/null +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-cell-jupyter.qmd @@ -0,0 +1,18 @@ +--- +title: Code Filename (Cell, Jupyter) +format: + typst: + keep-typ: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - 'quarto-code-filename\(' + - [] +--- + +```{python} +#| filename: "hello.py" +print("Hello, world!") +``` diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-cell-knitr.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-cell-knitr.qmd new file mode 100644 index 00000000000..867209f7512 --- /dev/null +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-cell-knitr.qmd @@ -0,0 +1,18 @@ +--- +title: Code Filename (Cell, Knitr) +format: + typst: + keep-typ: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - 'quarto-code-filename\(' + - [] +--- + +```{r} +#| filename: "hello.R" +print("Hello, world!") +``` diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-native.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-native.qmd new file mode 100644 index 00000000000..5f6fa3caf8e --- /dev/null +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-native.qmd @@ -0,0 +1,19 @@ +--- +title: Code Filename (Native) +format: + typst: + keep-typ: true + syntax-highlighting: idiomatic +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - 'quarto-code-filename\(' + - [] +--- + +```{.python filename="example.py"} +def hello(): + print("Hello, world!") +``` diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename.qmd index 1941ca9b5de..db7f158771b 100644 --- a/tests/docs/smoke-all/typst/code-filename/code-filename.qmd +++ b/tests/docs/smoke-all/typst/code-filename/code-filename.qmd @@ -1,5 +1,5 @@ --- -title: Code Filename Bar +title: Code Filename (Code Block) format: typst: keep-typ: true From 8241543b8e6bd7be987c7c2cde7a9358131be107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Fri, 6 Mar 2026 21:03:55 +0100 Subject: [PATCH 09/19] fix: harden Typst code annotation and filename escaping --- src/resources/filters/customnodes/decoratedcodeblock.lua | 3 ++- src/resources/filters/quarto-pre/code-annotation.lua | 8 +------- .../formats/typst/pandoc/quarto/definitions.typ | 9 ++++++--- .../typst/syntax-highlighting/skylighting-default.qmd | 2 +- .../syntax-highlighting/skylighting-line-numbers.qmd | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/resources/filters/customnodes/decoratedcodeblock.lua b/src/resources/filters/customnodes/decoratedcodeblock.lua index f355f378342..a0c7c1dd490 100644 --- a/src/resources/filters/customnodes/decoratedcodeblock.lua +++ b/src/resources/filters/customnodes/decoratedcodeblock.lua @@ -197,8 +197,9 @@ _quarto.ast.add_renderer("DecoratedCodeBlock", CodeBlock = render_folded_block }) or pandoc.Blocks({}) local blocks = pandoc.Blocks({}) + local escaped = node.filename:gsub('\\', '\\\\'):gsub('"', '\\"') blocks:insert(pandoc.RawBlock("typst", - '#quarto-code-filename("' .. node.filename .. '")[')) + '#quarto-code-filename("' .. escaped .. '")[')) blocks:extend(rendered) blocks:insert(pandoc.RawBlock("typst", "]")) return pandoc.Div(blocks) diff --git a/src/resources/filters/quarto-pre/code-annotation.lua b/src/resources/filters/quarto-pre/code-annotation.lua index 19a41adecce..20a5aa809a2 100644 --- a/src/resources/filters/quarto-pre/code-annotation.lua +++ b/src/resources/filters/quarto-pre/code-annotation.lua @@ -321,10 +321,6 @@ function processAnnotation(line, annoteNumber, annotationProvider) return stripped end -function processTypstAnnotation(line, annoteNumber, annotationProvider) - local stripped = annotationProvider.stripAnnotation(line, annoteNumber) - return stripped -end function code_meta() return { @@ -419,8 +415,6 @@ function code_annotations() annotationProcessor = processLaTeXAnnotation elseif _quarto.format.isAsciiDocOutput() then annotationProcessor = processAsciidocAnnotation - elseif _quarto.format.isTypstOutput() then - annotationProcessor = processTypstAnnotation end -- resolve annotations @@ -564,7 +558,7 @@ function code_annotations() local annotationNumber = block.start + i - 1 local annoteId = toAnnoteId(annotationNumber) if pendingAnnotations[annoteId] then - local content = pandoc.utils.stringify(v[1]) + local content = pandoc.write(pandoc.Pandoc({v[1]}), "typst") annotationBlocks:insert(pandoc.RawBlock("typst", "#quarto-annotation-item(" .. tostring(annotationNumber) .. ", [" .. content .. "])")) end diff --git a/src/resources/formats/typst/pandoc/quarto/definitions.typ b/src/resources/formats/typst/pandoc/quarto/definitions.typ index cefb9453a8d..cf3287037e7 100644 --- a/src/resources/formats/typst/pandoc/quarto/definitions.typ +++ b/src/resources/formats/typst/pandoc/quarto/definitions.typ @@ -35,11 +35,14 @@ // Derive a contrasting annotation colour from a background fill. // Light backgrounds get dark circles; dark backgrounds get light circles. -// Uses relative luminance: 0.2126R + 0.7152G + 0.0722B. #let quarto-annote-color(bg) = { if type(bg) == color { - let (r, g, b, ..) = bg.components(alpha: false) - let lum = 0.2126 * r / 100% + 0.7152 * g / 100% + 0.0722 * b / 100% + let comps = bg.components(alpha: false) + let lum = if comps.len() == 1 { + comps.at(0) / 100% + } else { + 0.2126 * comps.at(0) / 100% + 0.7152 * comps.at(1) / 100% + 0.0722 * comps.at(2) / 100% + } if lum < 0.5 { luma(200) } else { luma(60) } } else { luma(60) diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-default.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-default.qmd index de81bcf6460..9dd63241bbe 100644 --- a/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-default.qmd +++ b/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-default.qmd @@ -14,7 +14,7 @@ _quarto: - "#let EndLine" # Quarto override with proper block styling and arrow theme bgcolor - 'let bgcolor = rgb\("#f1f3f5"\)' - - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)' + - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, stroke: 0\.5pt \+ luma\(200\), blocks\)' - ["```python"] --- diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-line-numbers.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-line-numbers.qmd index af2fe7e9f32..b7e43d82eaa 100644 --- a/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-line-numbers.qmd +++ b/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-line-numbers.qmd @@ -14,7 +14,7 @@ _quarto: - '#Skylighting\(number: true' - "#KeywordTok" # Quarto override with proper block styling - - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)' + - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, stroke: 0\.5pt \+ luma\(200\), blocks\)' - ["```python"] --- From 9416ad16f6109ce8eaff3554cf25a0ae39d38d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Fri, 6 Mar 2026 21:58:39 +0100 Subject: [PATCH 10/19] test: tweak test files --- .../code-filename-annotation-cell-jupyter.qmd | 9 +++------ .../code-filename-annotation-cell-knitr.qmd | 9 +++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-jupyter.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-jupyter.qmd index 36c58f83581..c8ae00ad739 100644 --- a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-jupyter.qmd +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-jupyter.qmd @@ -9,7 +9,7 @@ _quarto: typst: ensureTypstFileRegexMatches: - - - 'quarto-code-filename\(' + - 'quarto-code-filename\("hello\.py"\)\[\s*#Skylighting\(annotations:' - "quarto-annotation-item" - "annotations:" - [] @@ -17,10 +17,7 @@ _quarto: ```{python} #| filename: "hello.py" -x = 1 # <1> -y = 2 -z = x + y # <2> +print("Hello, world!") # <1> ``` -1. Assign x. -2. Compute sum. +1. Print a greeting. diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-knitr.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-knitr.qmd index e8015b3e632..9e5fd673eca 100644 --- a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-knitr.qmd +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-knitr.qmd @@ -9,7 +9,7 @@ _quarto: typst: ensureTypstFileRegexMatches: - - - 'quarto-code-filename\(' + - 'quarto-code-filename\("hello\.R"\)\[\s*#Skylighting\(annotations:' - "quarto-annotation-item" - "annotations:" - [] @@ -17,10 +17,7 @@ _quarto: ```{r} #| filename: "hello.R" -x <- 1 # <1> -y <- 2 -z <- x + y # <2> +print("Hello, world!") # <1> ``` -1. Assign x. -2. Compute sum. +1. Print a greeting. From 78a54f80607282aa2930b7ffe1ceb956d7ca9360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Fri, 6 Mar 2026 21:59:34 +0100 Subject: [PATCH 11/19] fix: merge parent block from code cell with annotation marker regex for 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. --- src/format/typst/format-typst.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/format/typst/format-typst.ts b/src/format/typst/format-typst.ts index 40abe93e041..c4cc5750825 100644 --- a/src/format/typst/format-typst.ts +++ b/src/format/typst/format-typst.ts @@ -207,7 +207,7 @@ function skylightingPostProcessor(brandBgColor?: string) { // Annotation markers emitted by the Lua filter as Typst comments const annotationMarkerRe = - /\/\/ quarto-code-annotations: (\([^)]*\))\n\s*#Skylighting\(/g; + /\/\/ quarto-code-annotations: (\([^)]*\))\n(\s*(?:#block\[\s*)*(?:#quarto-code-filename\([^\n]*\)\[\s*)?)#Skylighting\(/g; return async (output: string) => { let content = Deno.readTextFileSync(output); @@ -260,10 +260,11 @@ function skylightingPostProcessor(brandBgColor?: string) { } } - // Merge annotation markers into Skylighting call sites + // Merge annotation markers into Skylighting call sites, including + // optional #block[ wrappers and #quarto-code-filename(...)[ wrappers. const merged = content.replace( annotationMarkerRe, - "#Skylighting(annotations: $1, ", + "$2#Skylighting(annotations: $1, ", ); if (merged !== content) { content = merged; From c0f14486008b5beb10af21496f5e062caf0a7a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sat, 7 Mar 2026 14:37:08 +0100 Subject: [PATCH 12/19] fix: improve semantic structure by linking code and annotation --- src/format/typst/format-typst.ts | 15 ++++++---- .../filters/quarto-pre/code-annotation.lua | 28 ++++++++++-------- .../typst/pandoc/quarto/definitions.typ | 29 ++++++++++++++----- 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/format/typst/format-typst.ts b/src/format/typst/format-typst.ts index c4cc5750825..0466cf12553 100644 --- a/src/format/typst/format-typst.ts +++ b/src/format/typst/format-typst.ts @@ -207,7 +207,7 @@ function skylightingPostProcessor(brandBgColor?: string) { // Annotation markers emitted by the Lua filter as Typst comments const annotationMarkerRe = - /\/\/ quarto-code-annotations: (\([^)]*\))\n(\s*(?:#block\[\s*)*(?:#quarto-code-filename\([^\n]*\)\[\s*)?)#Skylighting\(/g; + /\/\/ quarto-code-annotations: ([\w-]*) (\([^)]*\))\n(\s*(?:#block\[\s*)*(?:#quarto-code-filename\([^\n]*\)\[\s*)?)#Skylighting\(/g; return async (output: string) => { let content = Deno.readTextFileSync(output); @@ -231,10 +231,10 @@ function skylightingPostProcessor(brandBgColor?: string) { ); } - // Add annotations parameter to function signature + // Add cell-id and annotations parameters to function signature fn = fn.replace( "start: 1, sourcelines)", - "start: 1, annotations: (:), sourcelines)", + "start: 1, cell-id: \"\", annotations: (:), sourcelines)", ); // Move lnum increment outside if-number block (always track position) @@ -248,7 +248,12 @@ function skylightingPostProcessor(brandBgColor?: string) { "blocks = blocks + ln + EndLine()", `let annote-num = annotations.at(str(lnum), default: none) if annote-num != none { - blocks = blocks + box(width: 100%)[#ln #h(1fr) #quarto-circled-number(annote-num, color: quarto-annote-color(bgcolor))] + EndLine() + if cell-id != "" { + let lbl = cell-id + "-annote-" + str(annote-num) + blocks = blocks + box(width: 100%)[#ln #h(1fr) #link(label(lbl))[#quarto-circled-number(annote-num, color: quarto-annote-color(bgcolor))] #label(lbl + "-back")] + EndLine() + } else { + blocks = blocks + box(width: 100%)[#ln #h(1fr) #quarto-circled-number(annote-num, color: quarto-annote-color(bgcolor))] + EndLine() + } } else { blocks = blocks + ln + EndLine() }`, @@ -264,7 +269,7 @@ function skylightingPostProcessor(brandBgColor?: string) { // optional #block[ wrappers and #quarto-code-filename(...)[ wrappers. const merged = content.replace( annotationMarkerRe, - "$2#Skylighting(annotations: $1, ", + "$3#Skylighting(cell-id: \"$1\", annotations: $2, ", ); if (merged !== content) { content = merged; diff --git a/src/resources/filters/quarto-pre/code-annotation.lua b/src/resources/filters/quarto-pre/code-annotation.lua index 20a5aa809a2..b2ddf109d29 100644 --- a/src/resources/filters/quarto-pre/code-annotation.lua +++ b/src/resources/filters/quarto-pre/code-annotation.lua @@ -108,14 +108,14 @@ end -- Skylighting mode: emit a Typst comment that the TS post-processor -- will merge into the Skylighting call site. -local function typstAnnotationMarker(annotations) +local function typstAnnotationMarker(annotations, cellId) local dict = typstAnnotationsDict(annotations) - return pandoc.RawBlock("typst", "// quarto-code-annotations: " .. dict) + return pandoc.RawBlock("typst", "// quarto-code-annotations: " .. (cellId or "") .. " " .. dict) end -- Native/none mode: wrap a CodeBlock in #quarto-code-annotation(annotations)[...]. -- raw.line numbers always start at 1 regardless of startFrom, so adjust keys. -local function wrapTypstAnnotatedCode(codeBlock, annotations) +local function wrapTypstAnnotatedCode(codeBlock, annotations, cellId) local startFrom = tonumber(codeBlock.attr.attributes['startFrom']) or 1 local adjustedAnnotations = {} for annoteId, lineNumbers in pairs(annotations) do @@ -126,6 +126,10 @@ local function wrapTypstAnnotatedCode(codeBlock, annotations) adjustedAnnotations[annoteId] = adjusted end local dict = typstAnnotationsDict(adjustedAnnotations) + local cellIdParam = "" + if cellId and cellId ~= "" then + cellIdParam = ", cell-id: \"" .. cellId .. "\"" + end local lang = codeBlock.attr.classes[1] or "" local code = codeBlock.text local maxBackticks = 2 @@ -133,7 +137,7 @@ local function wrapTypstAnnotatedCode(codeBlock, annotations) maxBackticks = math.max(maxBackticks, #seq) end local fence = string.rep("`", maxBackticks + 1) - local raw = "#quarto-code-annotation(" .. dict .. ")[" .. fence .. lang .. "\n" .. code .. "\n" .. fence .. "]" + local raw = "#quarto-code-annotation(" .. dict .. cellIdParam .. ")[" .. fence .. lang .. "\n" .. code .. "\n" .. fence .. "]" return pandoc.RawBlock("typst", raw) end @@ -497,9 +501,9 @@ function code_annotations() and pendingAnnotations and next(pendingAnnotations) ~= nil then if param(constants.kSyntaxHighlighting, true) then block.content[1].content[1] = codeCell - block.content[1].content:insert(1, typstAnnotationMarker(pendingAnnotations)) + block.content[1].content:insert(1, typstAnnotationMarker(pendingAnnotations, pendingCellId)) else - block.content[1].content[1] = wrapTypstAnnotatedCode(codeCell, pendingAnnotations) + block.content[1].content[1] = wrapTypstAnnotatedCode(codeCell, pendingAnnotations, pendingCellId) end else block.content[1].content[1] = codeCell @@ -535,10 +539,10 @@ function code_annotations() and codeAnnotations ~= constants.kCodeAnnotationStyleNone and pendingAnnotations and next(pendingAnnotations) ~= nil then if param(constants.kSyntaxHighlighting, true) then - outputBlock(typstAnnotationMarker(pendingAnnotations)) + outputBlock(typstAnnotationMarker(pendingAnnotations, pendingCellId)) outputBlock(codeCell) else - outputBlock(wrapTypstAnnotatedCode(codeCell, pendingAnnotations)) + outputBlock(wrapTypstAnnotatedCode(codeCell, pendingAnnotations, pendingCellId)) end else outputBlock(codeCell) @@ -560,7 +564,7 @@ function code_annotations() if pendingAnnotations[annoteId] then local content = pandoc.write(pandoc.Pandoc({v[1]}), "typst") annotationBlocks:insert(pandoc.RawBlock("typst", - "#quarto-annotation-item(" .. tostring(annotationNumber) .. ", [" .. content .. "])")) + "#quarto-annotation-item(\"" .. (pendingCellId or "") .. "\", " .. tostring(annotationNumber) .. ", [" .. content .. "])")) end end @@ -574,7 +578,7 @@ function code_annotations() if useSkylighting then return nil else - return wrapTypstAnnotatedCode(el, pendingAnnotations) + return wrapTypstAnnotatedCode(el, pendingAnnotations, pendingCellId) end end end @@ -584,12 +588,12 @@ function code_annotations() if is_custom_node(resolvedCell) then local custom = _quarto.ast.resolve_custom_data(resolvedCell) or pandoc.Div({}) if useSkylighting then - custom.content:insert(1, typstAnnotationMarker(pendingAnnotations)) + custom.content:insert(1, typstAnnotationMarker(pendingAnnotations, pendingCellId)) end custom.content:insert(dlDiv) else if useSkylighting then - resolvedCell.content:insert(1, typstAnnotationMarker(pendingAnnotations)) + resolvedCell.content:insert(1, typstAnnotationMarker(pendingAnnotations, pendingCellId)) end resolvedCell.content:insert(dlDiv) end diff --git a/src/resources/formats/typst/pandoc/quarto/definitions.typ b/src/resources/formats/typst/pandoc/quarto/definitions.typ index cf3287037e7..ee210aa65fd 100644 --- a/src/resources/formats/typst/pandoc/quarto/definitions.typ +++ b/src/resources/formats/typst/pandoc/quarto/definitions.typ @@ -59,12 +59,17 @@ ] } -#let quarto-code-annotation(annotations, color: luma(60), body) = { +#let quarto-code-annotation(annotations, cell-id: "", color: luma(60), body) = { show raw.where(block: true): it => it show raw.line: it => { let annote-num = annotations.at(str(it.number), default: none) if annote-num != none { - box(width: 100%)[#it #h(1fr) #quarto-circled-number(annote-num, color: color)] + if cell-id != "" { + let lbl = cell-id + "-annote-" + str(annote-num) + box(width: 100%)[#it #h(1fr) #link(label(lbl))[#quarto-circled-number(annote-num, color: color)] #label(lbl + "-back")] + } else { + box(width: 100%)[#it #h(1fr) #quarto-circled-number(annote-num, color: color)] + } } else { it } @@ -72,12 +77,20 @@ body } -#let quarto-annotation-item(n, content) = { - block(above: 0.4em, below: 0.4em)[ - #quarto-circled-number(n) - #h(0.4em) - #content - ] +#let quarto-annotation-item(cell-id, n, content) = { + if cell-id != "" { + [#block(above: 0.4em, below: 0.4em)[ + #link(label(cell-id + "-annote-" + str(n) + "-back"))[#quarto-circled-number(n)] + #h(0.4em) + #content + ] #label(cell-id + "-annote-" + str(n))] + } else { + block(above: 0.4em, below: 0.4em)[ + #quarto-circled-number(n) + #h(0.4em) + #content + ] + } } // Style native raw code blocks with default inset, radius, and stroke From ac932e9a49df8bc8027f08e0f1a71d2eaa3e2bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sat, 7 Mar 2026 14:39:23 +0100 Subject: [PATCH 13/19] test: update tests with semantic links --- .../typst/code-annotations/code-annotations-cell-jupyter.qmd | 1 + .../typst/code-annotations/code-annotations-cell-knitr.qmd | 1 + .../typst/code-annotations/code-annotations-native.qmd | 1 + .../docs/smoke-all/typst/code-annotations/code-annotations.qmd | 1 + .../code-filename/code-filename-annotation-cell-jupyter.qmd | 3 ++- .../code-filename/code-filename-annotation-cell-knitr.qmd | 3 ++- .../typst/code-filename/code-filename-annotation-native.qmd | 1 + .../smoke-all/typst/code-filename/code-filename-annotation.qmd | 1 + 8 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-jupyter.qmd b/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-jupyter.qmd index 9fa5d039935..609c41dfc9a 100644 --- a/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-jupyter.qmd +++ b/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-jupyter.qmd @@ -11,6 +11,7 @@ _quarto: - - "quarto-circled-number" - "quarto-annotation-item" + - "cell-id:" - "annotations:" - [] --- diff --git a/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-knitr.qmd b/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-knitr.qmd index f7b6fc231f9..f2883a45b60 100644 --- a/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-knitr.qmd +++ b/tests/docs/smoke-all/typst/code-annotations/code-annotations-cell-knitr.qmd @@ -11,6 +11,7 @@ _quarto: - - "quarto-circled-number" - "quarto-annotation-item" + - "cell-id:" - "annotations:" - [] --- diff --git a/tests/docs/smoke-all/typst/code-annotations/code-annotations-native.qmd b/tests/docs/smoke-all/typst/code-annotations/code-annotations-native.qmd index ab87ca92c11..51e0205ae0f 100644 --- a/tests/docs/smoke-all/typst/code-annotations/code-annotations-native.qmd +++ b/tests/docs/smoke-all/typst/code-annotations/code-annotations-native.qmd @@ -11,6 +11,7 @@ _quarto: ensureTypstFileRegexMatches: - - "quarto-code-annotation" + - "cell-id:" - "quarto-annotation-item" - [] --- diff --git a/tests/docs/smoke-all/typst/code-annotations/code-annotations.qmd b/tests/docs/smoke-all/typst/code-annotations/code-annotations.qmd index d8ff469d811..a9c6f31227f 100644 --- a/tests/docs/smoke-all/typst/code-annotations/code-annotations.qmd +++ b/tests/docs/smoke-all/typst/code-annotations/code-annotations.qmd @@ -11,6 +11,7 @@ _quarto: - - "quarto-circled-number" - "quarto-annotation-item" + - "cell-id:" - "annotations:" - [] --- diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-jupyter.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-jupyter.qmd index c8ae00ad739..25add896637 100644 --- a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-jupyter.qmd +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-jupyter.qmd @@ -9,8 +9,9 @@ _quarto: typst: ensureTypstFileRegexMatches: - - - 'quarto-code-filename\("hello\.py"\)\[\s*#Skylighting\(annotations:' + - 'quarto-code-filename\("hello\.py"\)\[\s*#Skylighting\(cell-id:' - "quarto-annotation-item" + - "cell-id:" - "annotations:" - [] --- diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-knitr.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-knitr.qmd index 9e5fd673eca..76372384a9d 100644 --- a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-knitr.qmd +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-cell-knitr.qmd @@ -9,8 +9,9 @@ _quarto: typst: ensureTypstFileRegexMatches: - - - 'quarto-code-filename\("hello\.R"\)\[\s*#Skylighting\(annotations:' + - 'quarto-code-filename\("hello\.R"\)\[\s*#Skylighting\(cell-id:' - "quarto-annotation-item" + - "cell-id:" - "annotations:" - [] --- diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-native.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-native.qmd index fccf7d767ce..2789e01631d 100644 --- a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-native.qmd +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation-native.qmd @@ -12,6 +12,7 @@ _quarto: - - 'quarto-code-filename\(' - "quarto-code-annotation" + - "cell-id:" - "quarto-annotation-item" - [] --- diff --git a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation.qmd b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation.qmd index 040f4c5a112..2182af5069d 100644 --- a/tests/docs/smoke-all/typst/code-filename/code-filename-annotation.qmd +++ b/tests/docs/smoke-all/typst/code-filename/code-filename-annotation.qmd @@ -11,6 +11,7 @@ _quarto: - - 'quarto-code-filename\(' - "quarto-annotation-item" + - "cell-id:" - "annotations:" - [] --- From 5c8b344c1c57af83160409953a0289e3e7a53d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sat, 7 Mar 2026 20:15:03 +0100 Subject: [PATCH 14/19] fix: ensure back-labels are emitted only once --- src/format/typst/format-typst.ts | 17 +++++++++++++++-- .../filters/quarto-pre/code-annotation.lua | 16 ++++++---------- .../formats/typst/pandoc/quarto/definitions.typ | 17 ++++++++++++++++- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/format/typst/format-typst.ts b/src/format/typst/format-typst.ts index 0466cf12553..e4a1abd5798 100644 --- a/src/format/typst/format-typst.ts +++ b/src/format/typst/format-typst.ts @@ -210,7 +210,7 @@ function skylightingPostProcessor(brandBgColor?: string) { /\/\/ quarto-code-annotations: ([\w-]*) (\([^)]*\))\n(\s*(?:#block\[\s*)*(?:#quarto-code-filename\([^\n]*\)\[\s*)?)#Skylighting\(/g; return async (output: string) => { - let content = Deno.readTextFileSync(output); + let content = Deno.readTextFileSync(output).replace(/\r\n/g, "\n"); let changed = false; const match = skylightingFnRe.exec(content); @@ -243,6 +243,14 @@ function skylightingPostProcessor(brandBgColor?: string) { "lnum = lnum + 1\n if number {\n", ); + // Initialise a dictionary to track which annotation numbers have + // already emitted a back-label (avoids duplicate labels when one + // annotation spans multiple lines). + fn = fn.replace( + /let lnum = start - 1\n/, + "let lnum = start - 1\n let seen-annotes = (:)\n", + ); + // Add annotation rendering per line (derive circle colour from bgcolor) fn = fn.replace( "blocks = blocks + ln + EndLine()", @@ -250,7 +258,12 @@ function skylightingPostProcessor(brandBgColor?: string) { if annote-num != none { if cell-id != "" { let lbl = cell-id + "-annote-" + str(annote-num) - blocks = blocks + box(width: 100%)[#ln #h(1fr) #link(label(lbl))[#quarto-circled-number(annote-num, color: quarto-annote-color(bgcolor))] #label(lbl + "-back")] + EndLine() + if str(annote-num) not in seen-annotes { + seen-annotes.insert(str(annote-num), true) + blocks = blocks + box(width: 100%)[#ln #h(1fr) #link(label(lbl))[#quarto-circled-number(annote-num, color: quarto-annote-color(bgcolor))] #label(lbl + "-back")] + EndLine() + } else { + blocks = blocks + box(width: 100%)[#ln #h(1fr) #link(label(lbl))[#quarto-circled-number(annote-num, color: quarto-annote-color(bgcolor))]] + EndLine() + } } else { blocks = blocks + box(width: 100%)[#ln #h(1fr) #quarto-circled-number(annote-num, color: quarto-annote-color(bgcolor))] + EndLine() } diff --git a/src/resources/filters/quarto-pre/code-annotation.lua b/src/resources/filters/quarto-pre/code-annotation.lua index b2ddf109d29..ddae0f6ad7c 100644 --- a/src/resources/filters/quarto-pre/code-annotation.lua +++ b/src/resources/filters/quarto-pre/code-annotation.lua @@ -126,10 +126,6 @@ local function wrapTypstAnnotatedCode(codeBlock, annotations, cellId) adjustedAnnotations[annoteId] = adjusted end local dict = typstAnnotationsDict(adjustedAnnotations) - local cellIdParam = "" - if cellId and cellId ~= "" then - cellIdParam = ", cell-id: \"" .. cellId .. "\"" - end local lang = codeBlock.attr.classes[1] or "" local code = codeBlock.text local maxBackticks = 2 @@ -137,7 +133,9 @@ local function wrapTypstAnnotatedCode(codeBlock, annotations, cellId) maxBackticks = math.max(maxBackticks, #seq) end local fence = string.rep("`", maxBackticks + 1) - local raw = "#quarto-code-annotation(" .. dict .. cellIdParam .. ")[" .. fence .. lang .. "\n" .. code .. "\n" .. fence .. "]" + local raw = "#quarto-code-annotation(" .. dict + .. (cellId and cellId ~= "" and (", cell-id: \"" .. cellId .. "\"") or "") + .. ")[" .. fence .. lang .. "\n" .. code .. "\n" .. fence .. "]" return pandoc.RawBlock("typst", raw) end @@ -568,14 +566,12 @@ function code_annotations() end end - local useSkylighting = param(constants.kSyntaxHighlighting, true) - if pendingCodeCell ~= nil then local resolvedCell = _quarto.ast.walk(pendingCodeCell, { CodeBlock = function(el) if el.attr.classes:find('cell-code') or el.attr.classes:find(constants.kDataCodeAnnonationClz) then - if useSkylighting then + if param(constants.kSyntaxHighlighting, true) then return nil else return wrapTypstAnnotatedCode(el, pendingAnnotations, pendingCellId) @@ -587,12 +583,12 @@ function code_annotations() local dlDiv = pandoc.Div(annotationBlocks, pandoc.Attr("", {constants.kCellAnnotationClass})) if is_custom_node(resolvedCell) then local custom = _quarto.ast.resolve_custom_data(resolvedCell) or pandoc.Div({}) - if useSkylighting then + if param(constants.kSyntaxHighlighting, true) then custom.content:insert(1, typstAnnotationMarker(pendingAnnotations, pendingCellId)) end custom.content:insert(dlDiv) else - if useSkylighting then + if param(constants.kSyntaxHighlighting, true) then resolvedCell.content:insert(1, typstAnnotationMarker(pendingAnnotations, pendingCellId)) end resolvedCell.content:insert(dlDiv) diff --git a/src/resources/formats/typst/pandoc/quarto/definitions.typ b/src/resources/formats/typst/pandoc/quarto/definitions.typ index ee210aa65fd..04c68516106 100644 --- a/src/resources/formats/typst/pandoc/quarto/definitions.typ +++ b/src/resources/formats/typst/pandoc/quarto/definitions.typ @@ -60,13 +60,28 @@ } #let quarto-code-annotation(annotations, cell-id: "", color: luma(60), body) = { + // Build a set of first-line positions per annotation number so that + // back-labels are only emitted once (avoiding duplicate labels when + // one annotation spans multiple lines). + let first-lines = (:) + for (line, num) in annotations { + let key = str(num) + if key not in first-lines or int(line) < int(first-lines.at(key)) { + first-lines.insert(key, line) + } + } show raw.where(block: true): it => it show raw.line: it => { let annote-num = annotations.at(str(it.number), default: none) if annote-num != none { if cell-id != "" { let lbl = cell-id + "-annote-" + str(annote-num) - box(width: 100%)[#it #h(1fr) #link(label(lbl))[#quarto-circled-number(annote-num, color: color)] #label(lbl + "-back")] + let is-first = first-lines.at(str(annote-num), default: none) == str(it.number) + if is-first { + box(width: 100%)[#it #h(1fr) #link(label(lbl))[#quarto-circled-number(annote-num, color: color)] #label(lbl + "-back")] + } else { + box(width: 100%)[#it #h(1fr) #link(label(lbl))[#quarto-circled-number(annote-num, color: color)]] + } } else { box(width: 100%)[#it #h(1fr) #quarto-circled-number(annote-num, color: color)] } From 994a87803fee7c4887464b78d515794236d15339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sat, 7 Mar 2026 20:15:56 +0100 Subject: [PATCH 15/19] test: update block styling to include stroke in monospace tests --- .../brand-monospace-block-no-bg/brand-monospace-block-no-bg.qmd | 2 +- .../brand-monospace-inheritance/brand-monospace-inheritance.qmd | 2 +- .../brand-monospace-with-theme/brand-monospace-with-theme.qmd | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block-no-bg/brand-monospace-block-no-bg.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block-no-bg/brand-monospace-block-no-bg.qmd index 3e6bf00c79b..6c84975a01c 100644 --- a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block-no-bg/brand-monospace-block-no-bg.qmd +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block-no-bg/brand-monospace-block-no-bg.qmd @@ -17,7 +17,7 @@ _quarto: # Even without brand bg, Skylighting override uses theme bgcolor # so that width/inset/radius are applied - 'let bgcolor = rgb\("#f1f3f5"\)' - - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)' + - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, stroke: 0\.5pt \+ luma\(200\), blocks\)' # No brand background-color show rule (not configured) - ['^#show raw\.where\(block: true\): set block\(fill:'] --- diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inheritance/brand-monospace-inheritance.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inheritance/brand-monospace-inheritance.qmd index acdf6d9ade2..4b01d4ceb3b 100644 --- a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inheritance/brand-monospace-inheritance.qmd +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inheritance/brand-monospace-inheritance.qmd @@ -18,7 +18,7 @@ _quarto: - '^#show raw\.where\(block: true\): set par\(leading: 0\.75em\)$' # Quarto Skylighting override with inherited brand bg - 'let bgcolor = rgb\("#edf2f7"\)' - - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)' + - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, stroke: 0\.5pt \+ luma\(200\), blocks\)' # monospace-inline overrides weight to 700, inherits color, gets its own bg - '^#show raw\.where\(block: false\): set text\(weight: 700, size: 0\.85em, fill: rgb\("#2d3748"\), \)$' - '^#show raw\.where\(block: false\): content => highlight\(fill: rgb\("#fefcbf"\), content\)$' diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-with-theme/brand-monospace-with-theme.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-with-theme/brand-monospace-with-theme.qmd index 02489365969..d4c56fcff5d 100644 --- a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-with-theme/brand-monospace-with-theme.qmd +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-with-theme/brand-monospace-with-theme.qmd @@ -18,7 +18,7 @@ _quarto: - '^#show raw\.where\(block: true\): set par\(leading: 0\.65em\)$' # Quarto Skylighting override uses brand bg (not espresso theme bg) - 'let bgcolor = rgb\("#282a36"\)' - - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)' + - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, stroke: 0\.5pt \+ luma\(200\), blocks\)' # Brand monospace-inline properties - '^#show raw\.where\(block: false\): set text\(weight: 500, fill: rgb\("#6c3483"\), \)$' - '^#show raw\.where\(block: false\): content => highlight\(fill: rgb\("#e8e0f0"\), content\)$' From 6efcd12fcf0b89c953a56174e2e31b1e1dabf7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sun, 8 Mar 2026 21:19:07 +0100 Subject: [PATCH 16/19] test: update block styling to include stroke in brand monospace block --- .../brand-monospace-block/brand-monospace-block.qmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block/brand-monospace-block.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block/brand-monospace-block.qmd index 3cfd409d894..92ccdaa373f 100644 --- a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block/brand-monospace-block.qmd +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block/brand-monospace-block.qmd @@ -18,7 +18,7 @@ _quarto: - '^#show raw\.where\(block: true\): set par\(leading: 0\.85em\)$' # Quarto-generated Skylighting override with brand bg and proper block styling - 'let bgcolor = rgb\("#1e1e2e"\)' - - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)' + - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, stroke: 0\.5pt \+ luma\(200\), blocks\)' # Should NOT have raw fenced blocks - ["```python"] --- From da9fb87ecbf76e9e6ff34b4fa0c0eb7106e84215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:55:48 +0100 Subject: [PATCH 17/19] fix: escape newline, carriage return, and tab characters in filename --- src/resources/filters/customnodes/decoratedcodeblock.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/resources/filters/customnodes/decoratedcodeblock.lua b/src/resources/filters/customnodes/decoratedcodeblock.lua index a0c7c1dd490..b410cca78da 100644 --- a/src/resources/filters/customnodes/decoratedcodeblock.lua +++ b/src/resources/filters/customnodes/decoratedcodeblock.lua @@ -197,7 +197,12 @@ _quarto.ast.add_renderer("DecoratedCodeBlock", CodeBlock = render_folded_block }) or pandoc.Blocks({}) local blocks = pandoc.Blocks({}) - local escaped = node.filename:gsub('\\', '\\\\'):gsub('"', '\\"') + local escaped = node.filename + :gsub('\\', '\\\\') + :gsub('"', '\\"') + :gsub('\n', '\\n') + :gsub('\r', '\\r') + :gsub('\t', '\\t') blocks:insert(pandoc.RawBlock("typst", '#quarto-code-filename("' .. escaped .. '")[')) blocks:extend(rendered) From 422e053b03014045a951569068b3dc1b7ccbd9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:27:32 +0100 Subject: [PATCH 18/19] fix: improve filename escaping in DecoratedCodeBlock renderer --- .../filters/customnodes/decoratedcodeblock.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/resources/filters/customnodes/decoratedcodeblock.lua b/src/resources/filters/customnodes/decoratedcodeblock.lua index b410cca78da..936cb437cfb 100644 --- a/src/resources/filters/customnodes/decoratedcodeblock.lua +++ b/src/resources/filters/customnodes/decoratedcodeblock.lua @@ -197,12 +197,13 @@ _quarto.ast.add_renderer("DecoratedCodeBlock", CodeBlock = render_folded_block }) or pandoc.Blocks({}) local blocks = pandoc.Blocks({}) - local escaped = node.filename - :gsub('\\', '\\\\') - :gsub('"', '\\"') - :gsub('\n', '\\n') - :gsub('\r', '\\r') - :gsub('\t', '\\t') + local escaped = node.filename:gsub('[\\"\n\r\t]', { + ['\\'] = '\\\\', + ['"'] = '\\"', + ['\n'] = '\\n', + ['\r'] = '\\r', + ['\t'] = '\\t', + }) blocks:insert(pandoc.RawBlock("typst", '#quarto-code-filename("' .. escaped .. '")[')) blocks:extend(rendered) From ae392ad230bc018ef287b2c77e1d73210f6eb77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Tue, 24 Mar 2026 14:32:04 +0100 Subject: [PATCH 19/19] refactor: extract Typst annotation helpers into require() module 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. --- .../modules/typst-code-annotations.lua | 69 ++++++++++++++++++ .../filters/quarto-pre/code-annotation.lua | 70 +++---------------- 2 files changed, 77 insertions(+), 62 deletions(-) create mode 100644 src/resources/filters/modules/typst-code-annotations.lua diff --git a/src/resources/filters/modules/typst-code-annotations.lua b/src/resources/filters/modules/typst-code-annotations.lua new file mode 100644 index 00000000000..5a143d26b99 --- /dev/null +++ b/src/resources/filters/modules/typst-code-annotations.lua @@ -0,0 +1,69 @@ +-- typst-code-annotations.lua +-- Copyright (C) 2026 Posit Software, PBC + +-- Typst annotation helpers for code blocks. +-- Loaded via require() so locals stay out of the bundled main.lua scope. + +local function _main() + + -- Convert annotations table to a flat Typst dictionary string. + -- Keys are line positions (as strings), values are annotation numbers. + local function typstAnnotationsDict(annotations) + local entries = {} + for annoteId, lineNumbers in pairs(annotations) do + local num = annoteId:match("annote%-(%d+)") + if num then + for _, lineNo in ipairs(lineNumbers) do + table.insert(entries, {pos = lineNo, annoteNum = tonumber(num)}) + end + end + end + table.sort(entries, function(a, b) return a.pos < b.pos end) + local parts = {} + for _, e in ipairs(entries) do + table.insert(parts, '"' .. tostring(e.pos) .. '": ' .. tostring(e.annoteNum)) + end + return '(' .. table.concat(parts, ', ') .. ')' + end + + -- Skylighting mode: emit a Typst comment that the TS post-processor + -- will merge into the Skylighting call site. + local function typstAnnotationMarker(annotations, cellId) + local dict = typstAnnotationsDict(annotations) + return pandoc.RawBlock("typst", "// quarto-code-annotations: " .. (cellId or "") .. " " .. dict) + end + + -- Native/none mode: wrap a CodeBlock in #quarto-code-annotation(annotations)[...]. + -- raw.line numbers always start at 1 regardless of startFrom, so adjust keys. + local function wrapTypstAnnotatedCode(codeBlock, annotations, cellId) + local startFrom = tonumber(codeBlock.attr.attributes['startFrom']) or 1 + local adjustedAnnotations = {} + for annoteId, lineNumbers in pairs(annotations) do + local adjusted = pandoc.List({}) + for _, lineNo in ipairs(lineNumbers) do + adjusted:insert(lineNo - startFrom + 1) + end + adjustedAnnotations[annoteId] = adjusted + end + local dict = typstAnnotationsDict(adjustedAnnotations) + local lang = codeBlock.attr.classes[1] or "" + local code = codeBlock.text + local maxBackticks = 2 + for seq in code:gmatch("`+") do + maxBackticks = math.max(maxBackticks, #seq) + end + local fence = string.rep("`", maxBackticks + 1) + local raw = "#quarto-code-annotation(" .. dict + .. (cellId and cellId ~= "" and (", cell-id: \"" .. cellId .. "\"") or "") + .. ")[" .. fence .. lang .. "\n" .. code .. "\n" .. fence .. "]" + return pandoc.RawBlock("typst", raw) + end + + return { + typstAnnotationsDict = typstAnnotationsDict, + typstAnnotationMarker = typstAnnotationMarker, + wrapTypstAnnotatedCode = wrapTypstAnnotatedCode, + } +end + +return _main() diff --git a/src/resources/filters/quarto-pre/code-annotation.lua b/src/resources/filters/quarto-pre/code-annotation.lua index ddae0f6ad7c..3ffadbef4ef 100644 --- a/src/resources/filters/quarto-pre/code-annotation.lua +++ b/src/resources/filters/quarto-pre/code-annotation.lua @@ -2,6 +2,7 @@ -- Copyright (C) 2020-2022 Posit Software, PBC local constants = require("modules/constants") +local typstAnnotations = require("modules/typst-code-annotations") local hasAnnotations = false @@ -84,61 +85,6 @@ local function latexListPlaceholder(number) return '5CB6E08D-list-annote-' .. number end --- Typst annotation helpers - --- Convert annotations table to a flat Typst dictionary string. --- Keys are line positions (as strings), values are annotation numbers. -local function typstAnnotationsDict(annotations) - local entries = {} - for annoteId, lineNumbers in pairs(annotations) do - local num = annoteId:match("annote%-(%d+)") - if num then - for _, lineNo in ipairs(lineNumbers) do - table.insert(entries, {pos = lineNo, annoteNum = tonumber(num)}) - end - end - end - table.sort(entries, function(a, b) return a.pos < b.pos end) - local parts = {} - for _, e in ipairs(entries) do - table.insert(parts, '"' .. tostring(e.pos) .. '": ' .. tostring(e.annoteNum)) - end - return '(' .. table.concat(parts, ', ') .. ')' -end - --- Skylighting mode: emit a Typst comment that the TS post-processor --- will merge into the Skylighting call site. -local function typstAnnotationMarker(annotations, cellId) - local dict = typstAnnotationsDict(annotations) - return pandoc.RawBlock("typst", "// quarto-code-annotations: " .. (cellId or "") .. " " .. dict) -end - --- Native/none mode: wrap a CodeBlock in #quarto-code-annotation(annotations)[...]. --- raw.line numbers always start at 1 regardless of startFrom, so adjust keys. -local function wrapTypstAnnotatedCode(codeBlock, annotations, cellId) - local startFrom = tonumber(codeBlock.attr.attributes['startFrom']) or 1 - local adjustedAnnotations = {} - for annoteId, lineNumbers in pairs(annotations) do - local adjusted = pandoc.List({}) - for _, lineNo in ipairs(lineNumbers) do - adjusted:insert(lineNo - startFrom + 1) - end - adjustedAnnotations[annoteId] = adjusted - end - local dict = typstAnnotationsDict(adjustedAnnotations) - local lang = codeBlock.attr.classes[1] or "" - local code = codeBlock.text - local maxBackticks = 2 - for seq in code:gmatch("`+") do - maxBackticks = math.max(maxBackticks, #seq) - end - local fence = string.rep("`", maxBackticks + 1) - local raw = "#quarto-code-annotation(" .. dict - .. (cellId and cellId ~= "" and (", cell-id: \"" .. cellId .. "\"") or "") - .. ")[" .. fence .. lang .. "\n" .. code .. "\n" .. fence .. "]" - return pandoc.RawBlock("typst", raw) -end - local function toLines(s) if s:sub(-1)~="\n" then s=s.."\n" end return s:gmatch("(.-)\n") @@ -499,9 +445,9 @@ function code_annotations() and pendingAnnotations and next(pendingAnnotations) ~= nil then if param(constants.kSyntaxHighlighting, true) then block.content[1].content[1] = codeCell - block.content[1].content:insert(1, typstAnnotationMarker(pendingAnnotations, pendingCellId)) + block.content[1].content:insert(1, typstAnnotations.typstAnnotationMarker(pendingAnnotations, pendingCellId)) else - block.content[1].content[1] = wrapTypstAnnotatedCode(codeCell, pendingAnnotations, pendingCellId) + block.content[1].content[1] = typstAnnotations.wrapTypstAnnotatedCode(codeCell, pendingAnnotations, pendingCellId) end else block.content[1].content[1] = codeCell @@ -537,10 +483,10 @@ function code_annotations() and codeAnnotations ~= constants.kCodeAnnotationStyleNone and pendingAnnotations and next(pendingAnnotations) ~= nil then if param(constants.kSyntaxHighlighting, true) then - outputBlock(typstAnnotationMarker(pendingAnnotations, pendingCellId)) + outputBlock(typstAnnotations.typstAnnotationMarker(pendingAnnotations, pendingCellId)) outputBlock(codeCell) else - outputBlock(wrapTypstAnnotatedCode(codeCell, pendingAnnotations, pendingCellId)) + outputBlock(typstAnnotations.wrapTypstAnnotatedCode(codeCell, pendingAnnotations, pendingCellId)) end else outputBlock(codeCell) @@ -574,7 +520,7 @@ function code_annotations() if param(constants.kSyntaxHighlighting, true) then return nil else - return wrapTypstAnnotatedCode(el, pendingAnnotations, pendingCellId) + return typstAnnotations.wrapTypstAnnotatedCode(el, pendingAnnotations, pendingCellId) end end end @@ -584,12 +530,12 @@ function code_annotations() if is_custom_node(resolvedCell) then local custom = _quarto.ast.resolve_custom_data(resolvedCell) or pandoc.Div({}) if param(constants.kSyntaxHighlighting, true) then - custom.content:insert(1, typstAnnotationMarker(pendingAnnotations, pendingCellId)) + custom.content:insert(1, typstAnnotations.typstAnnotationMarker(pendingAnnotations, pendingCellId)) end custom.content:insert(dlDiv) else if param(constants.kSyntaxHighlighting, true) then - resolvedCell.content:insert(1, typstAnnotationMarker(pendingAnnotations, pendingCellId)) + resolvedCell.content:insert(1, typstAnnotations.typstAnnotationMarker(pendingAnnotations, pendingCellId)) end resolvedCell.content:insert(dlDiv) end