1use either::Either;
2use ide_db::imports::{
3 insert_use::{ImportGranularity, InsertUseConfig},
4 merge_imports::{MergeBehavior, try_merge_imports, try_merge_trees},
5};
6use syntax::{
7 AstNode, SyntaxElement, SyntaxNode,
8 algo::neighbor,
9 ast::{self, syntax_factory::SyntaxFactory},
10 match_ast,
11 syntax_editor::Removable,
12};
13
14use crate::{
15 AssistId,
16 assist_context::{AssistContext, Assists},
17 utils::next_prev,
18};
19
20use Edit::*;
21
22pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
35 let (target, edits) = if ctx.has_empty_selection() {
36 cov_mark::hit!(merge_with_use_item_neighbors);
38 let tree = ctx.find_node_at_offset::<ast::UseTree>()?.top_use_tree();
39 let target = tree.syntax().text_range();
40
41 let use_item = tree.syntax().parent().and_then(ast::Use::cast)?;
42 let mut neighbor = next_prev().find_map(|dir| neighbor(&use_item, dir)).into_iter();
43 let edits = use_item.try_merge_from(&mut neighbor, &ctx.config.insert_use);
44 (target, edits?)
45 } else {
46 let selection_range = ctx.selection_trimmed();
48 let parent_node = match ctx.covering_element() {
49 SyntaxElement::Node(n) => n,
50 SyntaxElement::Token(t) => t.parent()?,
51 };
52 let mut selected_nodes =
53 parent_node.children().filter(|it| selection_range.contains_range(it.text_range()));
54
55 let first_selected = selected_nodes.next()?;
56 let edits = match_ast! {
57 match first_selected {
58 ast::Use(use_item) => {
59 cov_mark::hit!(merge_with_selected_use_item_neighbors);
60 use_item.try_merge_from(&mut selected_nodes.filter_map(ast::Use::cast), &ctx.config.insert_use)
61 },
62 ast::UseTree(use_tree) => {
63 cov_mark::hit!(merge_with_selected_use_tree_neighbors);
64 use_tree.try_merge_from(&mut selected_nodes.filter_map(ast::UseTree::cast), &ctx.config.insert_use)
65 },
66 _ => return None,
67 }
68 };
69 (selection_range, edits?)
70 };
71
72 let parent_node = match ctx.covering_element() {
73 SyntaxElement::Node(n) => n,
74 SyntaxElement::Token(t) => t.parent()?,
75 };
76
77 acc.add(AssistId::refactor_rewrite("merge_imports"), "Merge imports", target, |builder| {
78 let make = SyntaxFactory::with_mappings();
79 let mut editor = builder.make_editor(&parent_node);
80
81 for edit in edits {
82 match edit {
83 Remove(it) => {
84 let node = it.as_ref();
85 if let Some(left) = node.left() {
86 left.remove(&mut editor);
87 } else if let Some(right) = node.right() {
88 right.remove(&mut editor);
89 }
90 }
91 Replace(old, new) => {
92 editor.replace(old, &new);
93 }
94 }
95 }
96 editor.add_mappings(make.finish_with_mappings());
97 builder.add_file_edits(ctx.vfs_file_id(), editor);
98 })
99}
100
101trait Merge: AstNode + Clone {
102 fn try_merge_from(
103 self,
104 items: &mut dyn Iterator<Item = Self>,
105 cfg: &InsertUseConfig,
106 ) -> Option<Vec<Edit>> {
107 let mut edits = Vec::new();
108 let mut merged = self.clone();
109 for item in items {
110 merged = merged.try_merge(&item, cfg)?;
111 edits.push(Edit::Remove(item.into_either()));
112 }
113 if !edits.is_empty() {
114 edits.push(Edit::replace(self, merged));
115 Some(edits)
116 } else {
117 None
118 }
119 }
120 fn try_merge(&self, other: &Self, cfg: &InsertUseConfig) -> Option<Self>;
121 fn into_either(self) -> Either<ast::Use, ast::UseTree>;
122}
123
124impl Merge for ast::Use {
125 fn try_merge(&self, other: &Self, cfg: &InsertUseConfig) -> Option<Self> {
126 let mb = match cfg.granularity {
127 ImportGranularity::One => MergeBehavior::One,
128 _ => MergeBehavior::Crate,
129 };
130 try_merge_imports(self, other, mb)
131 }
132 fn into_either(self) -> Either<ast::Use, ast::UseTree> {
133 Either::Left(self)
134 }
135}
136
137impl Merge for ast::UseTree {
138 fn try_merge(&self, other: &Self, _: &InsertUseConfig) -> Option<Self> {
139 try_merge_trees(self, other, MergeBehavior::Crate)
140 }
141 fn into_either(self) -> Either<ast::Use, ast::UseTree> {
142 Either::Right(self)
143 }
144}
145
146#[derive(Debug)]
147enum Edit {
148 Remove(Either<ast::Use, ast::UseTree>),
149 Replace(SyntaxNode, SyntaxNode),
150}
151
152impl Edit {
153 fn replace(old: impl AstNode, new: impl AstNode) -> Self {
154 Edit::Replace(old.syntax().clone(), new.syntax().clone())
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use crate::tests::{
161 check_assist, check_assist_import_one, check_assist_not_applicable,
162 check_assist_not_applicable_for_import_one,
163 };
164
165 use super::*;
166
167 macro_rules! check_assist_import_one_variations {
168 ($first: literal, $second: literal, $expected: literal) => {
169 check_assist_import_one(
170 merge_imports,
171 concat!(concat!("use ", $first, ";"), concat!("use ", $second, ";")),
172 $expected,
173 );
174 check_assist_import_one(
175 merge_imports,
176 concat!(concat!("use {", $first, "};"), concat!("use ", $second, ";")),
177 $expected,
178 );
179 check_assist_import_one(
180 merge_imports,
181 concat!(concat!("use ", $first, ";"), concat!("use {", $second, "};")),
182 $expected,
183 );
184 check_assist_import_one(
185 merge_imports,
186 concat!(concat!("use {", $first, "};"), concat!("use {", $second, "};")),
187 $expected,
188 );
189 };
190 }
191
192 #[test]
193 fn test_merge_equal() {
194 cov_mark::check!(merge_with_use_item_neighbors);
195 check_assist(
196 merge_imports,
197 r"
198use std::fmt$0::{Display, Debug};
199use std::fmt::{Display, Debug};
200",
201 r"
202use std::fmt::{Debug, Display};
203",
204 );
205
206 cov_mark::check_count!(merge_with_use_item_neighbors, 4);
209 check_assist_import_one_variations!(
210 "std::fmt$0::{Display, Debug}",
211 "std::fmt::{Display, Debug}",
212 "use {std::fmt::{Debug, Display}};"
213 );
214 }
215
216 #[test]
217 fn test_merge_first() {
218 check_assist(
219 merge_imports,
220 r"
221use std::fmt$0::Debug;
222use std::fmt::Display;
223",
224 r"
225use std::fmt::{Debug, Display};
226",
227 );
228 check_assist_import_one_variations!(
229 "std::fmt$0::Debug",
230 "std::fmt::Display",
231 "use {std::fmt::{Debug, Display}};"
232 );
233 }
234
235 #[test]
236 fn test_merge_second() {
237 check_assist(
238 merge_imports,
239 r"
240use std::fmt::Debug;
241use std::fmt$0::Display;
242",
243 r"
244use std::fmt::{Debug, Display};
245",
246 );
247 check_assist_import_one_variations!(
248 "std::fmt::Debug",
249 "std::fmt$0::Display",
250 "use {std::fmt::{Debug, Display}};"
251 );
252 }
253
254 #[test]
255 fn merge_self() {
256 check_assist(
257 merge_imports,
258 r"
259use std::fmt$0;
260use std::fmt::Display;
261",
262 r"
263use std::fmt::{self, Display};
264",
265 );
266 check_assist_import_one_variations!(
267 "std::fmt$0",
268 "std::fmt::Display",
269 "use {std::fmt::{self, Display}};"
270 );
271 }
272
273 #[test]
274 fn not_applicable_to_single_import() {
275 check_assist_not_applicable(merge_imports, "use std::{fmt, $0fmt::Display};");
276 check_assist_not_applicable_for_import_one(
277 merge_imports,
278 "use {std::{fmt, $0fmt::Display}};",
279 );
280 }
281
282 #[test]
283 fn skip_pub1() {
284 check_assist_not_applicable(
285 merge_imports,
286 r"
287pub use std::fmt$0::Debug;
288use std::fmt::Display;
289",
290 );
291 }
292
293 #[test]
294 fn skip_pub_last() {
295 check_assist_not_applicable(
296 merge_imports,
297 r"
298use std::fmt$0::Debug;
299pub use std::fmt::Display;
300",
301 );
302 }
303
304 #[test]
305 fn skip_pub_crate_pub() {
306 check_assist_not_applicable(
307 merge_imports,
308 r"
309pub(crate) use std::fmt$0::Debug;
310pub use std::fmt::Display;
311",
312 );
313 }
314
315 #[test]
316 fn skip_pub_pub_crate() {
317 check_assist_not_applicable(
318 merge_imports,
319 r"
320pub use std::fmt$0::Debug;
321pub(crate) use std::fmt::Display;
322",
323 );
324 }
325
326 #[test]
327 fn merge_pub() {
328 check_assist(
329 merge_imports,
330 r"
331pub use std::fmt$0::Debug;
332pub use std::fmt::Display;
333",
334 r"
335pub use std::fmt::{Debug, Display};
336",
337 )
338 }
339
340 #[test]
341 fn merge_pub_crate() {
342 check_assist(
343 merge_imports,
344 r"
345pub(crate) use std::fmt$0::Debug;
346pub(crate) use std::fmt::Display;
347",
348 r"
349pub(crate) use std::fmt::{Debug, Display};
350",
351 )
352 }
353
354 #[test]
355 fn merge_pub_in_path_crate() {
356 check_assist(
357 merge_imports,
358 r"
359pub(in this::path) use std::fmt$0::Debug;
360pub(in this::path) use std::fmt::Display;
361",
362 r"
363pub(in this::path) use std::fmt::{Debug, Display};
364",
365 )
366 }
367
368 #[test]
369 fn test_merge_nested() {
370 check_assist(
371 merge_imports,
372 r"
373use std::{fmt$0::Debug, fmt::Error};
374use std::{fmt::Write, fmt::Display};
375",
376 r"
377use std::fmt::{Debug, Display, Error, Write};
378",
379 );
380 }
381
382 #[test]
383 fn test_merge_nested2() {
384 check_assist(
385 merge_imports,
386 r"
387use std::{fmt::Debug, fmt$0::Error};
388use std::{fmt::Write, fmt::Display};
389",
390 r"
391use std::fmt::{Debug, Display, Error, Write};
392",
393 );
394 }
395
396 #[test]
397 fn test_merge_with_nested_self_item() {
398 check_assist(
399 merge_imports,
400 r"
401use std$0::{fmt::{Write, Display}};
402use std::{fmt::{self, Debug}};
403",
404 r"
405use std::fmt::{self, Debug, Display, Write};
406",
407 );
408 check_assist_import_one_variations!(
409 "std$0::{fmt::{Write, Display}}",
410 "std::{fmt::{self, Debug}}",
411 "use {std::fmt::{self, Debug, Display, Write}};"
412 );
413 }
414
415 #[test]
416 fn test_merge_with_nested_self_item2() {
417 check_assist(
418 merge_imports,
419 r"
420use std$0::{fmt::{self, Debug}};
421use std::{fmt::{Write, Display}};
422",
423 r"
424use std::fmt::{self, Debug, Display, Write};
425",
426 );
427 check_assist_import_one_variations!(
428 "std$0::{fmt::{self, Debug}}",
429 "std::{fmt::{Write, Display}}",
430 "use {std::fmt::{self, Debug, Display, Write}};"
431 );
432 }
433
434 #[test]
435 fn test_merge_nested_self_and_empty() {
436 check_assist(
437 merge_imports,
438 r"
439use foo::$0{bar::{self}};
440use foo::{bar};
441",
442 r"
443use foo::bar;
444",
445 );
446 check_assist_import_one_variations!(
447 "foo::$0{bar::{self}}",
448 "foo::{bar}",
449 "use {foo::bar};"
450 );
451 }
452
453 #[test]
454 fn test_merge_nested_empty_and_self() {
455 check_assist(
456 merge_imports,
457 r"
458use foo::$0{bar};
459use foo::{bar::{self}};
460",
461 r"
462use foo::bar;
463",
464 );
465 check_assist_import_one_variations!(
466 "foo::$0{bar}",
467 "foo::{bar::{self}}",
468 "use {foo::bar};"
469 );
470 }
471
472 #[test]
473 fn test_merge_nested_empty_and_self_with_other() {
474 check_assist(
475 merge_imports,
476 r"
477use foo::$0{bar};
478use foo::{bar::{self, other}};
479",
480 r"
481use foo::bar::{self, other};
482",
483 );
484 check_assist_import_one_variations!(
485 "foo::$0{bar}",
486 "foo::{bar::{self, other}}",
487 "use {foo::bar::{self, other}};"
488 );
489 }
490
491 #[test]
492 fn test_merge_nested_list_self_and_glob() {
493 check_assist(
494 merge_imports,
495 r"
496use std$0::{fmt::*};
497use std::{fmt::{self, Display}};
498",
499 r"
500use std::fmt::{self, Display, *};
501",
502 );
503 check_assist_import_one_variations!(
504 "std$0::{fmt::*}",
505 "std::{fmt::{self, Display}}",
506 "use {std::fmt::{self, Display, *}};"
507 );
508 }
509
510 #[test]
511 fn test_merge_single_wildcard_diff_prefixes() {
512 check_assist(
513 merge_imports,
514 r"
515use std$0::cell::*;
516use std::str;
517",
518 r"
519use std::{cell::*, str};
520",
521 );
522 check_assist_import_one_variations!(
523 "std$0::cell::*",
524 "std::str",
525 "use {std::{cell::*, str}};"
526 );
527 }
528
529 #[test]
530 fn test_merge_both_wildcard_diff_prefixes() {
531 check_assist(
532 merge_imports,
533 r"
534use std$0::cell::*;
535use std::str::*;
536",
537 r"
538use std::{cell::*, str::*};
539",
540 );
541 check_assist_import_one_variations!(
542 "std$0::cell::*",
543 "std::str::*",
544 "use {std::{cell::*, str::*}};"
545 );
546 }
547
548 #[test]
549 fn removes_just_enough_whitespace() {
550 check_assist(
551 merge_imports,
552 r"
553use foo$0::bar;
554use foo::baz;
555
556/// Doc comment
557",
558 r"
559use foo::{bar, baz};
560
561/// Doc comment
562",
563 );
564 }
565
566 #[test]
567 fn works_with_trailing_comma() {
568 check_assist(
569 merge_imports,
570 r"
571use foo$0::{
572 bar, baz,
573};
574use foo::qux;
575",
576 r"
577use foo::{
578 bar, baz, qux,
579};
580",
581 );
582 check_assist(
583 merge_imports,
584 r"
585use foo::{
586 baz, bar,
587};
588use foo$0::qux;
589",
590 r"
591use foo::{bar, baz, qux};
592",
593 );
594 }
595
596 #[test]
597 fn test_double_comma() {
598 check_assist(
599 merge_imports,
600 r"
601use foo::bar::baz;
602use foo::$0{
603 FooBar,
604};
605",
606 r"
607use foo::{
608 FooBar, bar::baz,
609};
610",
611 )
612 }
613
614 #[test]
615 fn test_empty_use() {
616 check_assist_not_applicable(
617 merge_imports,
618 r"
619use std::$0
620fn main() {}",
621 );
622 }
623
624 #[test]
625 fn split_glob() {
626 check_assist(
627 merge_imports,
628 r"
629use foo::$0*;
630use foo::bar::Baz;
631",
632 r"
633use foo::{bar::Baz, *};
634",
635 );
636 check_assist_import_one_variations!(
637 "foo::$0*",
638 "foo::bar::Baz",
639 "use {foo::{bar::Baz, *}};"
640 );
641 }
642
643 #[test]
644 fn merge_selection_uses() {
645 cov_mark::check!(merge_with_selected_use_item_neighbors);
646 check_assist(
647 merge_imports,
648 r"
649use std::fmt::Error;
650$0use std::fmt::Display;
651use std::fmt::Debug;
652use std::fmt::Write;
653$0use std::fmt::Result;
654",
655 r"
656use std::fmt::Error;
657use std::fmt::{Debug, Display, Write};
658use std::fmt::Result;
659",
660 );
661
662 cov_mark::check!(merge_with_selected_use_item_neighbors);
663 check_assist_import_one(
664 merge_imports,
665 r"
666use std::fmt::Error;
667$0use std::fmt::Display;
668use std::fmt::Debug;
669use std::fmt::Write;
670$0use std::fmt::Result;
671",
672 r"
673use std::fmt::Error;
674use {std::fmt::{Debug, Display, Write}};
675use std::fmt::Result;
676",
677 );
678 }
679
680 #[test]
681 fn merge_selection_use_trees() {
682 cov_mark::check!(merge_with_selected_use_tree_neighbors);
683 check_assist(
684 merge_imports,
685 r"
686use std::{
687 fmt::Error,
688 $0fmt::Display,
689 fmt::Debug,
690 fmt::Write,$0
691 fmt::Result,
692};",
693 r"
694use std::{
695 fmt::Error,
696 fmt::{Debug, Display, Write},
697 fmt::Result,
698};",
699 );
700
701 cov_mark::check!(merge_with_selected_use_tree_neighbors);
702 check_assist(
703 merge_imports,
704 r"use std::{fmt::Result, $0fmt::Display, fmt::Debug$0};",
705 r"use std::{fmt::Result, fmt::{Debug, Display}};",
706 );
707
708 cov_mark::check!(merge_with_selected_use_tree_neighbors);
709 check_assist(
710 merge_imports,
711 r"use std::$0{fmt::Display, fmt::Debug}$0;",
712 r"use std::{fmt::{Debug, Display}};",
713 );
714 }
715
716 #[test]
717 fn test_merge_with_synonymous_imports_1() {
718 check_assist(
719 merge_imports,
720 r"
721mod top {
722 pub(crate) mod a {
723 pub(crate) struct A;
724 }
725 pub(crate) mod b {
726 pub(crate) struct B;
727 pub(crate) struct D;
728 }
729}
730
731use top::a::A;
732use $0top::b::{B, B as C};
733",
734 r"
735mod top {
736 pub(crate) mod a {
737 pub(crate) struct A;
738 }
739 pub(crate) mod b {
740 pub(crate) struct B;
741 pub(crate) struct D;
742 }
743}
744
745use top::{a::A, b::{B, B as C}};
746",
747 );
748 }
749
750 #[test]
751 fn test_merge_with_synonymous_imports_2() {
752 check_assist(
753 merge_imports,
754 r"
755mod top {
756 pub(crate) mod a {
757 pub(crate) struct A;
758 }
759 pub(crate) mod b {
760 pub(crate) struct B;
761 pub(crate) struct D;
762 }
763}
764
765use top::a::A;
766use $0top::b::{B as D, B as C};
767",
768 r"
769mod top {
770 pub(crate) mod a {
771 pub(crate) struct A;
772 }
773 pub(crate) mod b {
774 pub(crate) struct B;
775 pub(crate) struct D;
776 }
777}
778
779use top::{a::A, b::{B as D, B as C}};
780",
781 );
782 }
783}