ide_assists/handlers/
merge_imports.rs

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
22// Assist: merge_imports
23//
24// Merges neighbor imports with a common prefix.
25//
26// ```
27// use std::$0fmt::Formatter;
28// use std::io;
29// ```
30// ->
31// ```
32// use std::{fmt::Formatter, io};
33// ```
34pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
35    let (target, edits) = if ctx.has_empty_selection() {
36        // Merge a neighbor
37        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        // Merge selected
47        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        // The assist macro below calls `check_assist_import_one` 4 times with different input
207        // use item variations based on the first 2 input parameters.
208        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}