diff --git a/crates/typst-syntax/src/parser.rs b/crates/typst-syntax/src/parser.rs index e087f9dd3..cb5e2dd85 100644 --- a/crates/typst-syntax/src/parser.rs +++ b/crates/typst-syntax/src/parser.rs @@ -47,14 +47,9 @@ fn markup_exprs(p: &mut Parser, mut at_start: bool, stop_set: SyntaxSet) { debug_assert!(stop_set.contains(SyntaxKind::End)); at_start |= p.had_newline(); let mut nesting: usize = 0; - loop { - match p.current() { - SyntaxKind::LeftBracket => nesting += 1, - SyntaxKind::RightBracket if nesting > 0 => nesting -= 1, - _ if p.at_set(stop_set) => break, - _ => {} - } - markup_expr(p, at_start); + // Keep going if we're at a nested right-bracket regardless of the stop set. + while !p.at_set(stop_set) || (nesting > 0 && p.at(SyntaxKind::RightBracket)) { + markup_expr(p, at_start, &mut nesting); at_start = p.had_newline(); } } @@ -69,15 +64,12 @@ pub(super) fn reparse_markup( ) -> Option> { let mut p = Parser::new(text, range.start, LexMode::Markup); *at_start |= p.had_newline(); - while p.current_start() < range.end { - match p.current() { - SyntaxKind::LeftBracket => *nesting += 1, - SyntaxKind::RightBracket if *nesting > 0 => *nesting -= 1, - SyntaxKind::RightBracket if !top_level => break, - SyntaxKind::End => break, - _ => {} + while !p.end() && p.current_start() < range.end { + // If not top-level and at a new RightBracket, stop the reparse. + if !top_level && *nesting == 0 && p.at(SyntaxKind::RightBracket) { + break; } - markup_expr(&mut p, *at_start); + markup_expr(&mut p, *at_start, nesting); *at_start = p.had_newline(); } (p.balanced && p.current_start() == range.end).then(|| p.finish()) @@ -86,8 +78,21 @@ pub(super) fn reparse_markup( /// Parses a single markup expression. This includes markup elements like text, /// headings, strong/emph, lists/enums, etc. This is also the entry point for /// parsing math equations and embedded code expressions. -fn markup_expr(p: &mut Parser, at_start: bool) { +fn markup_expr(p: &mut Parser, at_start: bool, nesting: &mut usize) { match p.current() { + SyntaxKind::LeftBracket => { + *nesting += 1; + p.convert_and_eat(SyntaxKind::Text); + } + SyntaxKind::RightBracket if *nesting > 0 => { + *nesting -= 1; + p.convert_and_eat(SyntaxKind::Text); + } + SyntaxKind::RightBracket => { + p.unexpected(); + p.hint("try using a backslash escape: \\]"); + } + SyntaxKind::Text | SyntaxKind::Linebreak | SyntaxKind::Escape @@ -108,9 +113,7 @@ fn markup_expr(p: &mut Parser, at_start: bool) { SyntaxKind::RefMarker => reference(p), SyntaxKind::Dollar => equation(p), - SyntaxKind::LeftBracket - | SyntaxKind::RightBracket - | SyntaxKind::HeadingMarker + SyntaxKind::HeadingMarker | SyntaxKind::ListMarker | SyntaxKind::EnumMarker | SyntaxKind::TermMarker @@ -201,7 +204,7 @@ fn equation(p: &mut Parser) { let m = p.marker(); p.enter_modes(LexMode::Math, AtNewline::Continue, |p| { p.assert(SyntaxKind::Dollar); - math(p, syntax_set!(Dollar, RightBracket, End)); + math(p, syntax_set!(Dollar, End)); p.expect_closing_delimiter(m, SyntaxKind::Dollar); }); p.wrap(m, SyntaxKind::Equation); diff --git a/tests/ref/single-right-bracket.png b/tests/ref/single-right-bracket.png deleted file mode 100644 index 9867424dd..000000000 Binary files a/tests/ref/single-right-bracket.png and /dev/null differ diff --git a/tests/suite/scripting/blocks.typ b/tests/suite/scripting/blocks.typ index ba1d9c89c..39cd37b24 100644 --- a/tests/suite/scripting/blocks.typ +++ b/tests/suite/scripting/blocks.typ @@ -136,8 +136,45 @@ #} --- single-right-bracket --- +// Error: 1-2 unexpected closing bracket +// Hint: 1-2 try using a backslash escape: \] ] +--- right-bracket-nesting --- +[ += [ Hi ]] +- how [ + - are ] +// Error: 10-11 unexpected closing bracket +// Hint: 10-11 try using a backslash escape: \] + - error][] +[[]] + +--- right-bracket-hash --- +// Error: 2-3 unexpected closing bracket +#] + +--- right-bracket-in-blocks --- +// Error: 3-4 unclosed delimiter +// Error: 6-7 unexpected closing bracket +// Hint: 6-7 try using a backslash escape: \] +[#{]}] + +// Error: 4-5 unexpected closing bracket +// Hint: 4-5 try using a backslash escape: \] +#[]] + +// Error: 4-5 unclosed delimiter +// Error: 7-8 unexpected closing bracket +// Hint: 7-8 try using a backslash escape: \] +#[#{]}] + +// Error: 2-3 unclosed delimiter +// Error: 3-4 unclosed delimiter +// Error: 4-5 unexpected closing bracket +// Hint: 4-5 try using a backslash escape: \] +#{{]}} + --- content-block-in-markup-scope --- // Content blocks also create a scope. #[#let x = 1]