aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/unmerge_use.rs228
-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, 251 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..6da1795da
--- /dev/null
+++ b/crates/assists/src/handlers/unmerge_use.rs
@@ -0,0 +1,228 @@
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 target = tree.syntax().text_range();
36 acc.add(
37 AssistId("unmerge_use", AssistKind::RefactorRewrite),
38 "Unmerge use",
39 target,
40 |builder| {
41 let new_use = ast::make::use_(
42 use_.visibility(),
43 ast::make::use_tree(
44 path,
45 tree.use_tree_list(),
46 tree.rename(),
47 tree.star_token().is_some(),
48 ),
49 );
50
51 let mut rewriter = SyntaxRewriter::default();
52 rewriter += tree.remove();
53 rewriter.insert_after(use_.syntax(), &ast::make::tokens::single_newline());
54 if let ident_level @ 1..=usize::MAX = use_.indent_level().0 as usize {
55 rewriter.insert_after(
56 use_.syntax(),
57 &ast::make::tokens::whitespace(&" ".repeat(4 * ident_level)),
58 );
59 }
60 rewriter.insert_after(use_.syntax(), new_use.syntax());
61
62 builder.rewrite(rewriter);
63 },
64 )
65}
66
67fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
68 let mut paths = tree
69 .syntax()
70 .ancestors()
71 .take_while(|n| n.kind() != SyntaxKind::USE_KW)
72 .filter_map(ast::UseTree::cast)
73 .filter_map(|t| t.path());
74
75 let mut final_path = paths.next()?;
76 for path in paths {
77 final_path = ast::make::path_concat(path, final_path)
78 }
79 Some(final_path)
80}
81
82#[cfg(test)]
83mod tests {
84 use crate::tests::{check_assist, check_assist_not_applicable};
85
86 use super::*;
87
88 #[test]
89 fn skip_single_use_item() {
90 check_assist_not_applicable(
91 unmerge_use,
92 r"
93use std::fmt::Debug$0;
94",
95 );
96 check_assist_not_applicable(
97 unmerge_use,
98 r"
99use std::fmt::{Debug$0};
100",
101 );
102 check_assist_not_applicable(
103 unmerge_use,
104 r"
105use std::fmt::Debug as Dbg$0;
106",
107 );
108 }
109
110 #[test]
111 fn skip_single_glob_import() {
112 check_assist_not_applicable(
113 unmerge_use,
114 r"
115use std::fmt::*$0;
116",
117 );
118 }
119
120 #[test]
121 fn unmerge_use_item() {
122 check_assist(
123 unmerge_use,
124 r"
125use std::fmt::{Debug, Display$0};
126",
127 r"
128use std::fmt::{Debug};
129use std::fmt::Display;
130",
131 );
132
133 check_assist(
134 unmerge_use,
135 r"
136use std::fmt::{Debug, format$0, Display};
137",
138 r"
139use std::fmt::{Debug, Display};
140use std::fmt::format;
141",
142 );
143 }
144
145 #[test]
146 fn unmerge_glob_import() {
147 check_assist(
148 unmerge_use,
149 r"
150use std::fmt::{*$0, Display};
151",
152 r"
153use std::fmt::{Display};
154use std::fmt::*;
155",
156 );
157 }
158
159 #[test]
160 fn unmerge_renamed_use_item() {
161 check_assist(
162 unmerge_use,
163 r"
164use std::fmt::{Debug, Display as Disp$0};
165",
166 r"
167use std::fmt::{Debug};
168use std::fmt::Display as Disp;
169",
170 );
171 }
172
173 #[test]
174 fn unmerge_indented_use_item() {
175 check_assist(
176 unmerge_use,
177 r"
178mod format {
179 use std::fmt::{Debug, Display$0 as Disp, format};
180}
181",
182 r"
183mod format {
184 use std::fmt::{Debug, format};
185 use std::fmt::Display as Disp;
186}
187",
188 );
189 }
190
191 #[test]
192 fn unmerge_nested_use_item() {
193 check_assist(
194 unmerge_use,
195 r"
196use foo::bar::{baz::{qux$0, foobar}, barbaz};
197",
198 r"
199use foo::bar::{baz::{foobar}, barbaz};
200use foo::bar::baz::qux;
201",
202 );
203 check_assist(
204 unmerge_use,
205 r"
206use foo::bar::{baz$0::{qux, foobar}, barbaz};
207",
208 r"
209use foo::bar::{barbaz};
210use foo::bar::baz::{qux, foobar};
211",
212 );
213 }
214
215 #[test]
216 fn unmerge_use_item_with_visibility() {
217 check_assist(
218 unmerge_use,
219 r"
220pub use std::fmt::{Debug, Display$0};
221",
222 r"
223pub use std::fmt::{Debug};
224pub use std::fmt::Display;
225",
226 );
227 }
228}
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 {