Restructure parsing to use edits_since

This commit is contained in:
Antonio Scandurra
2021-05-22 12:34:35 +02:00
parent 9836f1f638
commit 64b1113455
+87 -77
View File
@@ -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);
new_tree = Some(tree);
}
});
// 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())
}
});
// 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;
had_changes = has_changes;
old_text = new_text;
old_tree = new_tree;
old_version = new_version;
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()
})
}
}