diff options
Diffstat (limited to 'crates/assists/src/handlers/merge_imports.rs')
-rw-r--r-- | crates/assists/src/handlers/merge_imports.rs | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/merge_imports.rs b/crates/assists/src/handlers/merge_imports.rs new file mode 100644 index 000000000..35b884206 --- /dev/null +++ b/crates/assists/src/handlers/merge_imports.rs | |||
@@ -0,0 +1,318 @@ | |||
1 | use std::iter::successors; | ||
2 | |||
3 | use syntax::{ | ||
4 | algo::{neighbor, skip_trivia_token, SyntaxRewriter}, | ||
5 | ast::{self, edit::AstNodeEdit, make}, | ||
6 | AstNode, Direction, InsertPosition, SyntaxElement, T, | ||
7 | }; | ||
8 | |||
9 | use crate::{ | ||
10 | assist_context::{AssistContext, Assists}, | ||
11 | utils::next_prev, | ||
12 | AssistId, AssistKind, | ||
13 | }; | ||
14 | |||
15 | // Assist: merge_imports | ||
16 | // | ||
17 | // Merges two imports with a common prefix. | ||
18 | // | ||
19 | // ``` | ||
20 | // use std::<|>fmt::Formatter; | ||
21 | // use std::io; | ||
22 | // ``` | ||
23 | // -> | ||
24 | // ``` | ||
25 | // use std::{fmt::Formatter, io}; | ||
26 | // ``` | ||
27 | pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
28 | let tree: ast::UseTree = ctx.find_node_at_offset()?; | ||
29 | let mut rewriter = SyntaxRewriter::default(); | ||
30 | let mut offset = ctx.offset(); | ||
31 | |||
32 | if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) { | ||
33 | let (merged, to_delete) = next_prev() | ||
34 | .filter_map(|dir| neighbor(&use_item, dir)) | ||
35 | .filter_map(|it| Some((it.clone(), it.use_tree()?))) | ||
36 | .find_map(|(use_item, use_tree)| { | ||
37 | Some((try_merge_trees(&tree, &use_tree)?, use_item)) | ||
38 | })?; | ||
39 | |||
40 | rewriter.replace_ast(&tree, &merged); | ||
41 | rewriter += to_delete.remove(); | ||
42 | |||
43 | if to_delete.syntax().text_range().end() < offset { | ||
44 | offset -= to_delete.syntax().text_range().len(); | ||
45 | } | ||
46 | } else { | ||
47 | let (merged, to_delete) = next_prev() | ||
48 | .filter_map(|dir| neighbor(&tree, dir)) | ||
49 | .find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?; | ||
50 | |||
51 | rewriter.replace_ast(&tree, &merged); | ||
52 | rewriter += to_delete.remove(); | ||
53 | |||
54 | if to_delete.syntax().text_range().end() < offset { | ||
55 | offset -= to_delete.syntax().text_range().len(); | ||
56 | } | ||
57 | }; | ||
58 | |||
59 | let target = tree.syntax().text_range(); | ||
60 | acc.add( | ||
61 | AssistId("merge_imports", AssistKind::RefactorRewrite), | ||
62 | "Merge imports", | ||
63 | target, | ||
64 | |builder| { | ||
65 | builder.rewrite(rewriter); | ||
66 | }, | ||
67 | ) | ||
68 | } | ||
69 | |||
70 | fn try_merge_trees(old: &ast::UseTree, new: &ast::UseTree) -> Option<ast::UseTree> { | ||
71 | let lhs_path = old.path()?; | ||
72 | let rhs_path = new.path()?; | ||
73 | |||
74 | let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; | ||
75 | |||
76 | let lhs = old.split_prefix(&lhs_prefix); | ||
77 | let rhs = new.split_prefix(&rhs_prefix); | ||
78 | |||
79 | let should_insert_comma = lhs | ||
80 | .use_tree_list()? | ||
81 | .r_curly_token() | ||
82 | .and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev)) | ||
83 | .map(|it| it.kind() != T![,]) | ||
84 | .unwrap_or(true); | ||
85 | |||
86 | let mut to_insert: Vec<SyntaxElement> = Vec::new(); | ||
87 | if should_insert_comma { | ||
88 | to_insert.push(make::token(T![,]).into()); | ||
89 | to_insert.push(make::tokens::single_space().into()); | ||
90 | } | ||
91 | to_insert.extend( | ||
92 | rhs.use_tree_list()? | ||
93 | .syntax() | ||
94 | .children_with_tokens() | ||
95 | .filter(|it| it.kind() != T!['{'] && it.kind() != T!['}']), | ||
96 | ); | ||
97 | let use_tree_list = lhs.use_tree_list()?; | ||
98 | let pos = InsertPosition::Before(use_tree_list.r_curly_token()?.into()); | ||
99 | let use_tree_list = use_tree_list.insert_children(pos, to_insert); | ||
100 | Some(lhs.with_use_tree_list(use_tree_list)) | ||
101 | } | ||
102 | |||
103 | fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> { | ||
104 | let mut res = None; | ||
105 | let mut lhs_curr = first_path(&lhs); | ||
106 | let mut rhs_curr = first_path(&rhs); | ||
107 | loop { | ||
108 | match (lhs_curr.segment(), rhs_curr.segment()) { | ||
109 | (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (), | ||
110 | _ => break, | ||
111 | } | ||
112 | res = Some((lhs_curr.clone(), rhs_curr.clone())); | ||
113 | |||
114 | match (lhs_curr.parent_path(), rhs_curr.parent_path()) { | ||
115 | (Some(lhs), Some(rhs)) => { | ||
116 | lhs_curr = lhs; | ||
117 | rhs_curr = rhs; | ||
118 | } | ||
119 | _ => break, | ||
120 | } | ||
121 | } | ||
122 | |||
123 | res | ||
124 | } | ||
125 | |||
126 | fn first_path(path: &ast::Path) -> ast::Path { | ||
127 | successors(Some(path.clone()), |it| it.qualifier()).last().unwrap() | ||
128 | } | ||
129 | |||
130 | #[cfg(test)] | ||
131 | mod tests { | ||
132 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
133 | |||
134 | use super::*; | ||
135 | |||
136 | #[test] | ||
137 | fn test_merge_first() { | ||
138 | check_assist( | ||
139 | merge_imports, | ||
140 | r" | ||
141 | use std::fmt<|>::Debug; | ||
142 | use std::fmt::Display; | ||
143 | ", | ||
144 | r" | ||
145 | use std::fmt::{Debug, Display}; | ||
146 | ", | ||
147 | ) | ||
148 | } | ||
149 | |||
150 | #[test] | ||
151 | fn test_merge_second() { | ||
152 | check_assist( | ||
153 | merge_imports, | ||
154 | r" | ||
155 | use std::fmt::Debug; | ||
156 | use std::fmt<|>::Display; | ||
157 | ", | ||
158 | r" | ||
159 | use std::fmt::{Display, Debug}; | ||
160 | ", | ||
161 | ); | ||
162 | } | ||
163 | |||
164 | #[test] | ||
165 | fn merge_self1() { | ||
166 | check_assist( | ||
167 | merge_imports, | ||
168 | r" | ||
169 | use std::fmt<|>; | ||
170 | use std::fmt::Display; | ||
171 | ", | ||
172 | r" | ||
173 | use std::fmt::{self, Display}; | ||
174 | ", | ||
175 | ); | ||
176 | } | ||
177 | |||
178 | #[test] | ||
179 | fn merge_self2() { | ||
180 | check_assist( | ||
181 | merge_imports, | ||
182 | r" | ||
183 | use std::{fmt, <|>fmt::Display}; | ||
184 | ", | ||
185 | r" | ||
186 | use std::{fmt::{Display, self}}; | ||
187 | ", | ||
188 | ); | ||
189 | } | ||
190 | |||
191 | #[test] | ||
192 | fn test_merge_nested() { | ||
193 | check_assist( | ||
194 | merge_imports, | ||
195 | r" | ||
196 | use std::{fmt<|>::Debug, fmt::Display}; | ||
197 | ", | ||
198 | r" | ||
199 | use std::{fmt::{Debug, Display}}; | ||
200 | ", | ||
201 | ); | ||
202 | check_assist( | ||
203 | merge_imports, | ||
204 | r" | ||
205 | use std::{fmt::Debug, fmt<|>::Display}; | ||
206 | ", | ||
207 | r" | ||
208 | use std::{fmt::{Display, Debug}}; | ||
209 | ", | ||
210 | ); | ||
211 | } | ||
212 | |||
213 | #[test] | ||
214 | fn test_merge_single_wildcard_diff_prefixes() { | ||
215 | check_assist( | ||
216 | merge_imports, | ||
217 | r" | ||
218 | use std<|>::cell::*; | ||
219 | use std::str; | ||
220 | ", | ||
221 | r" | ||
222 | use std::{cell::*, str}; | ||
223 | ", | ||
224 | ) | ||
225 | } | ||
226 | |||
227 | #[test] | ||
228 | fn test_merge_both_wildcard_diff_prefixes() { | ||
229 | check_assist( | ||
230 | merge_imports, | ||
231 | r" | ||
232 | use std<|>::cell::*; | ||
233 | use std::str::*; | ||
234 | ", | ||
235 | r" | ||
236 | use std::{cell::*, str::*}; | ||
237 | ", | ||
238 | ) | ||
239 | } | ||
240 | |||
241 | #[test] | ||
242 | fn removes_just_enough_whitespace() { | ||
243 | check_assist( | ||
244 | merge_imports, | ||
245 | r" | ||
246 | use foo<|>::bar; | ||
247 | use foo::baz; | ||
248 | |||
249 | /// Doc comment | ||
250 | ", | ||
251 | r" | ||
252 | use foo::{bar, baz}; | ||
253 | |||
254 | /// Doc comment | ||
255 | ", | ||
256 | ); | ||
257 | } | ||
258 | |||
259 | #[test] | ||
260 | fn works_with_trailing_comma() { | ||
261 | check_assist( | ||
262 | merge_imports, | ||
263 | r" | ||
264 | use { | ||
265 | foo<|>::bar, | ||
266 | foo::baz, | ||
267 | }; | ||
268 | ", | ||
269 | r" | ||
270 | use { | ||
271 | foo::{bar, baz}, | ||
272 | }; | ||
273 | ", | ||
274 | ); | ||
275 | check_assist( | ||
276 | merge_imports, | ||
277 | r" | ||
278 | use { | ||
279 | foo::baz, | ||
280 | foo<|>::bar, | ||
281 | }; | ||
282 | ", | ||
283 | r" | ||
284 | use { | ||
285 | foo::{bar, baz}, | ||
286 | }; | ||
287 | ", | ||
288 | ); | ||
289 | } | ||
290 | |||
291 | #[test] | ||
292 | fn test_double_comma() { | ||
293 | check_assist( | ||
294 | merge_imports, | ||
295 | r" | ||
296 | use foo::bar::baz; | ||
297 | use foo::<|>{ | ||
298 | FooBar, | ||
299 | }; | ||
300 | ", | ||
301 | r" | ||
302 | use foo::{ | ||
303 | FooBar, | ||
304 | bar::baz}; | ||
305 | ", | ||
306 | ) | ||
307 | } | ||
308 | |||
309 | #[test] | ||
310 | fn test_empty_use() { | ||
311 | check_assist_not_applicable( | ||
312 | merge_imports, | ||
313 | r" | ||
314 | use std::<|> | ||
315 | fn main() {}", | ||
316 | ); | ||
317 | } | ||
318 | } | ||