Restructure parsing to use `edits_since`

Antonio Scandurra created

Change summary

zed/src/editor/buffer/mod.rs | 166 ++++++++++++++++++++-----------------
1 file changed, 88 insertions(+), 78 deletions(-)

Detailed changes

zed/src/editor/buffer/mod.rs 🔗

@@ -16,7 +16,7 @@ use crate::{
     language::{Language, Tree},
     operation_queue::{self, OperationQueue},
     sum_tree::{self, FilterCursor, SeekBias, SumTree},
-    time::{self, Global, ReplicaId},
+    time::{self, ReplicaId},
     worktree::FileHandle,
 };
 use anyhow::{anyhow, Result};
@@ -76,7 +76,7 @@ pub struct Buffer {
     history: History,
     file: Option<FileHandle>,
     language: Option<Arc<Language>>,
-    tree: Option<Tree>,
+    tree: Option<(Tree, time::Global, Rope)>,
     is_parsing: bool,
     selections: HashMap<SelectionSetId, Arc<[Selection]>>,
     pub selections_last_update: SelectionsVersion,
@@ -546,6 +546,12 @@ impl Buffer {
         ctx.emit(Event::Saved);
     }
 
+    fn should_reparse(&self) -> bool {
+        self.tree
+            .as_ref()
+            .map_or(true, |(_, tree_version, _)| *tree_version != self.version)
+    }
+
     fn reparse(&mut self, ctx: &mut ModelContext<Self>) {
         // Avoid spawning a new parsing task if the buffer is already being reparsed
         // due to an earlier edit.
@@ -555,76 +561,60 @@ impl Buffer {
 
         if let Some(language) = self.language.clone() {
             self.is_parsing = true;
-            let mut old_text = self.visible_text.clone();
-            let mut old_tree = self.tree.clone();
-            let mut old_version = self.version();
-            let mut had_changes = true;
             ctx.spawn(|handle, mut ctx| async move {
-                while had_changes {
-                    // Parse the current text in a background thread.
-                    let (mut tree, text) = ctx
-                        .background_executor()
-                        .spawn({
-                            let language = language.clone();
-                            async move {
-                                let tree = Self::parse_text(&old_text, old_tree, &language);
-                                (tree, old_text)
-                            }
-                        })
-                        .await;
-
-                    // When the parsing completes, check if any new changes have occurred since
-                    // this parse began. If so, edit the new tree to reflect these new changes.
-                    let (has_changes, new_text, new_tree, new_version) =
-                        handle.update(&mut ctx, move |this, ctx| {
+                while handle.read_with(&ctx, |this, _| this.should_reparse()) {
+                    // The parse tree is out of date, so we clone it and synchronously splice in all
+                    // the edits that have happened since the last parse.
+                    let (new_version, new_text) = handle
+                        .read_with(&ctx, |this, _| (this.version(), this.visible_text.clone()));
+                    let mut new_tree = None;
+                    handle.update(&mut ctx, |this, _| {
+                        if let Some((mut tree, tree_version, old_text)) = this.tree.clone() {
                             let mut delta = 0_isize;
-                            let mut has_further_changes = false;
                             for Edit {
                                 old_range,
                                 new_range,
-                            } in this.edits_since(old_version)
+                            } in this.edits_since(tree_version)
                             {
-                                let old_len = old_range.end - old_range.start;
-                                let new_len = new_range.end - new_range.start;
-                                let old_start = (old_range.start as isize + delta) as usize;
+                                let start_offset = (old_range.start as isize + delta) as usize;
+                                let start_point = new_text.to_point(start_offset);
+                                let old_bytes = old_range.end - old_range.start;
+                                let new_bytes = new_range.end - new_range.start;
+                                let old_lines = old_text.to_point(old_range.end)
+                                    - old_text.to_point(old_range.start);
                                 tree.edit(&InputEdit {
-                                    start_byte: old_start,
-                                    old_end_byte: old_start + old_len,
-                                    new_end_byte: old_start + new_len,
-                                    start_position: text.to_point(old_start).into(),
-                                    old_end_position: text.to_point(old_start + old_len).into(),
-                                    new_end_position: this
-                                        .point_for_offset(old_start + new_len)
-                                        .unwrap()
+                                    start_byte: start_offset,
+                                    old_end_byte: start_offset + old_bytes,
+                                    new_end_byte: start_offset + new_bytes,
+                                    start_position: start_point.into(),
+                                    old_end_position: (start_point + old_lines).into(),
+                                    new_end_position: new_text
+                                        .to_point(start_offset + new_bytes)
                                         .into(),
                                 });
-                                delta += new_len as isize - old_len as isize;
-                                has_further_changes = true;
+                                delta += new_bytes as isize - old_bytes as isize;
                             }
 
-                            this.tree = Some(tree);
-                            ctx.emit(Event::Reparsed);
-
-                            // If there were new changes, then continue the loop, spawning a new
-                            // parsing task. Otherwise, record the fact that parsing is complete.
-                            if has_further_changes {
-                                (
-                                    true,
-                                    this.visible_text.clone(),
-                                    this.tree.clone(),
-                                    this.version(),
-                                )
-                            } else {
-                                this.is_parsing = false;
-                                (false, Rope::new(), None, Global::new())
-                            }
-                        });
+                            new_tree = Some(tree);
+                        }
+                    });
 
-                    had_changes = has_changes;
-                    old_text = new_text;
-                    old_tree = new_tree;
-                    old_version = new_version;
+                    // Parse the current text in a background thread.
+                    let new_tree = ctx
+                        .background_executor()
+                        .spawn({
+                            let language = language.clone();
+                            let new_text = new_text.clone();
+                            async move { Self::parse_text(&new_text, new_tree, &language) }
+                        })
+                        .await;
+
+                    handle.update(&mut ctx, |this, ctx| {
+                        this.tree = Some((new_tree, new_version, new_text));
+                        ctx.emit(Event::Reparsed);
+                    });
                 }
+                handle.update(&mut ctx, |this, _| this.is_parsing = false);
             })
             .detach();
         }
@@ -637,15 +627,16 @@ impl Buffer {
                 .set_language(language.grammar)
                 .expect("incompatible grammar");
             let mut chunks = text.chunks_in_range(0..text.len());
-            parser
+            let tree = parser
                 .parse_with(
                     &mut move |offset, _| {
                         chunks.seek(offset);
-                        chunks.next().map(str::as_bytes).unwrap_or(&[])
+                        chunks.next().unwrap_or("").as_bytes()
                     },
                     old_tree.as_ref(),
                 )
-                .unwrap()
+                .unwrap();
+            tree
         })
     }
 
@@ -867,21 +858,6 @@ impl Buffer {
             .map(|range| range.start.to_offset(self)..range.end.to_offset(self))
             .collect::<Vec<Range<usize>>>();
 
-        if let Some(tree) = self.tree.as_mut() {
-            let new_extent = TextSummary::from(new_text.as_str()).lines;
-            for old_range in old_ranges.iter().rev() {
-                let start_position = self.visible_text.to_point(old_range.start);
-                tree.edit(&InputEdit {
-                    start_byte: old_range.start,
-                    old_end_byte: old_range.end,
-                    new_end_byte: old_range.start + new_text.len(),
-                    start_position: start_position.into(),
-                    old_end_position: self.visible_text.to_point(old_range.end).into(),
-                    new_end_position: (start_position + new_extent).into(),
-                });
-            }
-        }
-
         let new_text = if new_text.len() > 0 {
             Some(new_text)
         } else {
@@ -3432,9 +3408,43 @@ mod tests {
             )
         );
 
+        buffer.update(&mut ctx, |buf, ctx| {
+            buf.undo(Some(ctx));
+            assert_eq!(buf.text(), "fn a() {}");
+            assert!(buf.is_parsing);
+        });
+        buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await;
+        assert_eq!(
+            get_tree_sexp(&buffer, &ctx),
+            concat!(
+                "(source_file (function_item name: (identifier) ",
+                "parameters: (parameters) ",
+                "body: (block)))"
+            )
+        );
+
+        buffer.update(&mut ctx, |buf, ctx| {
+            buf.redo(Some(ctx));
+            assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
+            assert!(buf.is_parsing);
+        });
+        buffer.condition(&ctx, |buffer, _| !buffer.is_parsing).await;
+        assert_eq!(
+            get_tree_sexp(&buffer, &ctx),
+            concat!(
+                "(source_file (function_item name: (identifier) ",
+                    "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
+                    "body: (block (call_expression ",
+                        "function: (generic_function ",
+                            "function: (field_expression value: (identifier) field: (field_identifier)) ",
+                            "type_arguments: (type_arguments (type_identifier))) ",
+                            "arguments: (arguments (identifier))))))",
+            )
+        );
+
         fn get_tree_sexp(buffer: &ModelHandle<Buffer>, ctx: &gpui::TestAppContext) -> String {
             buffer.read_with(ctx, |buffer, _| {
-                buffer.tree.as_ref().unwrap().root_node().to_sexp()
+                buffer.tree.as_ref().unwrap().0.root_node().to_sexp()
             })
         }
     }