Auto merge of #18032 - DropDemBits:sed-tree-edits, r=davidbarsky
internal: Add preliminary `SyntaxEditor` functionality Related to #15710 Implements a `SyntaxEditor` interface to abstract over the details of modifying syntax trees, to both simplify creating new code fixes and code actions, as well as start on the path of getting rid of mutable syntax nodes. `SyntaxEditor` relies on `SyntaxMappingBuilder`s to feed in the correct information to map AST nodes created by `make` constructors, as `make` constructors do not guarantee that node identity is preserved. This is to paper over the fact that `make` constructors simply re-parse text input instead of building AST nodes from the ground up and re-using the provided syntax nodes. `SyntaxAnnotation`s are used to find where syntax elements have ended up after edits are applied. This is primarily useful for the `add_{placeholder,tabstop}` set of methods on `SourceChangeBuilder`, as that currently relies on the nodes provided being in the final syntax tree. Eventually, the goal should be to move this into the `rowan` crate when we move away from mutable syntax nodes, but for now it'll stay in the `syntax` crate. --- Closes #14921 as `SyntaxEditor` ensures that all replace changes are disjoint Closes #9649 by implementing `SyntaxAnnotation`s
This commit is contained in:
commit
2053d7d23e
@ -40,6 +40,7 @@
|
||||
#[doc(hidden)]
|
||||
pub mod fuzz;
|
||||
pub mod hacks;
|
||||
pub mod syntax_editor;
|
||||
pub mod ted;
|
||||
pub mod utils;
|
||||
|
||||
|
611
src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs
Normal file
611
src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs
Normal file
@ -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());
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
Loading…
Reference in New Issue
Block a user