diff --git a/crates/ide-assists/src/handlers/merge_imports.rs b/crates/ide-assists/src/handlers/merge_imports.rs index 417123083690..7eae4359e967 100644 --- a/crates/ide-assists/src/handlers/merge_imports.rs +++ b/crates/ide-assists/src/handlers/merge_imports.rs @@ -6,8 +6,10 @@ use ide_db::imports::{ use itertools::Itertools; use syntax::{ algo::neighbor, - ast::{self, edit_in_place::Removable}, - match_ast, ted, AstNode, SyntaxElement, SyntaxNode, + ast::{self, syntax_factory::SyntaxFactory}, + match_ast, + syntax_editor::edits::Removable, + AstNode, SyntaxElement, SyntaxNode, }; use crate::{ @@ -68,24 +70,30 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio (selection_range, edits?) }; + let parent_node = match ctx.covering_element() { + SyntaxElement::Node(n) => n, + SyntaxElement::Token(t) => t.parent()?, + }; + let make = SyntaxFactory::new(); + acc.add( AssistId("merge_imports", AssistKind::RefactorRewrite), "Merge imports", target, |builder| { - let edits_mut: Vec = edits - .into_iter() - .map(|it| match it { - Remove(Either::Left(it)) => Remove(Either::Left(builder.make_mut(it))), - Remove(Either::Right(it)) => Remove(Either::Right(builder.make_mut(it))), - Replace(old, new) => Replace(builder.make_syntax_mut(old), new), - }) - .collect(); - for edit in edits_mut { + let mut editor = builder.make_editor(&parent_node); + for edit in edits { match edit { - Remove(it) => it.as_ref().either(Removable::remove, Removable::remove), + Remove(it) => { + let node = it.as_ref(); + if let Some(left) = node.left() { + left.remove(&mut editor); + } else if let Some(right) = node.right() { + right.remove(&mut editor); + } + } Replace(old, new) => { - ted::replace(old, &new); + editor.replace(old, &new); // If there's a selection and we're replacing a use tree in a tree list, // normalize the parent use tree if it only contains the merged subtree. @@ -109,12 +117,14 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio }); if let Some((old_tree, new_tree)) = normalized_use_tree { cov_mark::hit!(replace_parent_with_normalized_use_tree); - ted::replace(old_tree.syntax(), new_tree.syntax()); + editor.replace(old_tree.syntax(), new_tree.syntax()); } } } } } + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.file_id(), editor); }, ) } diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs index b82181ae13ad..95a5f76e749c 100644 --- a/crates/syntax/src/syntax_editor.rs +++ b/crates/syntax/src/syntax_editor.rs @@ -16,7 +16,7 @@ use rustc_hash::FxHashMap; use crate::{SyntaxElement, SyntaxNode, SyntaxToken}; mod edit_algo; -mod edits; +pub mod edits; mod mapping; pub use mapping::{SyntaxMapping, SyntaxMappingBuilder}; diff --git a/crates/syntax/src/syntax_editor/edits.rs b/crates/syntax/src/syntax_editor/edits.rs index 450d601615ee..c17b62cba88c 100644 --- a/crates/syntax/src/syntax_editor/edits.rs +++ b/crates/syntax/src/syntax_editor/edits.rs @@ -1,9 +1,10 @@ //! Structural editing for ast using `SyntaxEditor` use crate::{ + algo::neighbor, ast::{ - self, edit::IndentLevel, make, syntax_factory::SyntaxFactory, AstNode, Fn, GenericParam, - HasGenericParams, HasName, + self, edit::IndentLevel, make, syntax_factory::SyntaxFactory, AstNode, AstToken, Fn, + GenericParam, HasGenericParams, HasName, }, syntax_editor::{Position, SyntaxEditor}, Direction, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, T, @@ -143,6 +144,53 @@ fn normalize_ws_between_braces(editor: &mut SyntaxEditor, node: &SyntaxNode) -> Some(()) } +pub trait Removable: AstNode { + fn remove(&self, editor: &mut SyntaxEditor); +} + +impl Removable for ast::Use { + fn remove(&self, editor: &mut SyntaxEditor) { + let make = SyntaxFactory::new(); + + let next_ws = self + .syntax() + .next_sibling_or_token() + .and_then(|it| it.into_token()) + .and_then(ast::Whitespace::cast); + 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() { + editor.delete(next_ws.syntax()); + } else { + editor.replace(next_ws.syntax(), make.whitespace(rest)); + } + } + } + + editor.delete(self.syntax()); + } +} + +impl Removable for ast::UseTree { + fn remove(&self, editor: &mut 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 sep in separators { + editor.delete(sep); + } + break; + } + } + editor.delete(self.syntax()); + } +} + #[cfg(test)] mod tests { use parser::Edition;