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