From ea0c124219da33462b9d0be93f7abe0478cc7af2 Mon Sep 17 00:00:00 2001
From: Aleksey Kladov <aleksey.kladov@gmail.com>
Date: Thu, 5 Mar 2020 19:03:14 +0100
Subject: [PATCH] Rerail split_import API onto AST

The code is more verbose and less efficient now, but should be
reusable in add_import context as well
---
 .../ra_assists/src/handlers/split_import.rs   | 52 ++++++++++++-------
 crates/ra_syntax/src/ast/edit.rs              | 18 +++++++
 crates/ra_syntax/src/ast/make.rs              | 21 ++++++++
 3 files changed, 72 insertions(+), 19 deletions(-)

diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs
index 2c3f07a79df..292c39f59b1 100644
--- a/crates/ra_assists/src/handlers/split_import.rs
+++ b/crates/ra_assists/src/handlers/split_import.rs
@@ -1,6 +1,9 @@
-use std::iter::successors;
+use std::iter::{once, successors};
 
-use ra_syntax::{ast, AstNode, TextUnit, T};
+use ra_syntax::{
+    ast::{self, make},
+    AstNode, T,
+};
 
 use crate::{Assist, AssistCtx, AssistId};
 
@@ -17,39 +20,50 @@ use crate::{Assist, AssistCtx, AssistId};
 // ```
 pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
     let colon_colon = ctx.find_token_at_offset(T![::])?;
-    let path = ast::Path::cast(colon_colon.parent())?;
-    let top_path = successors(Some(path), |it| it.parent_path()).last()?;
+    let path = ast::Path::cast(colon_colon.parent())?.qualifier()?;
+    let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?;
 
-    let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast);
-    if use_tree.is_none() {
-        return None;
-    }
+    let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast)?;
 
-    let l_curly = colon_colon.text_range().end();
-    let r_curly = match top_path.syntax().parent().and_then(ast::UseTree::cast) {
-        Some(tree) => tree.syntax().text_range().end(),
-        None => top_path.syntax().text_range().end(),
-    };
+    let new_tree = split_use_tree_prefix(&use_tree, &path)?;
+    let cursor = ctx.frange.range.start();
 
     ctx.add_assist(AssistId("split_import"), "Split import", |edit| {
         edit.target(colon_colon.text_range());
-        edit.insert(l_curly, "{");
-        edit.insert(r_curly, "}");
-        edit.set_cursor(l_curly + TextUnit::of_str("{"));
+        edit.replace_ast(use_tree, new_tree);
+        edit.set_cursor(cursor);
     })
 }
 
+fn split_use_tree_prefix(use_tree: &ast::UseTree, prefix: &ast::Path) -> Option<ast::UseTree> {
+    let suffix = split_path_prefix(&prefix)?;
+    let use_tree = make::use_tree(suffix.clone(), use_tree.use_tree_list(), use_tree.alias());
+    let nested = make::use_tree_list(once(use_tree));
+    let res = make::use_tree(prefix.clone(), Some(nested), None);
+    Some(res)
+}
+
+fn split_path_prefix(prefix: &ast::Path) -> Option<ast::Path> {
+    let parent = prefix.parent_path()?;
+    let mut res = make::path_unqualified(parent.segment()?);
+    for p in successors(parent.parent_path(), |it| it.parent_path()) {
+        res = make::path_qualified(res, p.segment()?);
+    }
+    Some(res)
+}
+
 #[cfg(test)]
 mod tests {
-    use super::*;
     use crate::helpers::{check_assist, check_assist_target};
 
+    use super::*;
+
     #[test]
     fn test_split_import() {
         check_assist(
             split_import,
             "use crate::<|>db::RootDatabase;",
-            "use crate::{<|>db::RootDatabase};",
+            "use crate::<|>{db::RootDatabase};",
         )
     }
 
@@ -58,7 +72,7 @@ mod tests {
         check_assist(
             split_import,
             "use crate:<|>:db::{RootDatabase, FileSymbol}",
-            "use crate::{<|>db::{RootDatabase, FileSymbol}}",
+            "use crate:<|>:{db::{RootDatabase, FileSymbol}}",
         )
     }
 
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
index 1858e2b6c17..e4cdccdb439 100644
--- a/crates/ra_syntax/src/ast/edit.rs
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -259,6 +259,24 @@ impl ast::UseItem {
     }
 }
 
+impl ast::UseTree {
+    #[must_use]
+    pub fn with_path(&self, path: ast::Path) -> ast::UseTree {
+        if let Some(old) = self.path() {
+            return replace_descendants(self, iter::once((old, path)));
+        }
+        self.clone()
+    }
+
+    #[must_use]
+    pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree {
+        if let Some(old) = self.use_tree_list() {
+            return replace_descendants(self, iter::once((old, use_tree_list)));
+        }
+        self.clone()
+    }
+}
+
 #[must_use]
 pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
     N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap()
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index 0da24560ead..22c54f363e4 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -25,6 +25,27 @@ fn path_from_text(text: &str) -> ast::Path {
     ast_from_text(text)
 }
 
+pub fn use_tree(
+    path: ast::Path,
+    use_tree_list: Option<ast::UseTreeList>,
+    alias: Option<ast::Alias>,
+) -> ast::UseTree {
+    let mut buf = "use ".to_string();
+    buf += &path.syntax().to_string();
+    if let Some(use_tree_list) = use_tree_list {
+        buf += &format!("::{}", use_tree_list.syntax());
+    }
+    if let Some(alias) = alias {
+        buf += &format!(" {}", alias.syntax());
+    }
+    ast_from_text(&buf)
+}
+
+pub fn use_tree_list(use_trees: impl IntoIterator<Item = ast::UseTree>) -> ast::UseTreeList {
+    let use_trees = use_trees.into_iter().map(|it| it.syntax().clone()).join(", ");
+    ast_from_text(&format!("use {{{}}};", use_trees))
+}
+
 pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordField {
     return match expr {
         Some(expr) => from_text(&format!("{}: {}", name.syntax(), expr.syntax())),