diff --git a/crates/typst-syntax/src/node.rs b/crates/typst-syntax/src/node.rs index 77c73f585..dd5deab2a 100644 --- a/crates/typst-syntax/src/node.rs +++ b/crates/typst-syntax/src/node.rs @@ -164,6 +164,16 @@ impl SyntaxNode { Repr::Error(node) => Arc::make_mut(node).error.span = span, } } + + /// Whether the two syntax nodes are the same apart from spans. + pub fn spanless_eq(&self, other: &Self) -> bool { + match (&self.0, &other.0) { + (Repr::Leaf(a), Repr::Leaf(b)) => a.spanless_eq(b), + (Repr::Inner(a), Repr::Inner(b)) => a.spanless_eq(b), + (Repr::Error(a), Repr::Error(b)) => a.spanless_eq(b), + _ => false, + } + } } impl SyntaxNode { @@ -326,6 +336,11 @@ impl LeafNode { fn len(&self) -> usize { self.text.len() } + + /// Whether the two leaf nodes are the same apart from spans. + fn spanless_eq(&self, other: &Self) -> bool { + self.kind == other.kind && self.text == other.text + } } impl Debug for LeafNode { @@ -440,6 +455,20 @@ impl InnerNode { Ok(()) } + /// Whether the two inner nodes are the same apart from spans. + fn spanless_eq(&self, other: &Self) -> bool { + self.kind == other.kind + && self.len == other.len + && self.descendants == other.descendants + && self.erroneous == other.erroneous + && self.children.len() == other.children.len() + && self + .children + .iter() + .zip(&other.children) + .all(|(a, b)| a.spanless_eq(b)) + } + /// Replaces a range of children with a replacement. /// /// May have mutated the children if it returns `Err(_)`. @@ -449,6 +478,30 @@ impl InnerNode { replacement: Vec, ) -> NumberingResult { let Some(id) = self.span.id() else { return Err(Unnumberable) }; + let mut replacement_range = 0..replacement.len(); + + // Trim off common prefix. + while range.start < range.end + && replacement_range.start < replacement_range.end + && self.children[range.start] + .spanless_eq(&replacement[replacement_range.start]) + { + range.start += 1; + replacement_range.start += 1; + } + + // Trim off common suffix. + while range.start < range.end + && replacement_range.start < replacement_range.end + && self.children[range.end - 1] + .spanless_eq(&replacement[replacement_range.end - 1]) + { + range.end -= 1; + replacement_range.end -= 1; + } + + let mut replacement_vec = replacement; + let replacement = &replacement_vec[replacement_range.clone()]; let superseded = &self.children[range.clone()]; // Compute the new byte length. @@ -470,9 +523,9 @@ impl InnerNode { || self.children[range.end..].iter().any(SyntaxNode::erroneous)); // Perform the replacement. - let replacement_count = replacement.len(); - self.children.splice(range.clone(), replacement); - range.end = range.start + replacement_count; + self.children + .splice(range.clone(), replacement_vec.drain(replacement_range.clone())); + range.end = range.start + replacement_range.len(); // Renumber the new children. Retries until it works, taking // exponentially more children into account. @@ -577,6 +630,11 @@ impl ErrorNode { fn hint(&mut self, hint: impl Into) { self.error.hints.push(hint.into()); } + + /// Whether the two leaf nodes are the same apart from spans. + fn spanless_eq(&self, other: &Self) -> bool { + self.text == other.text && self.error.spanless_eq(&other.error) + } } impl Debug for ErrorNode { @@ -597,6 +655,13 @@ pub struct SyntaxError { pub hints: Vec, } +impl SyntaxError { + /// Whether the two errors are the same apart from spans. + fn spanless_eq(&self, other: &Self) -> bool { + self.message == other.message && self.hints == other.hints + } +} + /// A syntax node in a context. /// /// Knows its exact offset in the file and provides access to its diff --git a/tests/src/tests.rs b/tests/src/tests.rs index e7595cf79..d75369074 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -25,7 +25,7 @@ use typst::doc::{Document, Frame, FrameItem, Meta}; use typst::eval::{eco_format, func, Bytes, Datetime, Library, NoneValue, Tracer, Value}; use typst::font::{Font, FontBook}; use typst::geom::{Abs, Color, Smart}; -use typst::syntax::{FileId, PackageVersion, Source, Span, SyntaxNode, VirtualPath}; +use typst::syntax::{FileId, PackageVersion, Source, SyntaxNode, VirtualPath}; use typst::{World, WorldExt}; use typst_library::layout::{Margin, PageElem}; use typst_library::text::{TextElem, TextSize}; @@ -779,7 +779,6 @@ fn test_reparse( ]; let mut ok = true; - let mut apply = |replace: Range, with| { let mut incr_source = Source::detached(text); if incr_source.root().len() != text.len() { @@ -795,18 +794,14 @@ fn test_reparse( let edited_src = incr_source.text(); let ref_source = Source::detached(edited_src); - let mut ref_root = ref_source.root().clone(); - let mut incr_root = incr_source.root().clone(); + let ref_root = ref_source.root(); + let incr_root = incr_source.root(); // Ensures that the span numbering invariants hold. - let spans_ok = test_spans(output, &ref_root) && test_spans(output, &incr_root); + let spans_ok = test_spans(output, ref_root) && test_spans(output, incr_root); - // Remove all spans so that the comparison works out. - let tree_ok = { - ref_root.synthesize(Span::detached()); - incr_root.synthesize(Span::detached()); - ref_root == incr_root - }; + // Ensure that the reference and incremental trees are the same. + let tree_ok = ref_root.spanless_eq(incr_root); if !tree_ok { writeln!(