diff --git a/crates/typst-syntax/src/lexer.rs b/crates/typst-syntax/src/lexer.rs index d09c6f842..1314016fa 100644 --- a/crates/typst-syntax/src/lexer.rs +++ b/crates/typst-syntax/src/lexer.rs @@ -69,9 +69,11 @@ impl<'s> Lexer<'s> { self.newline } - /// The number of characters until the most recent newline. - pub fn column(&self) -> usize { - self.s.before().chars().rev().take_while(|&c| !is_newline(c)).count() + /// The number of characters until the most recent newline from an index. + pub fn column(&self, index: usize) -> usize { + let mut s = self.s; // Make a new temporary scanner (cheap). + s.jump(index); + s.before().chars().rev().take_while(|&c| !is_newline(c)).count() } } diff --git a/crates/typst-syntax/src/parser.rs b/crates/typst-syntax/src/parser.rs index b26cc0020..5fc621d6d 100644 --- a/crates/typst-syntax/src/parser.rs +++ b/crates/typst-syntax/src/parser.rs @@ -1545,11 +1545,7 @@ struct Token { /// Information about a newline if present (currently only relevant in Markup). #[derive(Debug, Clone, Copy)] struct Newline { - /// The column of our token in its line. - /// - /// Note that this is actually the column of the first non-whitespace - /// `SyntaxKind` in the line, so `\n /**/- list` has column 2 (not 6) - /// because the block comment is the first non-space kind. + /// The column of the start of our token in its line. column: Option, /// Whether any of our newlines were paragraph breaks. parbreak: bool, @@ -1684,10 +1680,6 @@ impl<'s> Parser<'s> { /// The number of characters until the most recent newline from the current /// token, or 0 if it did not follow a newline. - /// - /// Note that this is actually the column of the first non-whitespace - /// `SyntaxKind` in the line, so `\n /**/- list` has column 2 (not 6) - /// because the block comment is the first non-space kind. fn current_column(&self) -> usize { self.token.newline.and_then(|newline| newline.column).unwrap_or(0) } @@ -1852,29 +1844,30 @@ impl<'s> Parser<'s> { let (mut kind, mut node) = lexer.next(); let mut n_trivia = 0; let mut had_newline = false; - let mut newline = Newline { column: None, parbreak: false }; + let mut parbreak = false; while kind.is_trivia() { - if lexer.newline() { - // Newlines are always trivia. - had_newline = true; - newline.parbreak |= kind == SyntaxKind::Parbreak; - if lexer.mode() == LexMode::Markup { - newline.column = Some(lexer.column()); - } - } + had_newline |= lexer.newline(); // Newlines are always trivia. + parbreak |= kind == SyntaxKind::Parbreak; n_trivia += 1; nodes.push(node); start = lexer.cursor(); (kind, node) = lexer.next(); } - if had_newline && nl_mode.stop_at(newline, kind) { - // Insert a temporary `SyntaxKind::End` to halt the parser. - // The actual kind will be restored from `node` later. - kind = SyntaxKind::End; - } - let newline = had_newline.then_some(newline); + let newline = if had_newline { + let column = (lexer.mode() == LexMode::Markup).then(|| lexer.column(start)); + let newline = Newline { column, parbreak }; + if nl_mode.stop_at(newline, kind) { + // Insert a temporary `SyntaxKind::End` to halt the parser. + // The actual kind will be restored from `node` later. + kind = SyntaxKind::End; + } + Some(newline) + } else { + None + }; + Token { kind, node, n_trivia, newline, start, prev_end } } } diff --git a/tests/suite/model/list.typ b/tests/suite/model/list.typ index 46f4621f5..c3c123de1 100644 --- a/tests/suite/model/list.typ +++ b/tests/suite/model/list.typ @@ -34,6 +34,51 @@ _Shopping list_ - C - D +--- list-indent-trivia-nesting --- +// Test indent nesting behavior with odd trivia (comments and spaces). + +#let indented = [ +- a + /**/- b +/**/ - c + /*spanning + multiple + lines */ - d + - e +/**/ - f +/**/ - g +] +// Current behavior is that list columns are based on the first non-whitespace +// element in their line, so the block comments here determine the column the +// list starts at + +#let item = list.item +#let manual = { + [ ] + item({ + [a] + [ ] + item[b] + [ ]; [ ] + item({ + [c] + [ ]; [ ] + item[d] + }) + [ ] + item({ + [e] + [ ]; [ ] + item[f] + [ ]; [ ] + item[g] + }) + }) + [ ] +} + +#test(indented, manual) + --- list-tabs --- // This works because tabs are used consistently. - A with 1 tab