aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/unmerge_use.rs213
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests/generated.rs14
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs2
-rw-r--r--crates/syntax/src/ast/make.rs8
5 files changed, 236 insertions, 3 deletions
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 @@
1use syntax::{
2 algo::SyntaxRewriter,
3 ast::{self, edit::AstNodeEdit, VisibilityOwner},
4 AstNode, SyntaxKind,
5};
6
7use crate::{
8 assist_context::{AssistContext, Assists},
9 AssistId, AssistKind,
10};
11
12// Assist: unmerge_use
13//
14// Extracts single use item from use list.
15//
16// ```
17// use std::fmt::{Debug, Display$0};
18// ```
19// ->
20// ```
21// use std::fmt::{Debug};
22// use std::fmt::Display;
23// ```
24pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let tree: ast::UseTree = ctx.find_node_at_offset()?;
26
27 let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
28 if tree_list.use_trees().count() < 2 {
29 return None;
30 }
31
32 let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
33 let path = resolve_full_path(&tree)?;
34
35 let new_use = ast::make::use_(
36 use_.visibility(),
37 ast::make::use_tree(path, None, tree.rename(), tree.star_token().is_some()),
38 );
39
40 let mut rewriter = SyntaxRewriter::default();
41 rewriter += tree.remove();
42 rewriter.insert_after(use_.syntax(), &ast::make::tokens::single_newline());
43 if let ident_level @ 1..=usize::MAX = use_.indent_level().0 as usize {
44 rewriter.insert_after(
45 use_.syntax(),
46 &ast::make::tokens::whitespace(&" ".repeat(4 * ident_level)),
47 );
48 }
49 rewriter.insert_after(use_.syntax(), new_use.syntax());
50
51 let target = tree.syntax().text_range();
52 acc.add(
53 AssistId("unmerge_use", AssistKind::RefactorRewrite),
54 "Unmerge use",
55 target,
56 |builder| {
57 builder.rewrite(rewriter);
58 },
59 )
60}
61
62fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
63 let mut paths = tree
64 .syntax()
65 .ancestors()
66 .take_while(|n| n.kind() != SyntaxKind::USE_KW)
67 .filter_map(ast::UseTree::cast)
68 .filter_map(|t| t.path());
69
70 let mut final_path = paths.next()?;
71 for path in paths {
72 final_path = ast::make::path_concat(path, final_path)
73 }
74 Some(final_path)
75}
76
77#[cfg(test)]
78mod tests {
79 use crate::tests::{check_assist, check_assist_not_applicable};
80
81 use super::*;
82
83 #[test]
84 fn skip_single_use_item() {
85 check_assist_not_applicable(
86 unmerge_use,
87 r"
88use std::fmt::Debug$0;
89",
90 );
91 check_assist_not_applicable(
92 unmerge_use,
93 r"
94use std::fmt::{Debug$0};
95",
96 );
97 check_assist_not_applicable(
98 unmerge_use,
99 r"
100use std::fmt::Debug as Dbg$0;
101",
102 );
103 }
104
105 #[test]
106 fn skip_single_glob_import() {
107 check_assist_not_applicable(
108 unmerge_use,
109 r"
110use std::fmt::*$0;
111",
112 );
113 }
114
115 #[test]
116 fn unmerge_use_item() {
117 check_assist(
118 unmerge_use,
119 r"
120use std::fmt::{Debug, Display$0};
121",
122 r"
123use std::fmt::{Debug};
124use std::fmt::Display;
125",
126 );
127
128 check_assist(
129 unmerge_use,
130 r"
131use std::fmt::{Debug, format$0, Display};
132",
133 r"
134use std::fmt::{Debug, Display};
135use std::fmt::format;
136",
137 );
138 }
139
140 #[test]
141 fn unmerge_glob_import() {
142 check_assist(
143 unmerge_use,
144 r"
145use std::fmt::{*$0, Display};
146",
147 r"
148use std::fmt::{Display};
149use std::fmt::*;
150",
151 );
152 }
153
154 #[test]
155 fn unmerge_renamed_use_item() {
156 check_assist(
157 unmerge_use,
158 r"
159use std::fmt::{Debug, Display as Disp$0};
160",
161 r"
162use std::fmt::{Debug};
163use std::fmt::Display as Disp;
164",
165 );
166 }
167
168 #[test]
169 fn unmerge_indented_use_item() {
170 check_assist(
171 unmerge_use,
172 r"
173mod format {
174 use std::fmt::{Debug, Display$0 as Disp, format};
175}
176",
177 r"
178mod format {
179 use std::fmt::{Debug, format};
180 use std::fmt::Display as Disp;
181}
182",
183 );
184 }
185
186 #[test]
187 fn unmerge_nested_use_item() {
188 check_assist(
189 unmerge_use,
190 r"
191use foo::bar::{baz::{qux$0, foobar}, barbaz};
192",
193 r"
194use foo::bar::{baz::{foobar}, barbaz};
195use foo::bar::baz::qux;
196",
197 );
198 }
199
200 #[test]
201 fn unmerge_use_item_with_visibility() {
202 check_assist(
203 unmerge_use,
204 r"
205pub use std::fmt::{Debug, Display$0};
206",
207 r"
208pub use std::fmt::{Debug};
209pub use std::fmt::Display;
210",
211 );
212 }
213}
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 {
156 mod replace_unwrap_with_match; 156 mod replace_unwrap_with_match;
157 mod split_import; 157 mod split_import;
158 mod toggle_ignore; 158 mod toggle_ignore;
159 mod unmerge_use;
159 mod unwrap_block; 160 mod unwrap_block;
160 mod wrap_return_type_in_result; 161 mod wrap_return_type_in_result;
161 162
@@ -213,6 +214,7 @@ mod handlers {
213 replace_unwrap_with_match::replace_unwrap_with_match, 214 replace_unwrap_with_match::replace_unwrap_with_match,
214 split_import::split_import, 215 split_import::split_import,
215 toggle_ignore::toggle_ignore, 216 toggle_ignore::toggle_ignore,
217 unmerge_use::unmerge_use,
216 unwrap_block::unwrap_block, 218 unwrap_block::unwrap_block,
217 wrap_return_type_in_result::wrap_return_type_in_result, 219 wrap_return_type_in_result::wrap_return_type_in_result,
218 // These are manually sorted for better priorities 220 // 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
@@ -1138,6 +1138,20 @@ fn arithmetics {
1138} 1138}
1139 1139
1140#[test] 1140#[test]
1141fn doctest_unmerge_use() {
1142 check_doc_test(
1143 "unmerge_use",
1144 r#####"
1145use std::fmt::{Debug, Display$0};
1146"#####,
1147 r#####"
1148use std::fmt::{Debug};
1149use std::fmt::Display;
1150"#####,
1151 )
1152}
1153
1154#[test]
1141fn doctest_unwrap_block() { 1155fn doctest_unwrap_block() {
1142 check_doc_test( 1156 check_doc_test(
1143 "unwrap_block", 1157 "unwrap_block",
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>(
97) -> SyntaxRewriter<'a> { 97) -> SyntaxRewriter<'a> {
98 let _p = profile::span("insert_use"); 98 let _p = profile::span("insert_use");
99 let mut rewriter = SyntaxRewriter::default(); 99 let mut rewriter = SyntaxRewriter::default();
100 let use_item = make::use_(make::use_tree(path.clone(), None, None, false)); 100 let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false));
101 // merge into existing imports if possible 101 // merge into existing imports if possible
102 if let Some(mb) = merge { 102 if let Some(mb) = merge {
103 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { 103 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<Item = ast::UseTree>) -> ast::
108 ast_from_text(&format!("use {{{}}};", use_trees)) 108 ast_from_text(&format!("use {{{}}};", use_trees))
109} 109}
110 110
111pub fn use_(use_tree: ast::UseTree) -> ast::Use { 111pub fn use_(visibility: Option<ast::Visibility>, use_tree: ast::UseTree) -> ast::Use {
112 ast_from_text(&format!("use {};", use_tree)) 112 let visibility = match visibility {
113 None => String::new(),
114 Some(it) => format!("{} ", it),
115 };
116 ast_from_text(&format!("{}use {};", visibility, use_tree))
113} 117}
114 118
115pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField { 119pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField {