Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-assists/src/handlers/generate_delegate_methods.rs1
-rw-r--r--crates/ide-assists/src/handlers/remove_unused_imports.rs17
-rw-r--r--crates/ide-db/src/imports/merge_imports.rs5
-rw-r--r--crates/ide-db/src/source_change.rs77
-rw-r--r--crates/syntax/src/ast/edit_in_place.rs65
-rw-r--r--crates/syntax/src/ast/node_ext.rs13
-rw-r--r--crates/syntax/src/syntax_editor.rs42
7 files changed, 103 insertions, 117 deletions
diff --git a/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/crates/ide-assists/src/handlers/generate_delegate_methods.rs
index b5c6aec0bb..6c9808fb1c 100644
--- a/crates/ide-assists/src/handlers/generate_delegate_methods.rs
+++ b/crates/ide-assists/src/handlers/generate_delegate_methods.rs
@@ -213,7 +213,6 @@ pub(crate) fn generate_delegate_methods(
let impl_def = impl_def.indent(indent);
// Insert the impl block.
- let strukt = edit.make_mut(strukt.clone());
editor.insert_all(
Position::after(strukt.syntax()),
vec![
diff --git a/crates/ide-assists/src/handlers/remove_unused_imports.rs b/crates/ide-assists/src/handlers/remove_unused_imports.rs
index c90623ceed..2958acc478 100644
--- a/crates/ide-assists/src/handlers/remove_unused_imports.rs
+++ b/crates/ide-assists/src/handlers/remove_unused_imports.rs
@@ -111,19 +111,24 @@ pub(crate) fn remove_unused_imports(acc: &mut Assists, ctx: &AssistContext<'_, '
is_path_per_ns_unused_in_scope(ctx, &u, scope, &res).then_some(u)
}
})
- .peekable();
+ .collect::<Vec<_>>();
- // Peek so we terminate early if an unused use is found. Only do the rest of the work if the user selects the assist.
- if unused.peek().is_some() {
+ // Terminate early unless an unused use is found. Only do the rest of the work if the user selects the assist.
+ if !unused.is_empty() {
acc.add(
AssistId::quick_fix("remove_unused_imports"),
"Remove all unused imports",
selected_el.text_range(),
|builder| {
- let unused: Vec<ast::UseTree> = unused.map(|x| builder.make_mut(x)).collect();
- for node in unused {
- node.remove_recursive();
+ let editor = builder.make_editor(&selected_el);
+ unused.sort_by_key(|use_tree| use_tree.syntax().text_range().start());
+ for node in &unused {
+ editor.delete(node.syntax());
}
+ for node in unused.iter().cloned() {
+ node.remove_recursive(&editor);
+ }
+ builder.add_file_edits(ctx.vfs_file_id(), editor);
},
)
} else {
diff --git a/crates/ide-db/src/imports/merge_imports.rs b/crates/ide-db/src/imports/merge_imports.rs
index 76645464dd..bbd351a4cc 100644
--- a/crates/ide-db/src/imports/merge_imports.rs
+++ b/crates/ide-db/src/imports/merge_imports.rs
@@ -256,10 +256,7 @@ pub fn try_normalize_import(use_item: &ast::Use, style: NormalizationStyle) -> O
Some(use_item)
}
-pub fn try_normalize_use_tree_mut(
- use_tree: &ast::UseTree,
- style: NormalizationStyle,
-) -> Option<()> {
+fn try_normalize_use_tree_mut(use_tree: &ast::UseTree, style: NormalizationStyle) -> Option<()> {
if style == NormalizationStyle::One {
let mut modified = false;
modified |= use_tree.wrap_in_tree_list().is_some();
diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs
index 3c5f703712..766d7251a1 100644
--- a/crates/ide-db/src/source_change.rs
+++ b/crates/ide-db/src/source_change.rs
@@ -5,7 +5,6 @@
use std::{collections::hash_map::Entry, fmt, iter, mem};
-use crate::imports::insert_use::{ImportScope, ImportScopeKind};
use crate::text_edit::{TextEdit, TextEditBuilder};
use crate::{SnippetCap, assists::Command, syntax_helpers::tree_diff::diff};
use base_db::AnchoredPathBuf;
@@ -16,7 +15,7 @@ use rustc_hash::FxHashMap;
use span::FileId;
use stdx::never;
use syntax::{
- AstNode, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize,
+ AstNode, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextSize,
syntax_editor::{SyntaxAnnotation, SyntaxEditor},
};
@@ -245,23 +244,6 @@ pub struct SnippetBuilder {
places: Vec<PlaceSnippet>,
}
-impl TreeMutator {
- fn new(immutable: &SyntaxNode) -> TreeMutator {
- let immutable = immutable.ancestors().last().unwrap();
- let mutable_clone = immutable.clone_for_update();
- TreeMutator { immutable, mutable_clone }
- }
-
- fn make_mut<N: AstNode>(&self, node: &N) -> N {
- N::cast(self.make_syntax_mut(node.syntax())).unwrap()
- }
-
- fn make_syntax_mut(&self, node: &SyntaxNode) -> SyntaxNode {
- let ptr = SyntaxNodePtr::new(node);
- ptr.to_node(&self.mutable_clone)
- }
-}
-
impl SourceChangeBuilder {
pub fn new(file_id: impl Into<FileId>) -> SourceChangeBuilder {
SourceChangeBuilder {
@@ -366,34 +348,6 @@ impl SourceChangeBuilder {
}
}
- pub fn make_mut<N: AstNode>(&mut self, node: N) -> N {
- self.mutated_tree.get_or_insert_with(|| TreeMutator::new(node.syntax())).make_mut(&node)
- }
-
- pub fn make_import_scope_mut(&mut self, scope: ImportScope) -> ImportScope {
- ImportScope {
- kind: match scope.kind.clone() {
- ImportScopeKind::File(it) => ImportScopeKind::File(self.make_mut(it)),
- ImportScopeKind::Module(it) => ImportScopeKind::Module(self.make_mut(it)),
- ImportScopeKind::Block(it) => ImportScopeKind::Block(self.make_mut(it)),
- },
- required_cfgs: scope.required_cfgs.iter().map(|it| self.make_mut(it.clone())).collect(),
- }
- }
- /// Returns a copy of the `node`, suitable for mutation.
- ///
- /// Syntax trees in rust-analyzer are typically immutable, and mutating
- /// operations panic at runtime. However, it is possible to make a copy of
- /// the tree and mutate the copy freely. Mutation is based on interior
- /// mutability, and different nodes in the same tree see the same mutations.
- ///
- /// The typical pattern for an assist is to find specific nodes in the read
- /// phase, and then get their mutable counterparts using `make_mut` in the
- /// mutable state.
- pub fn make_syntax_mut(&mut self, node: SyntaxNode) -> SyntaxNode {
- self.mutated_tree.get_or_insert_with(|| TreeMutator::new(&node)).make_syntax_mut(&node)
- }
-
/// Remove specified `range` of text.
pub fn delete(&mut self, range: TextRange) {
self.edit.delete(range)
@@ -434,12 +388,6 @@ impl SourceChangeBuilder {
self.add_snippet(PlaceSnippet::Before(node.syntax().clone().into()));
}
- /// Adds a tabstop snippet to place the cursor after `node`
- pub fn add_tabstop_after(&mut self, _cap: SnippetCap, node: impl AstNode) {
- assert!(node.syntax().parent().is_some());
- self.add_snippet(PlaceSnippet::After(node.syntax().clone().into()));
- }
-
/// Adds a tabstop snippet to place the cursor before `token`
pub fn add_tabstop_before_token(&mut self, _cap: SnippetCap, token: SyntaxToken) {
assert!(token.parent().is_some());
@@ -458,23 +406,6 @@ impl SourceChangeBuilder {
self.add_snippet(PlaceSnippet::Over(node.syntax().clone().into()))
}
- /// Adds a snippet to move the cursor selected over `token`
- pub fn add_placeholder_snippet_token(&mut self, _cap: SnippetCap, token: SyntaxToken) {
- assert!(token.parent().is_some());
- self.add_snippet(PlaceSnippet::Over(token.into()))
- }
-
- /// Adds a snippet to move the cursor selected over `nodes`
- ///
- /// This allows for renaming newly generated items without having to go
- /// through a separate rename step.
- pub fn add_placeholder_snippet_group(&mut self, _cap: SnippetCap, nodes: Vec<SyntaxNode>) {
- assert!(nodes.iter().all(|node| node.parent().is_some()));
- self.add_snippet(PlaceSnippet::OverGroup(
- nodes.into_iter().map(|node| node.into()).collect(),
- ))
- }
-
fn add_snippet(&mut self, snippet: PlaceSnippet) {
let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] });
snippet_builder.places.push(snippet);
@@ -553,9 +484,6 @@ enum PlaceSnippet {
After(SyntaxElement),
/// Place a placeholder snippet in place of the element
Over(SyntaxElement),
- /// Place a group of placeholder snippets which are linked together
- /// in place of the elements
- OverGroup(Vec<SyntaxElement>),
}
impl PlaceSnippet {
@@ -564,9 +492,6 @@ impl PlaceSnippet {
PlaceSnippet::Before(it) => vec![Snippet::Tabstop(it.text_range().start())],
PlaceSnippet::After(it) => vec![Snippet::Tabstop(it.text_range().end())],
PlaceSnippet::Over(it) => vec![Snippet::Placeholder(it.text_range())],
- PlaceSnippet::OverGroup(it) => {
- vec![Snippet::PlaceholderGroup(it.into_iter().map(|it| it.text_range()).collect())]
- }
}
}
}
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
index 9f12f94ce1..4a8c9d450c 100644
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ b/crates/syntax/src/ast/edit_in_place.rs
@@ -56,24 +56,41 @@ impl Removable for ast::UseTree {
}
impl ast::UseTree {
+ /// Editor variant of UseTree remove
+ fn remove_with_editor(&self, editor: &SyntaxEditor) {
+ for dir in [Direction::Next, Direction::Prev] {
+ if let Some(next_use_tree) = neighbor(self, dir) {
+ let separators = self
+ .syntax()
+ .siblings_with_tokens(dir)
+ .skip(1)
+ .take_while(|it| it.as_node() != Some(next_use_tree.syntax()));
+ for separator in separators {
+ editor.delete(separator);
+ }
+ break;
+ }
+ }
+ editor.delete(self.syntax());
+ }
+
/// Deletes the usetree node represented by the input. Recursively removes parents, including use nodes that become empty.
- pub fn remove_recursive(self) {
+ pub fn remove_recursive(self, editor: &SyntaxEditor) {
let parent = self.syntax().parent();
- self.remove();
-
if let Some(u) = parent.clone().and_then(ast::Use::cast) {
- if u.use_tree().is_none() {
- u.remove();
- }
+ u.remove(editor);
} else if let Some(u) = parent.and_then(ast::UseTreeList::cast) {
- if u.use_trees().next().is_none() {
- let parent = u.syntax().parent().and_then(ast::UseTree::cast);
- if let Some(u) = parent {
- u.remove_recursive();
- }
+ if u.use_trees().nth(1).is_none()
+ || u.use_trees().all(|use_tree| {
+ use_tree.syntax() == self.syntax() || editor.deleted(use_tree.syntax())
+ })
+ {
+ u.parent_use_tree().remove_recursive(editor);
+ return;
}
- u.remove_unnecessary_braces();
+ self.remove_with_editor(editor);
+ u.remove_unnecessary_braces(editor);
}
}
@@ -224,8 +241,9 @@ impl ast::UseTreeList {
}
}
-impl Removable for ast::Use {
- fn remove(&self) {
+impl ast::Use {
+ fn remove(&self, editor: &SyntaxEditor) {
+ let make = editor.make();
let next_ws = self
.syntax()
.next_sibling_or_token()
@@ -234,10 +252,17 @@ impl Removable for ast::Use {
if let Some(next_ws) = next_ws {
let ws_text = next_ws.syntax().text();
if let Some(rest) = ws_text.strip_prefix('\n') {
- if rest.is_empty() {
- ted::remove(next_ws.syntax());
+ let next_use_removed = next_ws
+ .syntax()
+ .next_sibling_or_token()
+ .and_then(|it| it.into_node())
+ .and_then(ast::Use::cast)
+ .and_then(|use_| use_.use_tree())
+ .is_some_and(|use_tree| editor.deleted(use_tree.syntax()));
+ if rest.is_empty() || next_use_removed {
+ editor.delete(next_ws.syntax());
} else {
- ted::replace(next_ws.syntax(), make::tokens::whitespace(rest));
+ editor.replace(next_ws.syntax(), make.whitespace(rest));
}
}
}
@@ -251,13 +276,13 @@ impl Removable for ast::Use {
let prev_newline = ws_text.rfind('\n').map(|x| x + 1).unwrap_or(0);
let rest = &ws_text[0..prev_newline];
if rest.is_empty() {
- ted::remove(prev_ws.syntax());
+ editor.delete(prev_ws.syntax());
} else {
- ted::replace(prev_ws.syntax(), make::tokens::whitespace(rest));
+ editor.replace(prev_ws.syntax(), make.whitespace(rest));
}
}
- ted::remove(self.syntax());
+ editor.delete(self.syntax());
}
}
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 751f8d7e1c..1eb658f4b8 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -16,7 +16,7 @@ use crate::{
self, AstNode, AstToken, HasAttrs, HasGenericArgs, HasGenericParams, HasName,
HasTypeBounds, SyntaxNode, support,
},
- ted,
+ syntax_editor::SyntaxEditor,
};
use super::{GenericParam, RangeItem, RangeOp};
@@ -454,11 +454,12 @@ impl ast::UseTreeList {
}
/// Remove the unnecessary braces in current `UseTreeList`
- pub fn remove_unnecessary_braces(mut self) {
+ pub fn remove_unnecessary_braces(mut self, editor: &SyntaxEditor) {
// Returns true iff there is a single subtree and it is not the self keyword. The braces in
// `use x::{self};` are necessary and so we should not remove them.
let has_single_subtree_that_is_not_self = |u: &ast::UseTreeList| {
- if let Some((single_subtree,)) = u.use_trees().collect_tuple() {
+ let use_trees = u.use_trees().filter(|use_tree| !editor.deleted(use_tree.syntax()));
+ if let Some((single_subtree,)) = use_trees.collect_tuple() {
// We have a single subtree, check whether it is self.
let is_self = single_subtree.path().as_ref().is_some_and(|path| {
@@ -476,12 +477,12 @@ impl ast::UseTreeList {
let remove_brace_in_use_tree_list = |u: &ast::UseTreeList| {
if has_single_subtree_that_is_not_self(u) {
if let Some(a) = u.l_curly_token() {
- ted::remove(a)
+ editor.delete(a)
}
if let Some(a) = u.r_curly_token() {
- ted::remove(a)
+ editor.delete(a)
}
- u.comma().for_each(ted::remove);
+ u.comma().for_each(|u| editor.delete(u));
}
};
diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs
index 7f499a241b..7d15195c6f 100644
--- a/crates/syntax/src/syntax_editor.rs
+++ b/crates/syntax/src/syntax_editor.rs
@@ -133,7 +133,19 @@ impl SyntaxEditor {
!matches!(&element, SyntaxElement::Node(node) if node == &self.root),
"should not delete root node"
);
- self.changes.borrow_mut().push(Change::Replace(element.syntax_element(), None));
+ let mut changes = self.changes.borrow_mut();
+ for change in changes.iter_mut() {
+ if let Change::Replace(existing, replacement) = change
+ && *existing == element
+ {
+ if replacement.is_none() {
+ return;
+ }
+ *replacement = None;
+ return;
+ }
+ }
+ changes.push(Change::Replace(element, None));
}
pub fn delete_all(&self, range: RangeInclusive<SyntaxElement>) {
@@ -149,9 +161,23 @@ impl SyntaxEditor {
pub fn replace(&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
- .borrow_mut()
- .push(Change::Replace(old.syntax_element(), Some(new.syntax_element())));
+ let new = new.syntax_element();
+ let mut changes = self.changes.borrow_mut();
+ for change in changes.iter_mut() {
+ if let Change::Replace(existing, replacement) = change
+ && *existing == old
+ {
+ match replacement {
+ None => return,
+ Some(existing_new) if *existing_new == new => return,
+ Some(existing_new) => {
+ *existing_new = new;
+ return;
+ }
+ }
+ }
+ }
+ changes.push(Change::Replace(old, Some(new)));
}
pub fn replace_with_many(&self, old: impl Element, new: Vec<SyntaxElement>) {
@@ -177,6 +203,14 @@ impl SyntaxEditor {
pub fn finish(self) -> SyntaxEdit {
edit_algo::apply_edits(self)
}
+
+ pub fn deleted(&self, element: impl Element) -> bool {
+ let element = element.syntax_element();
+ self.changes
+ .borrow()
+ .iter()
+ .any(|change| matches!(change, Change::Replace(existing, None) if *existing == element))
+ }
}
/// Represents a completed [`SyntaxEditor`] operation.