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