summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/parse/mod.rs14
-rw-r--r--src/source.rs54
-rw-r--r--src/syntax/mod.rs94
-rw-r--r--src/syntax/span.rs11
4 files changed, 170 insertions, 3 deletions
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 10aaad23..1ab2fb15 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -25,6 +25,20 @@ pub fn parse(src: &str) -> Rc<GreenNode> {
}
}
+/// Parse a block. Returns `Some` if there was only one block.
+pub fn parse_block(source: &str) -> Option<Rc<GreenNode>> {
+ 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 432688a0..069edd29 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<usize>, 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<usize>, 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 d9ad42a8..b0911c63 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<Green>) {
+ self.children[index] = child.into();
+ }
}
impl From<GreenNode> 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 4d5b8819..a707d3d9 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