use syntax::{ AstNode, SyntaxKind, ast::{self, HasAttrs, HasVisibility, edit::IndentLevel, make, syntax_factory::SyntaxFactory}, syntax_editor::{Element, Position, Removable}, }; use crate::{ AssistId, assist_context::{AssistContext, Assists}, }; // Assist: unmerge_imports // // Extracts a use item from a use list into a standalone use list. // // ``` // use std::fmt::{Debug, Display$0}; // ``` // -> // ``` // use std::fmt::{Debug}; // use std::fmt::Display; // ``` pub(crate) fn unmerge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let tree = ctx.find_node_at_offset::()?; let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?; if tree_list.use_trees().count() < 2 { cov_mark::hit!(skip_single_import); return None; } let use_ = tree_list.syntax().ancestors().find_map(ast::Use::cast)?; let path = resolve_full_path(&tree)?; // If possible, explain what is going to be done. let label = match tree.path().and_then(|path| path.first_segment()) { Some(name) => format!("Unmerge use of `{name}`"), None => "Unmerge use".into(), }; let target = tree.syntax().text_range(); acc.add(AssistId::refactor_rewrite("unmerge_imports"), label, target, |builder| { let make = SyntaxFactory::with_mappings(); let new_use = make.use_( use_.attrs(), use_.visibility(), make.use_tree(path, tree.use_tree_list(), tree.rename(), tree.star_token().is_some()), ); let mut editor = builder.make_editor(use_.syntax()); // Remove the use tree from the current use item tree.remove(&mut editor); // Insert a newline and indentation, followed by the new use item editor.insert_all( Position::after(use_.syntax()), vec![ make.whitespace(&format!("\n{}", IndentLevel::from_node(use_.syntax()))) .syntax_element(), new_use.syntax().syntax_element(), ], ); editor.add_mappings(make.finish_with_mappings()); builder.add_file_edits(ctx.vfs_file_id(), editor); }) } fn resolve_full_path(tree: &ast::UseTree) -> Option { let paths = tree .syntax() .ancestors() .take_while(|n| n.kind() != SyntaxKind::USE) .filter_map(ast::UseTree::cast) .filter_map(|t| t.path()); let final_path = paths.reduce(|prev, next| make::path_concat(next, prev))?; if final_path.segment().is_some_and(|it| it.self_token().is_some()) { final_path.qualifier() } else { Some(final_path) } } #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; #[test] fn skip_single_import() { cov_mark::check!(skip_single_import); check_assist_not_applicable( unmerge_imports, r" use std::fmt::Debug$0; ", ); check_assist_not_applicable( unmerge_imports, r" use std::fmt::{Debug$0}; ", ); check_assist_not_applicable( unmerge_imports, r" use std::fmt::Debug as Dbg$0; ", ); } #[test] fn skip_single_glob_import() { check_assist_not_applicable( unmerge_imports, r" use std::fmt::*$0; ", ); } #[test] fn unmerge_import() { check_assist( unmerge_imports, r" use std::fmt::{Debug, Display$0}; ", r" use std::fmt::{Debug}; use std::fmt::Display; ", ); check_assist( unmerge_imports, r" use std::fmt::{Debug, format$0, Display}; ", r" use std::fmt::{Debug, Display}; use std::fmt::format; ", ); } #[test] fn unmerge_glob_import() { check_assist( unmerge_imports, r" use std::fmt::{*$0, Display}; ", r" use std::fmt::{Display}; use std::fmt::*; ", ); } #[test] fn unmerge_renamed_import() { check_assist( unmerge_imports, r" use std::fmt::{Debug, Display as Disp$0}; ", r" use std::fmt::{Debug}; use std::fmt::Display as Disp; ", ); } #[test] fn unmerge_indented_import() { check_assist( unmerge_imports, r" mod format { use std::fmt::{Debug, Display$0 as Disp, format}; } ", r" mod format { use std::fmt::{Debug, format}; use std::fmt::Display as Disp; } ", ); } #[test] fn unmerge_nested_import() { check_assist( unmerge_imports, r" use foo::bar::{baz::{qux$0, foobar}, barbaz}; ", r" use foo::bar::{baz::{foobar}, barbaz}; use foo::bar::baz::qux; ", ); check_assist( unmerge_imports, r" use foo::bar::{baz$0::{qux, foobar}, barbaz}; ", r" use foo::bar::{barbaz}; use foo::bar::baz::{qux, foobar}; ", ); } #[test] fn unmerge_import_with_visibility() { check_assist( unmerge_imports, r" pub use std::fmt::{Debug, Display$0}; ", r" pub use std::fmt::{Debug}; pub use std::fmt::Display; ", ); } #[test] fn unmerge_import_on_self() { check_assist( unmerge_imports, r"use std::process::{Command, self$0};", r"use std::process::{Command}; use std::process;", ); } #[test] fn unmerge_import_with_attributes() { check_assist( unmerge_imports, r" #[allow(deprecated)] use foo::{bar, baz$0};", r" #[allow(deprecated)] use foo::{bar}; #[allow(deprecated)] use foo::baz;", ); } }