diff options
-rw-r--r-- | crates/ra_assists/src/handlers/split_import.rs | 52 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/edit.rs | 18 | ||||
-rw-r--r-- | 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 2c3f07a79..292c39f59 100644 --- a/crates/ra_assists/src/handlers/split_import.rs +++ b/crates/ra_assists/src/handlers/split_import.rs | |||
@@ -1,6 +1,9 @@ | |||
1 | use std::iter::successors; | 1 | use std::iter::{once, successors}; |
2 | 2 | ||
3 | use ra_syntax::{ast, AstNode, TextUnit, T}; | 3 | use ra_syntax::{ |
4 | ast::{self, make}, | ||
5 | AstNode, T, | ||
6 | }; | ||
4 | 7 | ||
5 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
6 | 9 | ||
@@ -17,39 +20,50 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
17 | // ``` | 20 | // ``` |
18 | pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> { | 21 | pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> { |
19 | let colon_colon = ctx.find_token_at_offset(T![::])?; | 22 | let colon_colon = ctx.find_token_at_offset(T![::])?; |
20 | let path = ast::Path::cast(colon_colon.parent())?; | 23 | let path = ast::Path::cast(colon_colon.parent())?.qualifier()?; |
21 | let top_path = successors(Some(path), |it| it.parent_path()).last()?; | 24 | let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?; |
22 | 25 | ||
23 | let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast); | 26 | let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast)?; |
24 | if use_tree.is_none() { | ||
25 | return None; | ||
26 | } | ||
27 | 27 | ||
28 | let l_curly = colon_colon.text_range().end(); | 28 | let new_tree = split_use_tree_prefix(&use_tree, &path)?; |
29 | let r_curly = match top_path.syntax().parent().and_then(ast::UseTree::cast) { | 29 | let cursor = ctx.frange.range.start(); |
30 | Some(tree) => tree.syntax().text_range().end(), | ||
31 | None => top_path.syntax().text_range().end(), | ||
32 | }; | ||
33 | 30 | ||
34 | ctx.add_assist(AssistId("split_import"), "Split import", |edit| { | 31 | ctx.add_assist(AssistId("split_import"), "Split import", |edit| { |
35 | edit.target(colon_colon.text_range()); | 32 | edit.target(colon_colon.text_range()); |
36 | edit.insert(l_curly, "{"); | 33 | edit.replace_ast(use_tree, new_tree); |
37 | edit.insert(r_curly, "}"); | 34 | edit.set_cursor(cursor); |
38 | edit.set_cursor(l_curly + TextUnit::of_str("{")); | ||
39 | }) | 35 | }) |
40 | } | 36 | } |
41 | 37 | ||
38 | fn split_use_tree_prefix(use_tree: &ast::UseTree, prefix: &ast::Path) -> Option<ast::UseTree> { | ||
39 | let suffix = split_path_prefix(&prefix)?; | ||
40 | let use_tree = make::use_tree(suffix.clone(), use_tree.use_tree_list(), use_tree.alias()); | ||
41 | let nested = make::use_tree_list(once(use_tree)); | ||
42 | let res = make::use_tree(prefix.clone(), Some(nested), None); | ||
43 | Some(res) | ||
44 | } | ||
45 | |||
46 | fn split_path_prefix(prefix: &ast::Path) -> Option<ast::Path> { | ||
47 | let parent = prefix.parent_path()?; | ||
48 | let mut res = make::path_unqualified(parent.segment()?); | ||
49 | for p in successors(parent.parent_path(), |it| it.parent_path()) { | ||
50 | res = make::path_qualified(res, p.segment()?); | ||
51 | } | ||
52 | Some(res) | ||
53 | } | ||
54 | |||
42 | #[cfg(test)] | 55 | #[cfg(test)] |
43 | mod tests { | 56 | mod tests { |
44 | use super::*; | ||
45 | use crate::helpers::{check_assist, check_assist_target}; | 57 | use crate::helpers::{check_assist, check_assist_target}; |
46 | 58 | ||
59 | use super::*; | ||
60 | |||
47 | #[test] | 61 | #[test] |
48 | fn test_split_import() { | 62 | fn test_split_import() { |
49 | check_assist( | 63 | check_assist( |
50 | split_import, | 64 | split_import, |
51 | "use crate::<|>db::RootDatabase;", | 65 | "use crate::<|>db::RootDatabase;", |
52 | "use crate::{<|>db::RootDatabase};", | 66 | "use crate::<|>{db::RootDatabase};", |
53 | ) | 67 | ) |
54 | } | 68 | } |
55 | 69 | ||
@@ -58,7 +72,7 @@ mod tests { | |||
58 | check_assist( | 72 | check_assist( |
59 | split_import, | 73 | split_import, |
60 | "use crate:<|>:db::{RootDatabase, FileSymbol}", | 74 | "use crate:<|>:db::{RootDatabase, FileSymbol}", |
61 | "use crate::{<|>db::{RootDatabase, FileSymbol}}", | 75 | "use crate:<|>:{db::{RootDatabase, FileSymbol}}", |
62 | ) | 76 | ) |
63 | } | 77 | } |
64 | 78 | ||
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 1858e2b6c..e4cdccdb4 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -259,6 +259,24 @@ impl ast::UseItem { | |||
259 | } | 259 | } |
260 | } | 260 | } |
261 | 261 | ||
262 | impl ast::UseTree { | ||
263 | #[must_use] | ||
264 | pub fn with_path(&self, path: ast::Path) -> ast::UseTree { | ||
265 | if let Some(old) = self.path() { | ||
266 | return replace_descendants(self, iter::once((old, path))); | ||
267 | } | ||
268 | self.clone() | ||
269 | } | ||
270 | |||
271 | #[must_use] | ||
272 | pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree { | ||
273 | if let Some(old) = self.use_tree_list() { | ||
274 | return replace_descendants(self, iter::once((old, use_tree_list))); | ||
275 | } | ||
276 | self.clone() | ||
277 | } | ||
278 | } | ||
279 | |||
262 | #[must_use] | 280 | #[must_use] |
263 | pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { | 281 | pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { |
264 | N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() | 282 | 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 0da24560e..22c54f363 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 { | |||
25 | ast_from_text(text) | 25 | ast_from_text(text) |
26 | } | 26 | } |
27 | 27 | ||
28 | pub fn use_tree( | ||
29 | path: ast::Path, | ||
30 | use_tree_list: Option<ast::UseTreeList>, | ||
31 | alias: Option<ast::Alias>, | ||
32 | ) -> ast::UseTree { | ||
33 | let mut buf = "use ".to_string(); | ||
34 | buf += &path.syntax().to_string(); | ||
35 | if let Some(use_tree_list) = use_tree_list { | ||
36 | buf += &format!("::{}", use_tree_list.syntax()); | ||
37 | } | ||
38 | if let Some(alias) = alias { | ||
39 | buf += &format!(" {}", alias.syntax()); | ||
40 | } | ||
41 | ast_from_text(&buf) | ||
42 | } | ||
43 | |||
44 | pub fn use_tree_list(use_trees: impl IntoIterator<Item = ast::UseTree>) -> ast::UseTreeList { | ||
45 | let use_trees = use_trees.into_iter().map(|it| it.syntax().clone()).join(", "); | ||
46 | ast_from_text(&format!("use {{{}}};", use_trees)) | ||
47 | } | ||
48 | |||
28 | pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordField { | 49 | pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordField { |
29 | return match expr { | 50 | return match expr { |
30 | Some(expr) => from_text(&format!("{}: {}", name.syntax(), expr.syntax())), | 51 | Some(expr) => from_text(&format!("{}: {}", name.syntax(), expr.syntax())), |