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 3473f5006f..b22373b547 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 @@ -27,7 +27,6 @@ item { Label | Ref | Escape | - Newline | MarkupContent } @@ -184,10 +183,12 @@ Escape { "\\" EscapeChar } // ── Regular tokens ──────────────────────────────────────────────────────── @tokens { - // Horizontal whitespace only. Newlines are kept as explicit Newline items - // so that HeadingMark (which checks start-of-line via input.peek(-1)) can - // reliably detect newlines in the raw input stream. - spaces { $[ \t]+ } + // All whitespace including newlines. Heading detection still works because + // headingTokenizer uses input.peek(-1) on the raw character stream — it sees + // the '\n' byte regardless of what @skip consumes at the token level. + // Including '\n' here lets multi-line code expressions (e.g. #figure(\n ...\n)) + // parse without error instead of triggering Lezer error recovery. + spaces { $[ \t\n\r]+ } // Boolean / null literals — distinct from keywords for highlighting. CodeBool { "true" | "false" | "none" | "auto" } @@ -213,9 +214,6 @@ Escape { "\\" EscapeChar } // Escape: any single character after backslash. EscapeChar { _ } - // Newline item — kept out of @skip so heading detection works. - Newline { "\n" } - // Resolve ambiguities in merged states: // EscapeChar > spaces: after '\', EscapeChar must win over the skip token. // "(" > "." > "]": callSuffix delimiters must win over MarkupContent after