diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs index 77a560bfe5..3a05cc480b 100644 --- a/crates/syntax/src/syntax_editor.rs +++ b/crates/syntax/src/syntax_editor.rs @@ -6,6 +6,7 @@ use std::{ num::NonZeroU32, + ops::RangeInclusive, sync::atomic::{AtomicU32, Ordering}, }; @@ -76,6 +77,26 @@ impl SyntaxEditor { self.changes.push(Change::Replace(old.syntax_element(), Some(new.syntax_element()))); } + pub fn replace_with_many(&mut self, old: impl Element, new: Vec) { + let old = old.syntax_element(); + debug_assert!(is_ancestor_or_self_of_element(&old, &self.root)); + debug_assert!( + !(matches!(&old, SyntaxElement::Node(node) if node == &self.root) && new.len() > 1), + "cannot replace root node with many elements" + ); + self.changes.push(Change::ReplaceWithMany(old.syntax_element(), new)); + } + + pub fn replace_all(&mut self, range: RangeInclusive, new: Vec) { + if range.start() == range.end() { + self.replace_with_many(range.start(), new); + return; + } + + debug_assert!(is_ancestor_or_self_of_element(range.start(), &self.root)); + self.changes.push(Change::ReplaceAll(range, new)) + } + pub fn finish(self) -> SyntaxEdit { edit_algo::apply_edits(self) } @@ -186,10 +207,17 @@ impl Position { #[derive(Debug)] enum Change { + /// Inserts a single element at the specified position. Insert(Position, SyntaxElement), + /// Inserts many elements in-order at the specified position. InsertAll(Position, Vec), /// Represents both a replace single element and a delete element operation. Replace(SyntaxElement, Option), + /// Replaces a single element with many elements. + ReplaceWithMany(SyntaxElement, Vec), + /// Replaces a range of elements with another list of elements. + /// Range will always have start != end. + ReplaceAll(RangeInclusive, Vec), } impl Change { @@ -202,24 +230,29 @@ impl Change { ), PositionRepr::After(child) => TextRange::at(child.text_range().end(), 0.into()), }, - Change::Replace(target, _) => target.text_range(), + Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => target.text_range(), + Change::ReplaceAll(range, _) => { + range.start().text_range().cover(range.end().text_range()) + } } } fn target_parent(&self) -> SyntaxNode { match self { Change::Insert(target, _) | Change::InsertAll(target, _) => target.parent(), - Change::Replace(SyntaxElement::Node(target), _) => { - target.parent().unwrap_or_else(|| target.clone()) - } - Change::Replace(SyntaxElement::Token(target), _) => target.parent().unwrap(), + Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => match target { + SyntaxElement::Node(target) => target.parent().unwrap_or_else(|| target.clone()), + SyntaxElement::Token(target) => target.parent().unwrap(), + }, + Change::ReplaceAll(target, _) => target.start().parent().unwrap(), } } fn change_kind(&self) -> ChangeKind { match self { Change::Insert(_, _) | Change::InsertAll(_, _) => ChangeKind::Insert, - Change::Replace(_, _) => ChangeKind::Replace, + Change::Replace(_, _) | Change::ReplaceWithMany(_, _) => ChangeKind::Replace, + Change::ReplaceAll(_, _) => ChangeKind::ReplaceRange, } } } @@ -227,7 +260,7 @@ impl Change { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] enum ChangeKind { Insert, - // TODO: deal with replace spans + ReplaceRange, Replace, } diff --git a/crates/syntax/src/syntax_editor/edit_algo.rs b/crates/syntax/src/syntax_editor/edit_algo.rs index 2c331fc1f6..3b92ac1cbd 100644 --- a/crates/syntax/src/syntax_editor/edit_algo.rs +++ b/crates/syntax/src/syntax_editor/edit_algo.rs @@ -61,7 +61,13 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { .zip(changes.iter().skip(1)) .filter(|(l, r)| { // We only care about checking for disjoint replace ranges - l.change_kind() == ChangeKind::Replace && r.change_kind() == ChangeKind::Replace + matches!( + (l.change_kind(), r.change_kind()), + ( + ChangeKind::Replace | ChangeKind::ReplaceRange, + ChangeKind::Replace | ChangeKind::ReplaceRange + ) + ) }) .all(|(l, r)| { get_node_depth(l.target_parent()) != get_node_depth(r.target_parent()) @@ -97,6 +103,7 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { // Pop off any ancestors that aren't applicable changed_ancestors.drain((index + 1)..); + // FIXME: Resolve changes that depend on a range of elements let ancestor = &changed_ancestors[index]; dependent_changes.push(DependentChange { @@ -115,9 +122,12 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { // Add to changed ancestors, if applicable match change { Change::Insert(_, _) | Change::InsertAll(_, _) => {} - Change::Replace(target, _) => { + Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => { changed_ancestors.push_back(ChangedAncestor::single(target, change_index)) } + Change::ReplaceAll(range, _) => { + changed_ancestors.push_back(ChangedAncestor::multiple(range, change_index)) + } } } @@ -137,9 +147,15 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { } }; } - Change::Replace(target, _) => { + Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => { *target = tree_mutator.make_element_mut(target); } + Change::ReplaceAll(range, _) => { + let start = tree_mutator.make_element_mut(range.start()); + let end = tree_mutator.make_element_mut(range.end()); + + *range = start..=end; + } } // Collect changed elements @@ -148,6 +164,10 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { Change::InsertAll(_, elements) => changed_elements.extend(elements.iter().cloned()), Change::Replace(_, Some(element)) => changed_elements.push(element.clone()), Change::Replace(_, None) => {} + Change::ReplaceWithMany(_, elements) => { + changed_elements.extend(elements.iter().cloned()) + } + Change::ReplaceAll(_, elements) => changed_elements.extend(elements.iter().cloned()), } } @@ -160,6 +180,9 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { } // Silently drop outdated change Change::Replace(_, None) => continue, + Change::ReplaceAll(_, _) | Change::ReplaceWithMany(_, _) => { + unimplemented!("cannot resolve changes that depend on replacing many elements") + } }; let upmap_target_node = |target: &SyntaxNode| { @@ -185,9 +208,12 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { *child = upmap_target(child); } }, - Change::Replace(target, _) => { + Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => { *target = upmap_target(&target); } + Change::ReplaceAll(range, _) => { + *range = upmap_target(range.start())..=upmap_target(range.end()); + } } } @@ -214,6 +240,16 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { let parent = target.parent().unwrap(); parent.splice_children(target.index()..target.index() + 1, vec![new_target]); } + Change::ReplaceWithMany(target, elements) => { + let parent = target.parent().unwrap(); + parent.splice_children(target.index()..target.index() + 1, elements); + } + Change::ReplaceAll(range, elements) => { + let start = range.start().index(); + let end = range.end().index(); + let parent = range.start().parent().unwrap(); + parent.splice_children(start..end + 1, elements); + } } } @@ -252,7 +288,7 @@ struct ChangedAncestor { enum ChangedAncestorKind { Single { node: SyntaxNode }, - Range { changed_nodes: RangeInclusive, in_parent: SyntaxNode }, + Range { _changed_elements: RangeInclusive, in_parent: SyntaxNode }, } impl ChangedAncestor { @@ -267,13 +303,25 @@ impl ChangedAncestor { Self { kind, change_index } } + fn multiple(range: &RangeInclusive, change_index: usize) -> Self { + Self { + kind: ChangedAncestorKind::Range { + _changed_elements: range.clone(), + in_parent: range.start().parent().unwrap(), + }, + change_index, + } + } + fn affected_range(&self) -> TextRange { match &self.kind { ChangedAncestorKind::Single { node } => node.text_range(), - ChangedAncestorKind::Range { changed_nodes, in_parent: _ } => TextRange::new( - changed_nodes.start().text_range().start(), - changed_nodes.end().text_range().end(), - ), + ChangedAncestorKind::Range { _changed_elements: changed_nodes, in_parent: _ } => { + TextRange::new( + changed_nodes.start().text_range().start(), + changed_nodes.end().text_range().end(), + ) + } } } }