diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 10aaad234..1ab2fb15d 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -25,6 +25,20 @@ pub fn parse(src: &str) -> Rc { } } +/// Parse a block. Returns `Some` if there was only one block. +pub fn parse_block(source: &str) -> Option> { + let mut p = Parser::new(source); + block(&mut p); + if p.eof() { + match p.finish().into_iter().next() { + Some(Green::Node(node)) => Some(node), + _ => unreachable!(), + } + } else { + None + } +} + /// Parse markup. fn markup(p: &mut Parser) { markup_while(p, true, &mut |_| true) diff --git a/src/source.rs b/src/source.rs index 432688a0b..069edd29f 100644 --- a/src/source.rs +++ b/src/source.rs @@ -268,7 +268,7 @@ impl SourceFile { /// This panics if the `replace` range is out of bounds. pub fn edit(&mut self, replace: Range, with: &str) { let start = replace.start; - self.src.replace_range(replace, with); + self.src.replace_range(replace.clone(), with); // Remove invalidated line starts. let line = self.byte_to_line(start).unwrap(); @@ -283,8 +283,39 @@ impl SourceFile { self.line_starts .extend(newlines(&self.src[start ..]).map(|idx| start + idx)); - // Reparse. - self.root = parse(&self.src); + // Update the root node. + #[cfg(not(feature = "parse-cache"))] + { + self.root = parse(&self.src); + } + + #[cfg(feature = "parse-cache")] + { + let insertion_span = replace.into_span(self.id); + let incremental_target = + Rc::make_mut(&mut self.root).incremental_parent(insertion_span); + + match incremental_target { + Some((child_idx, parent, offset)) => { + let child = &parent.children()[child_idx]; + let src = &self.src[offset .. offset + child.len()]; + let parse_res = match child.kind() { + NodeKind::Markup => Some(parse(src)), + _ => parse_block(src), + } + .and_then(|x| x.data().erroneous().not().then(|| x)); + + if let Some(parse_res) = parse_res { + parent.replace_child(child_idx, parse_res); + } else { + self.root = parse(&self.src); + } + } + None => { + self.root = parse(&self.src); + } + } + } } /// Provide highlighting categories for the given range of the source file. @@ -473,4 +504,21 @@ mod tests { // Test removing everything. test(TEST, 0 .. 21, "", ""); } + + #[test] + fn test_source_file_edit_2() { + #[track_caller] + fn test(prev: &str, range: Range, with: &str, after: &str) { + let mut source = SourceFile::detached(prev); + let result = SourceFile::detached(after); + dbg!(Green::from(source.root.clone())); + source.edit(range, with); + assert_eq!(source.src, result.src); + assert_eq!(source.line_starts, result.line_starts); + dbg!(Green::from(source.root)); + } + + // Test inserting at the begining. + test("abc #f()[def] ghi", 10 .. 11, "xyz", "abc #f()[dxyzf] ghi"); + } } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index d9ad42a88..b0911c63d 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -127,6 +127,92 @@ impl GreenNode { pub fn children(&self) -> &[Green] { &self.children } + + /// The node's children, mutably. + pub fn children_mut(&mut self) -> &mut [Green] { + &mut self.children + } + + /// The node's metadata. + pub fn data(&self) -> &GreenData { + &self.data + } + + /// The node's type. + pub fn kind(&self) -> &NodeKind { + self.data().kind() + } + + /// The node's length. + pub fn len(&self) -> usize { + self.data().len() + } + + /// Find the parent of the deepest incremental-safe node and the index of + /// the found child. + pub fn incremental_parent( + &mut self, + span: Span, + ) -> Option<(usize, &mut GreenNode, usize)> { + self.incremental_parent_internal(span, 0) + } + + fn incremental_parent_internal( + &mut self, + span: Span, + mut offset: usize, + ) -> Option<(usize, &mut GreenNode, usize)> { + let x = unsafe { &mut *(self as *mut _) }; + + for (i, child) in self.children.iter_mut().enumerate() { + match child { + Green::Token(n) => { + if offset < span.start { + // the token is strictly before the span + offset += n.len(); + } else { + // the token is within or after the span; tokens are + // never safe, so we return. + return None; + } + } + Green::Node(n) => { + let end = n.len() + offset; + if offset < span.start && end < span.start { + // the node is strictly before the span + offset += n.len(); + } else if span.start >= offset + && span.start < end + && span.end <= end + && span.end > offset + { + // the node is within the span. + if n.kind().is_incremental_safe() { + let res = + Rc::make_mut(n).incremental_parent_internal(span, offset); + if res.is_none() { + return Some((i, x, offset)); + } + } else { + return Rc::make_mut(n) + .incremental_parent_internal(span, offset); + } + } else { + // the node is overlapping or after after the span; nodes are + // never safe, so we return. + return None; + } + } + } + } + + return None; + } + + /// Replace one of the node's children. + pub fn replace_child(&mut self, index: usize, child: impl Into) { + self.children[index] = child.into(); + } } impl From for Green { @@ -653,6 +739,14 @@ impl NodeKind { matches!(self, NodeKind::Error(_, _) | NodeKind::Unknown(_)) } + /// Whether it is safe to do incremental parsing on this node. + pub fn is_incremental_safe(&self) -> bool { + match self { + Self::Block | Self::Markup => true, + _ => false, + } + } + /// A human-readable name for the kind. pub fn as_str(&self) -> &'static str { match self { diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 4d5b88195..a707d3d93 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -125,6 +125,17 @@ impl Span { *self = self.join(other) } + /// Create a new span with n characters inserted inside of this span. + pub fn inserted(mut self, other: Self, n: usize) -> Self { + if !self.contains(other.start) || !self.contains(other.end) { + panic!(); + } + + let len_change = (n as isize - other.len() as isize) as usize; + self.end += len_change; + self + } + /// Test whether a position is within the span. pub fn contains(&self, pos: usize) -> bool { self.start <= pos && self.end >= pos