use syntax::{ algo::SyntaxRewriter, ast::{self, edit::AstNodeEdit, VisibilityOwner}, AstNode, SyntaxKind, }; use test_utils::mark; use crate::{ assist_context::{AssistContext, Assists}, AssistId, AssistKind, }; // Assist: unmerge_use // // Extracts single use item from use list. // // ``` // use std::fmt::{Debug, Display$0}; // ``` // -> // ``` // use std::fmt::{Debug}; // use std::fmt::Display; // ``` pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let tree: ast::UseTree = ctx.find_node_at_offset()?; let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?; if tree_list.use_trees().count() < 2 { mark::hit!(skip_single_use_item); return None; } let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?; let path = resolve_full_path(&tree)?; let target = tree.syntax().text_range(); acc.add( AssistId("unmerge_use", AssistKind::RefactorRewrite), "Unmerge use", target, |builder| { let new_use = ast::make::use_( use_.visibility(), ast::make::use_tree( path, tree.use_tree_list(), tree.rename(), tree.star_token().is_some(), ), ); let mut rewriter = SyntaxRewriter::default(); rewriter += tree.remove(); rewriter.insert_after(use_.syntax(), &ast::make::tokens::single_newline()); if let ident_level @ 1..=usize::MAX = use_.indent_level().0 as usize { rewriter.insert_after( use_.syntax(), &ast::make::tokens::whitespace(&" ".repeat(4 * ident_level)), ); } rewriter.insert_after(use_.syntax(), new_use.syntax()); builder.rewrite(rewriter); }, ) } fn resolve_full_path(tree: &ast::UseTree) -> Option { let mut paths = tree .syntax() .ancestors() .take_while(|n| n.kind() != SyntaxKind::USE_KW) .filter_map(ast::UseTree::cast) .filter_map(|t| t.path()); let mut final_path = paths.next()?; for path in paths { final_path = ast::make::path_concat(path, final_path) } Some(final_path) } #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; #[test] fn skip_single_use_item() { mark::check!(skip_single_use_item); check_assist_not_applicable( unmerge_use, r" use std::fmt::Debug$0; ", ); check_assist_not_applicable( unmerge_use, r" use std::fmt::{Debug$0}; ", ); check_assist_not_applicable( unmerge_use, r" use std::fmt::Debug as Dbg$0; ", ); } #[test] fn skip_single_glob_import() { check_assist_not_applicable( unmerge_use, r" use std::fmt::*$0; ", ); } #[test] fn unmerge_use_item() { check_assist( unmerge_use, r" use std::fmt::{Debug, Display$0}; ", r" use std::fmt::{Debug}; use std::fmt::Display; ", ); check_assist( unmerge_use, 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_use, r" use std::fmt::{*$0, Display}; ", r" use std::fmt::{Display}; use std::fmt::*; ", ); } #[test] fn unmerge_renamed_use_item() { check_assist( unmerge_use, r" use std::fmt::{Debug, Display as Disp$0}; ", r" use std::fmt::{Debug}; use std::fmt::Display as Disp; ", ); } #[test] fn unmerge_indented_use_item() { check_assist( unmerge_use, 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_use_item() { check_assist( unmerge_use, r" use foo::bar::{baz::{qux$0, foobar}, barbaz}; ", r" use foo::bar::{baz::{foobar}, barbaz}; use foo::bar::baz::qux; ", ); check_assist( unmerge_use, r" use foo::bar::{baz$0::{qux, foobar}, barbaz}; ", r" use foo::bar::{barbaz}; use foo::bar::baz::{qux, foobar}; ", ); } #[test] fn unmerge_use_item_with_visibility() { check_assist( unmerge_use, r" pub use std::fmt::{Debug, Display$0}; ", r" pub use std::fmt::{Debug}; pub use std::fmt::Display; ", ); } }