fix(typst): exclude ] from MarkupContent so ContentBlock always closes
Build and Deploy Verso / deploy (push) Successful in 13m35s

The core bug: MarkupContent { ![...]+ } did not exclude ']', so inside
#figure(table([A],[B]), caption:[...]) the tokenizer consumed ']' as
MarkupContent, ContentBlocks never closed, and all remaining args like
'caption:' were swallowed as MarkupContent instead of CodeArgKey.

Fix mirrors the LaTeX grammar pattern (its Normal token excludes \] and
\[): add ']' to MarkupContent's exclusion set and provide ClosingSquare
{ "]" } as an item alternative for bare ']' in body text.  The grammar's
existing @precedence { "]" ClosingSquare } ensures "]" wins and closes
the ContentBlock; outside a ContentBlock only ClosingSquare is valid.

Also change URL style tag from t.url (tok-url, unstyled in all themes)
to t.string (tok-string, styled in every theme).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
claude
2026-06-09 18:33:20 +00:00
parent f9fc0d9905
commit e9a34a5bd8
2 changed files with 12 additions and 5 deletions
@@ -78,7 +78,7 @@ export const TypstLanguage = LRLanguage.define({
'Emphasis/"_" Emphasis/EmphBody': t.emphasis, 'Emphasis/"_" Emphasis/EmphBody': t.emphasis,
// Bare URLs (https://... / http://...) // Bare URLs (https://... / http://...)
URL: t.url, URL: t.string,
// Labels (<name>) and references (@name) // Labels (<name>) and references (@name)
'Label/"<" Label/">" Label/LabelName': t.labelName, 'Label/"<" Label/">" Label/LabelName': t.labelName,
@@ -28,7 +28,8 @@ item {
Ref | Ref |
Escape | Escape |
URL | URL |
MarkupContent MarkupContent |
ClosingSquare
} }
// ── Headings ────────────────────────────────────────────────────────────── // ── Headings ──────────────────────────────────────────────────────────────
@@ -226,8 +227,14 @@ Escape { "\\" EscapeChar }
// Regular markup: excludes all special-character starters plus whitespace // Regular markup: excludes all special-character starters plus whitespace
// (whitespace is handled by @skip). The '/' is excluded so that '//' and // (whitespace is handled by @skip). The '/' is excluded so that '//' and
// '/*' are not accidentally consumed as plain text. // '/*' are not accidentally consumed as plain text. ']' is excluded so
MarkupContent { ![\n \t=*_$#/<@`\\]+ } // that ContentBlock { "[" item* "]" } can always close reliably — a bare
// ']' in body text is matched as ClosingSquare instead.
MarkupContent { ![\n \t\]=*_$#/<@`\\]+ }
// Fallback for a bare ']' in markup text (outside any ContentBlock).
// Inside ContentBlock the literal "]" terminal wins via @precedence.
ClosingSquare { "]" }
// Label names: identifiers with optional dots/colons (e.g. <sec:intro>). // Label names: identifiers with optional dots/colons (e.g. <sec:intro>).
LabelName { (@asciiLetter | "_" | @digit) (@asciiLetter | @digit | "_" | "-" | "." | ":")* } LabelName { (@asciiLetter | "_" | @digit) (@asciiLetter | @digit | "_" | "-" | "." | ":")* }
@@ -248,7 +255,7 @@ Escape { "\\" EscapeChar }
// CodeString > MarkupContent: '"' starts a string literal after a keyword. // CodeString > MarkupContent: '"' starts a string literal after a keyword.
// ":" > MarkupContent: keywordBody ':' wins over markup colon in code states. // ":" > MarkupContent: keywordBody ':' wins over markup colon in code states.
// URL > MarkupContent: 'https://' / 'http://' wins over plain markup text. // URL > MarkupContent: 'https://' / 'http://' wins over plain markup text.
@precedence { CodeBool EscapeChar CodeString URL "[" ":" "(" "." "]" "_" spaces MarkupContent } @precedence { CodeBool EscapeChar CodeString URL "[" ":" "(" "." "]" ClosingSquare "_" spaces MarkupContent }
} }
@skip { spaces } @skip { spaces }