diff --git a/services/web/frontend/js/features/source-editor/lezer-typst/tokens.mjs b/services/web/frontend/js/features/source-editor/lezer-typst/tokens.mjs index a91dfb01b7..90fb687339 100644 --- a/services/web/frontend/js/features/source-editor/lezer-typst/tokens.mjs +++ b/services/web/frontend/js/features/source-editor/lezer-typst/tokens.mjs @@ -238,26 +238,19 @@ export const lineCommentContentTokenizer = new ExternalTokenizer( ) // ── mathContentTokenizer ──────────────────────────────────────────────── -// Emits MathContent — everything between the $...$ delimiters. -// -// contextual: true — only fires when MathContent is in the valid set, i.e. -// inside InlineMath → "$" . MathContent? "$". This prevents the tokenizer -// from consuming body text in LALR-merged states. -// -// No newline restriction: Typst block math spans multiple lines -// ($ formula \n continuation $), so we must read until the closing '$' -// regardless of newlines. The grammar's InlineMath rule handles both inline -// ($x^2$) and block ($ ... $) forms with the same production. +// Emits MathContent — everything between the $...$ delimiters (no newlines). +// External rather than a @tokens rule for the same reason as LineCommentContent: +// ![$\n]+ overlaps with spaces, '<', '@', and other literals in merged states. export const mathContentTokenizer = new ExternalTokenizer( (input, _stack) => { let hasContent = false - while (input.next !== -1 && input.next !== DOLLAR) { + while (input.next !== -1 && input.next !== DOLLAR && input.next !== NEWLINE) { input.advance() hasContent = true } if (hasContent) input.acceptToken(MathContent) }, - { contextual: true } + { contextual: false } ) // ── codeKeywordTokenizer ─────────────────────────────────────────────────