From 64b1113455f3b64e8d042b0b8aa649913b351a9d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 21 May 2021 11:46:59 +0200 Subject: [PATCH] Restructure parsing to use `edits_since` --- zed/src/editor/buffer/mod.rs | 166 +++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 78 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 233ec2832d02480a91b580b403f437a9ea5db4bc..178f1844a45534d632fd07eca3c2b6d9858886bc 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/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, language: Option>, - tree: Option, + tree: Option<(Tree, time::Global, Rope)>, is_parsing: bool, selections: HashMap>, 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) { // 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::>>(); - 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::(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, 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() }) } }