From e9a34a5bd8e40a899bacd5048a04577694a6eca6 Mon Sep 17 00:00:00 2001 From: claude Date: Tue, 9 Jun 2026 18:33:20 +0000 Subject: [PATCH] fix(typst): exclude ] from MarkupContent so ContentBlock always closes 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 --- .../source-editor/languages/typst/index.ts | 2 +- .../source-editor/lezer-typst/typst.grammar | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/services/web/frontend/js/features/source-editor/languages/typst/index.ts b/services/web/frontend/js/features/source-editor/languages/typst/index.ts index 4d47492557..da826de772 100644 --- a/services/web/frontend/js/features/source-editor/languages/typst/index.ts +++ b/services/web/frontend/js/features/source-editor/languages/typst/index.ts @@ -78,7 +78,7 @@ export const TypstLanguage = LRLanguage.define({ 'Emphasis/"_" Emphasis/EmphBody': t.emphasis, // Bare URLs (https://... / http://...) - URL: t.url, + URL: t.string, // Labels () and references (@name) 'Label/"<" Label/">" Label/LabelName': t.labelName, diff --git a/services/web/frontend/js/features/source-editor/lezer-typst/typst.grammar b/services/web/frontend/js/features/source-editor/lezer-typst/typst.grammar index 558607ff35..cb00cda20b 100644 --- a/services/web/frontend/js/features/source-editor/lezer-typst/typst.grammar +++ b/services/web/frontend/js/features/source-editor/lezer-typst/typst.grammar @@ -28,7 +28,8 @@ item { Ref | Escape | URL | - MarkupContent + MarkupContent | + ClosingSquare } // ── Headings ────────────────────────────────────────────────────────────── @@ -226,8 +227,14 @@ Escape { "\\" EscapeChar } // Regular markup: excludes all special-character starters plus whitespace // (whitespace is handled by @skip). The '/' is excluded so that '//' and - // '/*' are not accidentally consumed as plain text. - MarkupContent { ![\n \t=*_$#/<@`\\]+ } + // '/*' are not accidentally consumed as plain text. ']' is excluded so + // 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. ). LabelName { (@asciiLetter | "_" | @digit) (@asciiLetter | @digit | "_" | "-" | "." | ":")* } @@ -248,7 +255,7 @@ Escape { "\\" EscapeChar } // CodeString > MarkupContent: '"' starts a string literal after a keyword. // ":" > MarkupContent: keywordBody ':' wins over markup colon in code states. // 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 }