1
+ use either:: Either ;
1
2
use ide_db:: imports:: merge_imports:: { try_merge_imports, try_merge_trees, MergeBehavior } ;
2
- use syntax:: { algo:: neighbor, ast, ted, AstNode } ;
3
+ use syntax:: { algo:: neighbor, ast, match_ast , ted, AstNode , SyntaxElement , SyntaxNode } ;
3
4
4
5
use crate :: {
5
6
assist_context:: { AssistContext , Assists } ,
6
7
utils:: next_prev,
7
8
AssistId , AssistKind ,
8
9
} ;
9
10
11
+ use Edit :: * ;
12
+
10
13
// Assist: merge_imports
11
14
//
12
15
// Merges two imports with a common prefix.
@@ -20,51 +23,115 @@ use crate::{
20
23
// use std::{fmt::Formatter, io};
21
24
// ```
22
25
pub ( crate ) fn merge_imports ( acc : & mut Assists , ctx : & AssistContext ) -> Option < ( ) > {
23
- let tree: ast:: UseTree = ctx. find_node_at_offset ( ) ?;
24
-
25
- let mut imports = None ;
26
- let mut uses = None ;
27
- if let Some ( use_item) = tree. syntax ( ) . parent ( ) . and_then ( ast:: Use :: cast) {
28
- let ( merged, to_remove) =
29
- next_prev ( ) . filter_map ( |dir| neighbor ( & use_item, dir) ) . find_map ( |use_item2| {
30
- try_merge_imports ( & use_item, & use_item2, MergeBehavior :: Crate ) . zip ( Some ( use_item2) )
31
- } ) ?;
32
-
33
- imports = Some ( ( use_item, merged, to_remove) ) ;
26
+ let ( target, edits) = if ctx. has_empty_selection ( ) {
27
+ // Merge a neighbor
28
+ let tree: ast:: UseTree = ctx. find_node_at_offset ( ) ?;
29
+ let target = tree. syntax ( ) . text_range ( ) ;
30
+
31
+ let edits = if let Some ( use_item) = tree. syntax ( ) . parent ( ) . and_then ( ast:: Use :: cast) {
32
+ let mut neighbor = next_prev ( ) . find_map ( |dir| neighbor ( & use_item, dir) ) . into_iter ( ) ;
33
+ use_item. try_merge_from ( & mut neighbor)
34
+ } else {
35
+ let mut neighbor = next_prev ( ) . find_map ( |dir| neighbor ( & tree, dir) ) . into_iter ( ) ;
36
+ tree. try_merge_from ( & mut neighbor)
37
+ } ;
38
+ ( target, edits?)
34
39
} else {
35
- let ( merged, to_remove) =
36
- next_prev ( ) . filter_map ( |dir| neighbor ( & tree, dir) ) . find_map ( |use_tree| {
37
- try_merge_trees ( & tree, & use_tree, MergeBehavior :: Crate ) . zip ( Some ( use_tree) )
38
- } ) ?;
39
-
40
- uses = Some ( ( tree. clone ( ) , merged, to_remove) )
40
+ // Merge selected
41
+ let selection_range = ctx. selection_trimmed ( ) ;
42
+ let parent_node = match ctx. covering_element ( ) {
43
+ SyntaxElement :: Node ( n) => n,
44
+ SyntaxElement :: Token ( t) => t. parent ( ) ?,
45
+ } ;
46
+ let mut selected_nodes =
47
+ parent_node. children ( ) . filter ( |it| selection_range. contains_range ( it. text_range ( ) ) ) ;
48
+
49
+ let first_selected = selected_nodes. next ( ) ?;
50
+ let edits = match_ast ! {
51
+ match first_selected {
52
+ ast:: Use ( use_item) => {
53
+ use_item. try_merge_from( & mut selected_nodes. filter_map( ast:: Use :: cast) )
54
+ } ,
55
+ ast:: UseTree ( use_tree) => {
56
+ use_tree. try_merge_from( & mut selected_nodes. filter_map( ast:: UseTree :: cast) )
57
+ } ,
58
+ _ => return None ,
59
+ }
60
+ } ;
61
+ ( selection_range, edits?)
41
62
} ;
42
63
43
- let target = tree. syntax ( ) . text_range ( ) ;
44
64
acc. add (
45
65
AssistId ( "merge_imports" , AssistKind :: RefactorRewrite ) ,
46
66
"Merge imports" ,
47
67
target,
48
68
|builder| {
49
- if let Some ( ( to_replace, replacement, to_remove) ) = imports {
50
- let to_replace = builder. make_mut ( to_replace) ;
51
- let to_remove = builder. make_mut ( to_remove) ;
52
-
53
- ted:: replace ( to_replace. syntax ( ) , replacement. syntax ( ) ) ;
54
- to_remove. remove ( ) ;
55
- }
56
-
57
- if let Some ( ( to_replace, replacement, to_remove) ) = uses {
58
- let to_replace = builder. make_mut ( to_replace) ;
59
- let to_remove = builder. make_mut ( to_remove) ;
60
-
61
- ted:: replace ( to_replace. syntax ( ) , replacement. syntax ( ) ) ;
62
- to_remove. remove ( )
69
+ let edits_mut: Vec < Edit > = edits
70
+ . into_iter ( )
71
+ . map ( |it| match it {
72
+ Remove ( Either :: Left ( it) ) => Remove ( Either :: Left ( builder. make_mut ( it) ) ) ,
73
+ Remove ( Either :: Right ( it) ) => Remove ( Either :: Right ( builder. make_mut ( it) ) ) ,
74
+ Replace ( old, new) => Replace ( builder. make_syntax_mut ( old) , new) ,
75
+ } )
76
+ . collect ( ) ;
77
+ for edit in edits_mut {
78
+ match edit {
79
+ Remove ( it) => it. as_ref ( ) . either ( ast:: Use :: remove, ast:: UseTree :: remove) ,
80
+ Replace ( old, new) => ted:: replace ( old, new) ,
81
+ }
63
82
}
64
83
} ,
65
84
)
66
85
}
67
86
87
+ trait Merge : AstNode + Clone {
88
+ fn try_merge_from ( self , items : & mut dyn Iterator < Item = Self > ) -> Option < Vec < Edit > > {
89
+ let mut edits = Vec :: new ( ) ;
90
+ let mut merged = self . clone ( ) ;
91
+ while let Some ( item) = items. next ( ) {
92
+ merged = merged. try_merge ( & item) ?;
93
+ edits. push ( Edit :: Remove ( item. into_either ( ) ) ) ;
94
+ }
95
+ if !edits. is_empty ( ) {
96
+ edits. push ( Edit :: replace ( self , merged) ) ;
97
+ Some ( edits)
98
+ } else {
99
+ None
100
+ }
101
+ }
102
+ fn try_merge ( & self , other : & Self ) -> Option < Self > ;
103
+ fn into_either ( self ) -> Either < ast:: Use , ast:: UseTree > ;
104
+ }
105
+
106
+ impl Merge for ast:: Use {
107
+ fn try_merge ( & self , other : & Self ) -> Option < Self > {
108
+ try_merge_imports ( self , other, MergeBehavior :: Crate )
109
+ }
110
+ fn into_either ( self ) -> Either < ast:: Use , ast:: UseTree > {
111
+ Either :: Left ( self )
112
+ }
113
+ }
114
+
115
+ impl Merge for ast:: UseTree {
116
+ fn try_merge ( & self , other : & Self ) -> Option < Self > {
117
+ try_merge_trees ( self , other, MergeBehavior :: Crate )
118
+ }
119
+ fn into_either ( self ) -> Either < ast:: Use , ast:: UseTree > {
120
+ Either :: Right ( self )
121
+ }
122
+ }
123
+
124
+ enum Edit {
125
+ Remove ( Either < ast:: Use , ast:: UseTree > ) ,
126
+ Replace ( SyntaxNode , SyntaxNode ) ,
127
+ }
128
+
129
+ impl Edit {
130
+ fn replace ( old : impl AstNode , new : impl AstNode ) -> Self {
131
+ Edit :: Replace ( old. syntax ( ) . clone ( ) , new. syntax ( ) . clone ( ) )
132
+ }
133
+ }
134
+
68
135
#[ cfg( test) ]
69
136
mod tests {
70
137
use crate :: tests:: { check_assist, check_assist_not_applicable} ;
@@ -454,4 +521,50 @@ use foo::{*, bar::Baz};
454
521
" ,
455
522
) ;
456
523
}
524
+
525
+ #[ test]
526
+ fn merge_selection_uses ( ) {
527
+ check_assist (
528
+ merge_imports,
529
+ r"
530
+ use std::fmt::Error;
531
+ $0use std::fmt::Display;
532
+ use std::fmt::Debug;
533
+ use std::fmt::Write;
534
+ $0use std::fmt::Result;
535
+ " ,
536
+ r"
537
+ use std::fmt::Error;
538
+ use std::fmt::{Display, Debug, Write};
539
+ use std::fmt::Result;
540
+ " ,
541
+ ) ;
542
+ }
543
+
544
+ #[ test]
545
+ fn merge_selection_use_trees ( ) {
546
+ check_assist (
547
+ merge_imports,
548
+ r"
549
+ use std::{
550
+ fmt::Error,
551
+ $0fmt::Display,
552
+ fmt::Debug,
553
+ fmt::Write,$0
554
+ fmt::Result,
555
+ };" ,
556
+ r"
557
+ use std::{
558
+ fmt::Error,
559
+ fmt::{Display, Debug, Write},
560
+ fmt::Result,
561
+ };" ,
562
+ ) ;
563
+ // FIXME: Remove redundant braces. See also unnecessary-braces diagnostic.
564
+ check_assist (
565
+ merge_imports,
566
+ r"use std::$0{fmt::Display, fmt::Debug}$0;" ,
567
+ r"use std::{fmt::{Display, Debug}};" ,
568
+ ) ;
569
+ }
457
570
}
0 commit comments