--- /dev/null
+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<ast::Path> {
+ 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;
+",
+ );
+ }
+}