Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/syntax/src/lib.rs1
-rw-r--r--crates/syntax/src/syntax_editor.rs611
-rw-r--r--crates/syntax/src/syntax_editor/edit_algo.rs367
-rw-r--r--crates/syntax/src/syntax_editor/mapping.rs272
4 files changed, 1251 insertions, 0 deletions
diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs
index b68374848b..dbbb290b4f 100644
--- a/crates/syntax/src/lib.rs
+++ b/crates/syntax/src/lib.rs
@@ -40,6 +40,7 @@ pub mod ast;
#[doc(hidden)]
pub mod fuzz;
pub mod hacks;
+pub mod syntax_editor;
pub mod ted;
pub mod utils;
diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs
new file mode 100644
index 0000000000..eb114f5e5f
--- /dev/null
+++ b/crates/syntax/src/syntax_editor.rs
@@ -0,0 +1,611 @@
+//! Syntax Tree editor
+//!
+//! Inspired by Roslyn's [`SyntaxEditor`], but is temporarily built upon mutable syntax tree editing.
+//!
+//! [`SyntaxEditor`]: https://github.com/dotnet/roslyn/blob/43b0b05cc4f492fd5de00f6f6717409091df8daa/src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs
+
+use std::{
+ num::NonZeroU32,
+ ops::RangeInclusive,
+ sync::atomic::{AtomicU32, Ordering},
+};
+
+use rowan::TextRange;
+use rustc_hash::FxHashMap;
+
+use crate::{SyntaxElement, SyntaxNode, SyntaxToken};
+
+mod edit_algo;
+mod mapping;
+
+pub use mapping::{SyntaxMapping, SyntaxMappingBuilder};
+
+#[derive(Debug)]
+pub struct SyntaxEditor {
+ root: SyntaxNode,
+ changes: Vec<Change>,
+ mappings: SyntaxMapping,
+ annotations: Vec<(SyntaxElement, SyntaxAnnotation)>,
+}
+
+impl SyntaxEditor {
+ /// Creates a syntax editor to start editing from `root`
+ pub fn new(root: SyntaxNode) -> Self {
+ Self { root, changes: vec![], mappings: SyntaxMapping::new(), annotations: vec![] }
+ }
+
+ pub fn add_annotation(&mut self, element: impl Element, annotation: SyntaxAnnotation) {
+ self.annotations.push((element.syntax_element(), annotation))
+ }
+
+ pub fn merge(&mut self, mut other: SyntaxEditor) {
+ debug_assert!(
+ self.root == other.root || other.root.ancestors().any(|node| node == self.root),
+ "{:?} is not in the same tree as {:?}",
+ other.root,
+ self.root
+ );
+
+ self.changes.append(&mut other.changes);
+ self.mappings.merge(other.mappings);
+ self.annotations.append(&mut other.annotations);
+ }
+
+ pub fn insert(&mut self, position: Position, element: impl Element) {
+ debug_assert!(is_ancestor_or_self(&position.parent(), &self.root));
+ self.changes.push(Change::Insert(position, element.syntax_element()))
+ }
+
+ pub fn insert_all(&mut self, position: Position, elements: Vec<SyntaxElement>) {
+ debug_assert!(is_ancestor_or_self(&position.parent(), &self.root));
+ self.changes.push(Change::InsertAll(position, elements))
+ }
+
+ pub fn delete(&mut self, element: impl Element) {
+ let element = element.syntax_element();
+ debug_assert!(is_ancestor_or_self_of_element(&element, &self.root));
+ debug_assert!(
+ !matches!(&element, SyntaxElement::Node(node) if node == &self.root),
+ "should not delete root node"
+ );
+ self.changes.push(Change::Replace(element.syntax_element(), None));
+ }
+
+ pub fn replace(&mut self, old: impl Element, new: impl Element) {
+ let old = old.syntax_element();
+ debug_assert!(is_ancestor_or_self_of_element(&old, &self.root));
+ self.changes.push(Change::Replace(old.syntax_element(), Some(new.syntax_element())));
+ }
+
+ pub fn replace_with_many(&mut self, old: impl Element, new: Vec<SyntaxElement>) {
+ 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<SyntaxElement>, new: Vec<SyntaxElement>) {
+ 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)
+ }
+}
+
+/// Represents a completed [`SyntaxEditor`] operation.
+pub struct SyntaxEdit {
+ old_root: SyntaxNode,
+ new_root: SyntaxNode,
+ changed_elements: Vec<SyntaxElement>,
+ annotations: FxHashMap<SyntaxAnnotation, Vec<SyntaxElement>>,
+}
+
+impl SyntaxEdit {
+ /// Root of the initial unmodified syntax tree.
+ pub fn old_root(&self) -> &SyntaxNode {
+ &self.old_root
+ }
+
+ /// Root of the modified syntax tree.
+ pub fn new_root(&self) -> &SyntaxNode {
+ &self.new_root
+ }
+
+ /// Which syntax elements in the modified syntax tree were inserted or
+ /// modified as part of the edit.
+ ///
+ /// Note that for syntax nodes, only the upper-most parent of a set of
+ /// changes is included, not any child elements that may have been modified.
+ pub fn changed_elements(&self) -> &[SyntaxElement] {
+ self.changed_elements.as_slice()
+ }
+
+ /// Finds which syntax elements have been annotated with the given
+ /// annotation.
+ ///
+ /// Note that an annotation might not appear in the modified syntax tree if
+ /// the syntax elements that were annotated did not make it into the final
+ /// syntax tree.
+ pub fn find_annotation(&self, annotation: SyntaxAnnotation) -> &[SyntaxElement] {
+ self.annotations.get(&annotation).as_ref().map_or(&[], |it| it.as_slice())
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[repr(transparent)]
+pub struct SyntaxAnnotation(NonZeroU32);
+
+impl SyntaxAnnotation {
+ /// Creates a unique syntax annotation to attach data to.
+ pub fn new() -> Self {
+ static COUNTER: AtomicU32 = AtomicU32::new(1);
+
+ // Only consistency within a thread matters, as SyntaxElements are !Send
+ let id = COUNTER.fetch_add(1, Ordering::Relaxed);
+
+ Self(NonZeroU32::new(id).expect("syntax annotation id overflow"))
+ }
+}
+
+impl Default for SyntaxAnnotation {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// Position describing where to insert elements
+#[derive(Debug)]
+pub struct Position {
+ repr: PositionRepr,
+}
+
+impl Position {
+ pub(crate) fn parent(&self) -> SyntaxNode {
+ self.place().0
+ }
+
+ pub(crate) fn place(&self) -> (SyntaxNode, usize) {
+ match &self.repr {
+ PositionRepr::FirstChild(parent) => (parent.clone(), 0),
+ PositionRepr::After(child) => (child.parent().unwrap(), child.index() + 1),
+ }
+ }
+}
+
+#[derive(Debug)]
+enum PositionRepr {
+ FirstChild(SyntaxNode),
+ After(SyntaxElement),
+}
+
+impl Position {
+ pub fn after(elem: impl Element) -> Position {
+ let repr = PositionRepr::After(elem.syntax_element());
+ Position { repr }
+ }
+
+ pub fn before(elem: impl Element) -> Position {
+ let elem = elem.syntax_element();
+ let repr = match elem.prev_sibling_or_token() {
+ Some(it) => PositionRepr::After(it),
+ None => PositionRepr::FirstChild(elem.parent().unwrap()),
+ };
+ Position { repr }
+ }
+
+ pub fn first_child_of(node: &(impl Into<SyntaxNode> + Clone)) -> Position {
+ let repr = PositionRepr::FirstChild(node.clone().into());
+ Position { repr }
+ }
+
+ pub fn last_child_of(node: &(impl Into<SyntaxNode> + Clone)) -> Position {
+ let node = node.clone().into();
+ let repr = match node.last_child_or_token() {
+ Some(it) => PositionRepr::After(it),
+ None => PositionRepr::FirstChild(node),
+ };
+ Position { repr }
+ }
+}
+
+#[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<SyntaxElement>),
+ /// Represents both a replace single element and a delete element operation.
+ Replace(SyntaxElement, Option<SyntaxElement>),
+ /// Replaces a single element with many elements.
+ ReplaceWithMany(SyntaxElement, Vec<SyntaxElement>),
+ /// Replaces a range of elements with another list of elements.
+ /// Range will always have start != end.
+ ReplaceAll(RangeInclusive<SyntaxElement>, Vec<SyntaxElement>),
+}
+
+impl Change {
+ fn target_range(&self) -> TextRange {
+ match self {
+ Change::Insert(target, _) | Change::InsertAll(target, _) => match &target.repr {
+ PositionRepr::FirstChild(parent) => TextRange::at(
+ parent.first_child_or_token().unwrap().text_range().start(),
+ 0.into(),
+ ),
+ PositionRepr::After(child) => TextRange::at(child.text_range().end(), 0.into()),
+ },
+ 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(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(_, _) | Change::ReplaceWithMany(_, _) => ChangeKind::Replace,
+ Change::ReplaceAll(_, _) => ChangeKind::ReplaceRange,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+enum ChangeKind {
+ Insert,
+ ReplaceRange,
+ Replace,
+}
+
+/// Utility trait to allow calling syntax editor functions with references or owned
+/// nodes. Do not use outside of this module.
+pub trait Element {
+ fn syntax_element(self) -> SyntaxElement;
+}
+
+impl<E: Element + Clone> Element for &'_ E {
+ fn syntax_element(self) -> SyntaxElement {
+ self.clone().syntax_element()
+ }
+}
+
+impl Element for SyntaxElement {
+ fn syntax_element(self) -> SyntaxElement {
+ self
+ }
+}
+
+impl Element for SyntaxNode {
+ fn syntax_element(self) -> SyntaxElement {
+ self.into()
+ }
+}
+
+impl Element for SyntaxToken {
+ fn syntax_element(self) -> SyntaxElement {
+ self.into()
+ }
+}
+
+fn is_ancestor_or_self(node: &SyntaxNode, ancestor: &SyntaxNode) -> bool {
+ node == ancestor || node.ancestors().any(|it| &it == ancestor)
+}
+
+fn is_ancestor_or_self_of_element(node: &SyntaxElement, ancestor: &SyntaxNode) -> bool {
+ matches!(node, SyntaxElement::Node(node) if node == ancestor)
+ || node.ancestors().any(|it| &it == ancestor)
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::expect;
+ use itertools::Itertools;
+
+ use crate::{
+ ast::{self, make, HasName},
+ AstNode,
+ };
+
+ use super::*;
+
+ fn make_ident_pat(
+ editor: Option<&mut SyntaxEditor>,
+ ref_: bool,
+ mut_: bool,
+ name: ast::Name,
+ ) -> ast::IdentPat {
+ let ast = make::ident_pat(ref_, mut_, name.clone()).clone_for_update();
+
+ if let Some(editor) = editor {
+ let mut mapping = SyntaxMappingBuilder::new(ast.syntax().clone());
+ mapping.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone());
+ mapping.finish(editor);
+ }
+
+ ast
+ }
+
+ fn make_let_stmt(
+ editor: Option<&mut SyntaxEditor>,
+ pattern: ast::Pat,
+ ty: Option<ast::Type>,
+ initializer: Option<ast::Expr>,
+ ) -> ast::LetStmt {
+ let ast =
+ make::let_stmt(pattern.clone(), ty.clone(), initializer.clone()).clone_for_update();
+
+ if let Some(editor) = editor {
+ let mut mapping = SyntaxMappingBuilder::new(ast.syntax().clone());
+ mapping.map_node(pattern.syntax().clone(), ast.pat().unwrap().syntax().clone());
+ if let Some(input) = ty {
+ mapping.map_node(input.syntax().clone(), ast.ty().unwrap().syntax().clone());
+ }
+ if let Some(input) = initializer {
+ mapping
+ .map_node(input.syntax().clone(), ast.initializer().unwrap().syntax().clone());
+ }
+ mapping.finish(editor);
+ }
+
+ ast
+ }
+
+ fn make_block_expr(
+ editor: Option<&mut SyntaxEditor>,
+ stmts: impl IntoIterator<Item = ast::Stmt>,
+ tail_expr: Option<ast::Expr>,
+ ) -> ast::BlockExpr {
+ let stmts = stmts.into_iter().collect_vec();
+ let input = stmts.iter().map(|it| it.syntax().clone()).collect_vec();
+
+ let ast = make::block_expr(stmts, tail_expr.clone()).clone_for_update();
+
+ if let Some((editor, stmt_list)) = editor.zip(ast.stmt_list()) {
+ let mut mapping = SyntaxMappingBuilder::new(stmt_list.syntax().clone());
+
+ mapping.map_children(
+ input.into_iter(),
+ stmt_list.statements().map(|it| it.syntax().clone()),
+ );
+
+ if let Some((input, output)) = tail_expr.zip(stmt_list.tail_expr()) {
+ mapping.map_node(input.syntax().clone(), output.syntax().clone());
+ }
+
+ mapping.finish(editor);
+ }
+
+ ast
+ }
+
+ #[test]
+ fn basic_usage() {
+ let root = make::match_arm(
+ [make::wildcard_pat().into()],
+ None,
+ make::expr_tuple([
+ make::expr_bin_op(
+ make::expr_literal("2").into(),
+ ast::BinaryOp::ArithOp(ast::ArithOp::Add),
+ make::expr_literal("2").into(),
+ ),
+ make::expr_literal("true").into(),
+ ]),
+ );
+
+ let to_wrap = root.syntax().descendants().find_map(ast::TupleExpr::cast).unwrap();
+ let to_replace = root.syntax().descendants().find_map(ast::BinExpr::cast).unwrap();
+
+ let mut editor = SyntaxEditor::new(root.syntax().clone());
+
+ let name = make::name("var_name");
+ let name_ref = make::name_ref("var_name").clone_for_update();
+
+ let placeholder_snippet = SyntaxAnnotation::new();
+ editor.add_annotation(name.syntax(), placeholder_snippet);
+ editor.add_annotation(name_ref.syntax(), placeholder_snippet);
+
+ let make_ident_pat = make_ident_pat(Some(&mut editor), false, false, name);
+ let make_let_stmt = make_let_stmt(
+ Some(&mut editor),
+ make_ident_pat.into(),
+ None,
+ Some(to_replace.clone().into()),
+ );
+ let new_block = make_block_expr(
+ Some(&mut editor),
+ [make_let_stmt.into()],
+ Some(to_wrap.clone().into()),
+ );
+
+ editor.replace(to_replace.syntax(), name_ref.syntax());
+ editor.replace(to_wrap.syntax(), new_block.syntax());
+
+ let edit = editor.finish();
+
+ let expect = expect![[r#"
+ _ => {
+ let var_name = 2 + 2;
+ (var_name, true)
+ }"#]];
+ expect.assert_eq(&edit.new_root.to_string());
+
+ assert_eq!(edit.find_annotation(placeholder_snippet).len(), 2);
+ assert!(edit
+ .annotations
+ .iter()
+ .flat_map(|(_, elements)| elements)
+ .all(|element| element.ancestors().any(|it| &it == edit.new_root())))
+ }
+
+ #[test]
+ fn test_insert_independent() {
+ let root = make::block_expr(
+ [make::let_stmt(
+ make::ext::simple_ident_pat(make::name("second")).into(),
+ None,
+ Some(make::expr_literal("2").into()),
+ )
+ .into()],
+ None,
+ );
+
+ let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap();
+
+ let mut editor = SyntaxEditor::new(root.syntax().clone());
+
+ editor.insert(
+ Position::first_child_of(root.stmt_list().unwrap().syntax()),
+ make_let_stmt(
+ None,
+ make::ext::simple_ident_pat(make::name("first")).into(),
+ None,
+ Some(make::expr_literal("1").into()),
+ )
+ .syntax(),
+ );
+
+ editor.insert(
+ Position::after(second_let.syntax()),
+ make_let_stmt(
+ None,
+ make::ext::simple_ident_pat(make::name("third")).into(),
+ None,
+ Some(make::expr_literal("3").into()),
+ )
+ .syntax(),
+ );
+
+ let edit = editor.finish();
+
+ let expect = expect![[r#"
+ let first = 1;{
+ let second = 2;let third = 3;
+ }"#]];
+ expect.assert_eq(&edit.new_root.to_string());
+ }
+
+ #[test]
+ fn test_insert_dependent() {
+ let root = make::block_expr(
+ [],
+ Some(
+ make::block_expr(
+ [make::let_stmt(
+ make::ext::simple_ident_pat(make::name("second")).into(),
+ None,
+ Some(make::expr_literal("2").into()),
+ )
+ .into()],
+ None,
+ )
+ .into(),
+ ),
+ );
+
+ let inner_block =
+ root.syntax().descendants().flat_map(ast::BlockExpr::cast).nth(1).unwrap();
+ let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap();
+
+ let mut editor = SyntaxEditor::new(root.syntax().clone());
+
+ let new_block_expr =
+ make_block_expr(Some(&mut editor), [], Some(ast::Expr::BlockExpr(inner_block.clone())));
+
+ let first_let = make_let_stmt(
+ Some(&mut editor),
+ make::ext::simple_ident_pat(make::name("first")).into(),
+ None,
+ Some(make::expr_literal("1").into()),
+ );
+
+ let third_let = make_let_stmt(
+ Some(&mut editor),
+ make::ext::simple_ident_pat(make::name("third")).into(),
+ None,
+ Some(make::expr_literal("3").into()),
+ );
+
+ editor.insert(
+ Position::first_child_of(inner_block.stmt_list().unwrap().syntax()),
+ first_let.syntax(),
+ );
+ editor.insert(Position::after(second_let.syntax()), third_let.syntax());
+ editor.replace(inner_block.syntax(), new_block_expr.syntax());
+
+ let edit = editor.finish();
+
+ let expect = expect![[r#"
+ {
+ {
+ let first = 1;{
+ let second = 2;let third = 3;
+ }
+ }
+ }"#]];
+ expect.assert_eq(&edit.new_root.to_string());
+ }
+
+ #[test]
+ fn test_replace_root_with_dependent() {
+ let root = make::block_expr(
+ [make::let_stmt(
+ make::ext::simple_ident_pat(make::name("second")).into(),
+ None,
+ Some(make::expr_literal("2").into()),
+ )
+ .into()],
+ None,
+ );
+
+ let inner_block = root.clone();
+
+ let mut editor = SyntaxEditor::new(root.syntax().clone());
+
+ let new_block_expr =
+ make_block_expr(Some(&mut editor), [], Some(ast::Expr::BlockExpr(inner_block.clone())));
+
+ let first_let = make_let_stmt(
+ Some(&mut editor),
+ make::ext::simple_ident_pat(make::name("first")).into(),
+ None,
+ Some(make::expr_literal("1").into()),
+ );
+
+ editor.insert(
+ Position::first_child_of(inner_block.stmt_list().unwrap().syntax()),
+ first_let.syntax(),
+ );
+ editor.replace(inner_block.syntax(), new_block_expr.syntax());
+
+ let edit = editor.finish();
+
+ let expect = expect![[r#"
+ {
+ let first = 1;{
+ let second = 2;
+ }
+ }"#]];
+ expect.assert_eq(&edit.new_root.to_string());
+ }
+}
diff --git a/crates/syntax/src/syntax_editor/edit_algo.rs b/crates/syntax/src/syntax_editor/edit_algo.rs
new file mode 100644
index 0000000000..b769c94110
--- /dev/null
+++ b/crates/syntax/src/syntax_editor/edit_algo.rs
@@ -0,0 +1,367 @@
+//! Implementation of applying changes to a syntax tree.
+
+use std::{cmp::Ordering, collections::VecDeque, ops::RangeInclusive};
+
+use rowan::TextRange;
+use rustc_hash::FxHashMap;
+
+use crate::{
+ syntax_editor::{mapping::MissingMapping, Change, ChangeKind, PositionRepr},
+ SyntaxElement, SyntaxNode, SyntaxNodePtr,
+};
+
+use super::{SyntaxEdit, SyntaxEditor};
+
+pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit {
+ // Algorithm overview:
+ //
+ // - Sort changes by (range, type)
+ // - Ensures that parent edits are before child edits
+ // - Ensures that inserts will be guaranteed to be inserted at the right range
+ // - Validate changes
+ // - Checking for invalid changes is easy since the changes will be sorted by range
+ // - Fixup change targets
+ // - standalone change? map to original syntax tree
+ // - dependent change?
+ // - try to map to parent change (either independent or another dependent)
+ // - note: need to keep track of a parent change stack, since a change can be a parent of multiple changes
+ // - Apply changes
+ // - find changes to apply to real tree by applying nested changes first
+ // - changed nodes become part of the changed node set (useful for the formatter to only change those parts)
+ // - Propagate annotations
+
+ let SyntaxEditor { root, mut changes, mappings, annotations } = editor;
+
+ let mut node_depths = FxHashMap::<SyntaxNode, usize>::default();
+ let mut get_node_depth = |node: SyntaxNode| {
+ *node_depths.entry(node).or_insert_with_key(|node| node.ancestors().count())
+ };
+
+ // Sort changes by range, then depth, then change kind, so that we can:
+ // - ensure that parent edits are ordered before child edits
+ // - ensure that inserts will be guaranteed to be inserted at the right range
+ // - easily check for disjoint replace ranges
+ changes.sort_by(|a, b| {
+ a.target_range()
+ .start()
+ .cmp(&b.target_range().start())
+ .then_with(|| {
+ let a_target = a.target_parent();
+ let b_target = b.target_parent();
+
+ if a_target == b_target {
+ return Ordering::Equal;
+ }
+
+ get_node_depth(a_target).cmp(&get_node_depth(b_target))
+ })
+ .then(a.change_kind().cmp(&b.change_kind()))
+ });
+
+ let disjoint_replaces_ranges = changes
+ .iter()
+ .zip(changes.iter().skip(1))
+ .filter(|(l, r)| {
+ // We only care about checking for disjoint replace ranges
+ 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())
+ || l.target_range().intersect(r.target_range()).is_none()
+ });
+
+ if stdx::never!(
+ !disjoint_replaces_ranges,
+ "some replace change ranges intersect: {:?}",
+ changes
+ ) {
+ return SyntaxEdit {
+ old_root: root.clone(),
+ new_root: root,
+ annotations: Default::default(),
+ changed_elements: vec![],
+ };
+ }
+
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+ struct DependentChange {
+ parent: u32,
+ child: u32,
+ }
+
+ // Build change tree
+ let mut changed_ancestors: VecDeque<ChangedAncestor> = VecDeque::new();
+ let mut dependent_changes = vec![];
+ let mut independent_changes = vec![];
+
+ for (change_index, change) in changes.iter().enumerate() {
+ // Check if this change is dependent on another change (i.e. it's contained within another range)
+ if let Some(index) = changed_ancestors
+ .iter()
+ .rev()
+ .position(|ancestor| ancestor.affected_range().contains_range(change.target_range()))
+ {
+ // 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 {
+ parent: ancestor.change_index as u32,
+ child: change_index as u32,
+ });
+ } else {
+ // This change is independent of any other change
+
+ // Drain the changed ancestors since we're no longer in a set of dependent changes
+ changed_ancestors.drain(..);
+
+ independent_changes.push(change_index as u32);
+ }
+
+ // Add to changed ancestors, if applicable
+ match change {
+ Change::Insert(_, _) | Change::InsertAll(_, _) => {}
+ 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))
+ }
+ }
+ }
+
+ // Map change targets to the correct syntax nodes
+ let tree_mutator = TreeMutator::new(&root);
+ let mut changed_elements = vec![];
+
+ for index in independent_changes {
+ match &mut changes[index as usize] {
+ Change::Insert(target, _) | Change::InsertAll(target, _) => {
+ match &mut target.repr {
+ PositionRepr::FirstChild(parent) => {
+ *parent = tree_mutator.make_syntax_mut(parent);
+ }
+ PositionRepr::After(child) => {
+ *child = tree_mutator.make_element_mut(child);
+ }
+ };
+ }
+ 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
+ match &changes[index as usize] {
+ Change::Insert(_, element) => changed_elements.push(element.clone()),
+ 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()),
+ }
+ }
+
+ for DependentChange { parent, child } in dependent_changes.into_iter() {
+ let (input_ancestor, output_ancestor) = match &changes[parent as usize] {
+ // No change will depend on an insert since changes can only depend on nodes in the root tree
+ Change::Insert(_, _) | Change::InsertAll(_, _) => unreachable!(),
+ Change::Replace(target, Some(new_target)) => {
+ (to_owning_node(target), to_owning_node(new_target))
+ }
+ // 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| {
+ match mappings.upmap_child(target, &input_ancestor, &output_ancestor) {
+ Ok(it) => it,
+ Err(MissingMapping(current)) => unreachable!("no mappings exist between {current:?} (ancestor of {input_ancestor:?}) and {output_ancestor:?}"),
+ }
+ };
+
+ let upmap_target = |target: &SyntaxElement| {
+ match mappings.upmap_child_element(target, &input_ancestor, &output_ancestor) {
+ Ok(it) => it,
+ Err(MissingMapping(current)) => unreachable!("no mappings exist between {current:?} (ancestor of {input_ancestor:?}) and {output_ancestor:?}"),
+ }
+ };
+
+ match &mut changes[child as usize] {
+ Change::Insert(target, _) | Change::InsertAll(target, _) => match &mut target.repr {
+ PositionRepr::FirstChild(parent) => {
+ *parent = upmap_target_node(parent);
+ }
+ PositionRepr::After(child) => {
+ *child = upmap_target(child);
+ }
+ },
+ Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => {
+ *target = upmap_target(target);
+ }
+ Change::ReplaceAll(range, _) => {
+ *range = upmap_target(range.start())..=upmap_target(range.end());
+ }
+ }
+ }
+
+ // Apply changes
+ let mut root = tree_mutator.mutable_clone;
+
+ for change in changes {
+ match change {
+ Change::Insert(position, element) => {
+ let (parent, index) = position.place();
+ parent.splice_children(index..index, vec![element]);
+ }
+ Change::InsertAll(position, elements) => {
+ let (parent, index) = position.place();
+ parent.splice_children(index..index, elements);
+ }
+ Change::Replace(target, None) => {
+ target.detach();
+ }
+ Change::Replace(SyntaxElement::Node(target), Some(new_target)) if target == root => {
+ root = new_target.into_node().expect("root node replacement should be a node");
+ }
+ Change::Replace(target, Some(new_target)) => {
+ 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);
+ }
+ }
+ }
+
+ // Propagate annotations
+ let annotations = annotations.into_iter().filter_map(|(element, annotation)| {
+ match mappings.upmap_element(&element, &root) {
+ // Needed to follow the new tree to find the resulting element
+ Some(Ok(mapped)) => Some((mapped, annotation)),
+ // Element did not need to be mapped
+ None => Some((element, annotation)),
+ // Element did not make it to the final tree
+ Some(Err(_)) => None,
+ }
+ });
+
+ let mut annotation_groups = FxHashMap::default();
+
+ for (element, annotation) in annotations {
+ annotation_groups.entry(annotation).or_insert(vec![]).push(element);
+ }
+
+ SyntaxEdit {
+ old_root: tree_mutator.immutable,
+ new_root: root,
+ changed_elements,
+ annotations: annotation_groups,
+ }
+}
+
+fn to_owning_node(element: &SyntaxElement) -> SyntaxNode {
+ match element {
+ SyntaxElement::Node(node) => node.clone(),
+ SyntaxElement::Token(token) => token.parent().unwrap().clone(),
+ }
+}
+
+struct ChangedAncestor {
+ kind: ChangedAncestorKind,
+ change_index: usize,
+}
+
+enum ChangedAncestorKind {
+ Single { node: SyntaxNode },
+ Range { _changed_elements: RangeInclusive<SyntaxElement>, _in_parent: SyntaxNode },
+}
+
+impl ChangedAncestor {
+ fn single(element: &SyntaxElement, change_index: usize) -> Self {
+ let kind = match element {
+ SyntaxElement::Node(node) => ChangedAncestorKind::Single { node: node.clone() },
+ SyntaxElement::Token(token) => {
+ ChangedAncestorKind::Single { node: token.parent().unwrap() }
+ }
+ };
+
+ Self { kind, change_index }
+ }
+
+ fn multiple(range: &RangeInclusive<SyntaxElement>, 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_elements: changed_nodes, _in_parent: _ } => {
+ TextRange::new(
+ changed_nodes.start().text_range().start(),
+ changed_nodes.end().text_range().end(),
+ )
+ }
+ }
+ }
+}
+
+struct TreeMutator {
+ immutable: SyntaxNode,
+ mutable_clone: SyntaxNode,
+}
+
+impl TreeMutator {
+ fn new(immutable: &SyntaxNode) -> TreeMutator {
+ let immutable = immutable.clone();
+ let mutable_clone = immutable.clone_for_update();
+ TreeMutator { immutable, mutable_clone }
+ }
+
+ fn make_element_mut(&self, element: &SyntaxElement) -> SyntaxElement {
+ match element {
+ SyntaxElement::Node(node) => SyntaxElement::Node(self.make_syntax_mut(node)),
+ SyntaxElement::Token(token) => {
+ let parent = self.make_syntax_mut(&token.parent().unwrap());
+ parent.children_with_tokens().nth(token.index()).unwrap()
+ }
+ }
+ }
+
+ fn make_syntax_mut(&self, node: &SyntaxNode) -> SyntaxNode {
+ let ptr = SyntaxNodePtr::new(node);
+ ptr.to_node(&self.mutable_clone)
+ }
+}
diff --git a/crates/syntax/src/syntax_editor/mapping.rs b/crates/syntax/src/syntax_editor/mapping.rs
new file mode 100644
index 0000000000..9bb5e6d933
--- /dev/null
+++ b/crates/syntax/src/syntax_editor/mapping.rs
@@ -0,0 +1,272 @@
+//! Maps syntax elements through disjoint syntax nodes.
+//!
+//! [`SyntaxMappingBuilder`] should be used to create mappings to add to a [`SyntaxEditor`]
+
+use itertools::Itertools;
+use rustc_hash::FxHashMap;
+
+use crate::{SyntaxElement, SyntaxNode};
+
+use super::SyntaxEditor;
+
+#[derive(Debug, Default)]
+pub struct SyntaxMapping {
+ // important information to keep track of:
+ // node -> node
+ // token -> token (implicit in mappings)
+ // input parent -> output parent (for deep lookups)
+
+ // mappings -> parents
+ entry_parents: Vec<SyntaxNode>,
+ node_mappings: FxHashMap<SyntaxNode, MappingEntry>,
+}
+
+impl SyntaxMapping {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Like [`SyntaxMapping::upmap_child`] but for syntax elements.
+ pub fn upmap_child_element(
+ &self,
+ child: &SyntaxElement,
+ input_ancestor: &SyntaxNode,
+ output_ancestor: &SyntaxNode,
+ ) -> Result<SyntaxElement, MissingMapping> {
+ match child {
+ SyntaxElement::Node(node) => {
+ self.upmap_child(node, input_ancestor, output_ancestor).map(SyntaxElement::Node)
+ }
+ SyntaxElement::Token(token) => {
+ let upmap_parent =
+ self.upmap_child(&token.parent().unwrap(), input_ancestor, output_ancestor)?;
+
+ let element = upmap_parent.children_with_tokens().nth(token.index()).unwrap();
+ debug_assert!(
+ element.as_token().is_some_and(|it| it.kind() == token.kind()),
+ "token upmapping mapped to the wrong node ({token:?} -> {element:?})"
+ );
+
+ Ok(element)
+ }
+ }
+ }
+
+ /// Maps a child node of the input ancestor to the corresponding node in
+ /// the output ancestor.
+ pub fn upmap_child(
+ &self,
+ child: &SyntaxNode,
+ input_ancestor: &SyntaxNode,
+ output_ancestor: &SyntaxNode,
+ ) -> Result<SyntaxNode, MissingMapping> {
+ debug_assert!(
+ child == input_ancestor
+ || child.ancestors().any(|ancestor| &ancestor == input_ancestor)
+ );
+
+ // Build a list mapping up to the first mappable ancestor
+ let to_first_upmap = if child != input_ancestor {
+ std::iter::successors(Some((child.index(), child.clone())), |(_, current)| {
+ let parent = current.parent().unwrap();
+
+ if &parent == input_ancestor {
+ return None;
+ }
+
+ Some((parent.index(), parent))
+ })
+ .map(|(i, _)| i)
+ .collect::<Vec<_>>()
+ } else {
+ vec![]
+ };
+
+ // Progressively up-map the input ancestor until we get to the output ancestor
+ let to_output_ancestor = if input_ancestor != output_ancestor {
+ self.upmap_to_ancestor(input_ancestor, output_ancestor)?
+ } else {
+ vec![]
+ };
+
+ let to_map_down =
+ to_output_ancestor.into_iter().rev().chain(to_first_upmap.into_iter().rev());
+
+ let mut target = output_ancestor.clone();
+
+ for index in to_map_down {
+ target = target
+ .children_with_tokens()
+ .nth(index)
+ .and_then(|it| it.into_node())
+ .expect("equivalent ancestor node should be present in target tree");
+ }
+
+ debug_assert_eq!(child.kind(), target.kind());
+
+ Ok(target)
+ }
+
+ fn upmap_to_ancestor(
+ &self,
+ input_ancestor: &SyntaxNode,
+ output_ancestor: &SyntaxNode,
+ ) -> Result<Vec<usize>, MissingMapping> {
+ let mut current =
+ self.upmap_node_single(input_ancestor).unwrap_or_else(|| input_ancestor.clone());
+ let mut upmap_chain = vec![current.index()];
+
+ loop {
+ let Some(parent) = current.parent() else { break };
+
+ if &parent == output_ancestor {
+ return Ok(upmap_chain);
+ }
+
+ current = match self.upmap_node_single(&parent) {
+ Some(next) => next,
+ None => parent,
+ };
+ upmap_chain.push(current.index());
+ }
+
+ Err(MissingMapping(current))
+ }
+
+ pub fn upmap_element(
+ &self,
+ input: &SyntaxElement,
+ output_root: &SyntaxNode,
+ ) -> Option<Result<SyntaxElement, MissingMapping>> {
+ match input {
+ SyntaxElement::Node(node) => {
+ Some(self.upmap_node(node, output_root)?.map(SyntaxElement::Node))
+ }
+ SyntaxElement::Token(token) => {
+ let upmap_parent = match self.upmap_node(&token.parent().unwrap(), output_root)? {
+ Ok(it) => it,
+ Err(err) => return Some(Err(err)),
+ };
+
+ let element = upmap_parent.children_with_tokens().nth(token.index()).unwrap();
+ debug_assert!(
+ element.as_token().is_some_and(|it| it.kind() == token.kind()),
+ "token upmapping mapped to the wrong node ({token:?} -> {element:?})"
+ );
+
+ Some(Ok(element))
+ }
+ }
+ }
+
+ pub fn upmap_node(
+ &self,
+ input: &SyntaxNode,
+ output_root: &SyntaxNode,
+ ) -> Option<Result<SyntaxNode, MissingMapping>> {
+ // Try to follow the mapping tree, if it exists
+ let input_mapping = self.upmap_node_single(input);
+ let input_ancestor =
+ input.ancestors().find_map(|ancestor| self.upmap_node_single(&ancestor));
+
+ match (input_mapping, input_ancestor) {
+ (Some(input_mapping), _) => {
+ // A mapping exists at the input, follow along the tree
+ Some(self.upmap_child(&input_mapping, &input_mapping, output_root))
+ }
+ (None, Some(input_ancestor)) => {
+ // A mapping exists at an ancestor, follow along the tree
+ Some(self.upmap_child(input, &input_ancestor, output_root))
+ }
+ (None, None) => {
+ // No mapping exists at all, is the same position in the final tree
+ None
+ }
+ }
+ }
+
+ pub fn merge(&mut self, mut other: SyntaxMapping) {
+ // Remap other's entry parents to be after the current list of entry parents
+ let remap_base: u32 = self.entry_parents.len().try_into().unwrap();
+
+ self.entry_parents.append(&mut other.entry_parents);
+ self.node_mappings.extend(other.node_mappings.into_iter().map(|(node, entry)| {
+ (node, MappingEntry { parent: entry.parent + remap_base, ..entry })
+ }));
+ }
+
+ /// Follows the input one step along the syntax mapping tree
+ fn upmap_node_single(&self, input: &SyntaxNode) -> Option<SyntaxNode> {
+ let MappingEntry { parent, child_slot } = self.node_mappings.get(input)?;
+
+ let output = self.entry_parents[*parent as usize]
+ .children_with_tokens()
+ .nth(*child_slot as usize)
+ .and_then(SyntaxElement::into_node)
+ .unwrap();
+
+ debug_assert_eq!(input.kind(), output.kind());
+ Some(output)
+ }
+
+ fn add_mapping(&mut self, syntax_mapping: SyntaxMappingBuilder) {
+ let SyntaxMappingBuilder { parent_node, node_mappings } = syntax_mapping;
+
+ let parent_entry: u32 = self.entry_parents.len().try_into().unwrap();
+ self.entry_parents.push(parent_node);
+
+ let node_entries = node_mappings
+ .into_iter()
+ .map(|(node, slot)| (node, MappingEntry { parent: parent_entry, child_slot: slot }));
+
+ self.node_mappings.extend(node_entries);
+ }
+}
+
+#[derive(Debug)]
+pub struct SyntaxMappingBuilder {
+ parent_node: SyntaxNode,
+ node_mappings: Vec<(SyntaxNode, u32)>,
+}
+
+impl SyntaxMappingBuilder {
+ pub fn new(parent_node: SyntaxNode) -> Self {
+ Self { parent_node, node_mappings: vec![] }
+ }
+
+ pub fn map_node(&mut self, input: SyntaxNode, output: SyntaxNode) {
+ debug_assert_eq!(output.parent().as_ref(), Some(&self.parent_node));
+ self.node_mappings.push((input, output.index() as u32));
+ }
+
+ pub fn map_children(
+ &mut self,
+ input: impl Iterator<Item = SyntaxNode>,
+ output: impl Iterator<Item = SyntaxNode>,
+ ) {
+ for pairs in input.zip_longest(output) {
+ let (input, output) = match pairs {
+ itertools::EitherOrBoth::Both(l, r) => (l, r),
+ itertools::EitherOrBoth::Left(_) => {
+ unreachable!("mapping more input nodes than there are output nodes")
+ }
+ itertools::EitherOrBoth::Right(_) => break,
+ };
+
+ self.map_node(input, output);
+ }
+ }
+
+ pub fn finish(self, editor: &mut SyntaxEditor) {
+ editor.mappings.add_mapping(self);
+ }
+}
+
+#[derive(Debug)]
+pub struct MissingMapping(pub SyntaxNode);
+
+#[derive(Debug, Clone, Copy)]
+struct MappingEntry {
+ parent: u32,
+ child_slot: u32,
+}