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