From a3a722de9ffaefdd0a46194e7ea2edac9754fd38 Mon Sep 17 00:00:00 2001 From: unexge Date: Fri, 15 Jan 2021 22:14:51 +0300 Subject: Add Unmerge Use assist --- crates/assists/src/handlers/unmerge_use.rs | 213 +++++++++++++++++++++++++++++ crates/assists/src/lib.rs | 2 + crates/assists/src/tests/generated.rs | 14 ++ crates/ide_db/src/helpers/insert_use.rs | 2 +- crates/syntax/src/ast/make.rs | 8 +- 5 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 crates/assists/src/handlers/unmerge_use.rs diff --git a/crates/assists/src/handlers/unmerge_use.rs b/crates/assists/src/handlers/unmerge_use.rs new file mode 100644 index 000000000..d7dfe70d9 --- /dev/null +++ b/crates/assists/src/handlers/unmerge_use.rs @@ -0,0 +1,213 @@ +use syntax::{ + algo::SyntaxRewriter, + ast::{self, edit::AstNodeEdit, VisibilityOwner}, + AstNode, SyntaxKind, +}; + +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 { + return None; + } + + let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?; + let path = resolve_full_path(&tree)?; + + let new_use = ast::make::use_( + use_.visibility(), + ast::make::use_tree(path, None, 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()); + + let target = tree.syntax().text_range(); + acc.add( + AssistId("unmerge_use", AssistKind::RefactorRewrite), + "Unmerge use", + target, + |builder| { + 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() { + 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; +", + ); + } + + #[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; +", + ); + } +} diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index 1080294ab..3d7971806 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs @@ -156,6 +156,7 @@ mod handlers { mod replace_unwrap_with_match; mod split_import; mod toggle_ignore; + mod unmerge_use; mod unwrap_block; mod wrap_return_type_in_result; @@ -213,6 +214,7 @@ mod handlers { replace_unwrap_with_match::replace_unwrap_with_match, split_import::split_import, toggle_ignore::toggle_ignore, + unmerge_use::unmerge_use, unwrap_block::unwrap_block, wrap_return_type_in_result::wrap_return_type_in_result, // These are manually sorted for better priorities diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs index 217f577eb..d48d063b4 100644 --- a/crates/assists/src/tests/generated.rs +++ b/crates/assists/src/tests/generated.rs @@ -1137,6 +1137,20 @@ fn arithmetics { ) } +#[test] +fn doctest_unmerge_use() { + check_doc_test( + "unmerge_use", + r#####" +use std::fmt::{Debug, Display$0}; +"#####, + r#####" +use std::fmt::{Debug}; +use std::fmt::Display; +"#####, + ) +} + #[test] fn doctest_unwrap_block() { check_doc_test( diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs index 0c180e9bc..d2f9f5d25 100644 --- a/crates/ide_db/src/helpers/insert_use.rs +++ b/crates/ide_db/src/helpers/insert_use.rs @@ -97,7 +97,7 @@ pub fn insert_use<'a>( ) -> SyntaxRewriter<'a> { let _p = profile::span("insert_use"); let mut rewriter = SyntaxRewriter::default(); - let use_item = make::use_(make::use_tree(path.clone(), None, None, false)); + let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false)); // merge into existing imports if possible if let Some(mb) = merge { for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 1ed8a96e5..9ffc3ae11 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -108,8 +108,12 @@ pub fn use_tree_list(use_trees: impl IntoIterator) -> ast:: ast_from_text(&format!("use {{{}}};", use_trees)) } -pub fn use_(use_tree: ast::UseTree) -> ast::Use { - ast_from_text(&format!("use {};", use_tree)) +pub fn use_(visibility: Option, use_tree: ast::UseTree) -> ast::Use { + let visibility = match visibility { + None => String::new(), + Some(it) => format!("{} ", it), + }; + ast_from_text(&format!("{}use {};", visibility, use_tree)) } pub fn record_expr_field(name: ast::NameRef, expr: Option) -> ast::RecordExprField { -- cgit v1.2.3