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 }