1
- use crate :: { Assist , AssistCtx , AssistId , TextRange , TextUnit } ;
1
+ use std:: iter:: successors;
2
+
2
3
use hir:: db:: HirDatabase ;
3
- use ra_syntax:: ast:: { AstNode , MatchArm } ;
4
+ use ra_syntax:: {
5
+ ast:: { self , AstNode } ,
6
+ Direction , TextUnit ,
7
+ } ;
8
+
9
+ use crate :: { Assist , AssistCtx , AssistId , TextRange } ;
4
10
5
11
// Assist: merge_match_arms
6
12
//
@@ -27,62 +33,80 @@ use ra_syntax::ast::{AstNode, MatchArm};
27
33
// }
28
34
// ```
29
35
pub ( crate ) fn merge_match_arms ( ctx : AssistCtx < impl HirDatabase > ) -> Option < Assist > {
30
- let current_arm = ctx. find_node_at_offset :: < MatchArm > ( ) ?;
31
-
32
- // We check if the following match arm matches this one. We could, but don't,
33
- // compare to the previous match arm as well.
34
- let next = current_arm. syntax ( ) . next_sibling ( ) ;
35
- let next_arm = MatchArm :: cast ( next?) ?;
36
-
36
+ let current_arm = ctx. find_node_at_offset :: < ast:: MatchArm > ( ) ?;
37
37
// Don't try to handle arms with guards for now - can add support for this later
38
- if current_arm. guard ( ) . is_some ( ) || next_arm . guard ( ) . is_some ( ) {
38
+ if current_arm. guard ( ) . is_some ( ) {
39
39
return None ;
40
40
}
41
-
42
41
let current_expr = current_arm. expr ( ) ?;
43
- let next_expr = next_arm . expr ( ) ? ;
42
+ let current_text_range = current_arm . syntax ( ) . text_range ( ) ;
44
43
45
- // Check for match arm equality by comparing lengths and then string contents
46
- if current_expr . syntax ( ) . text_range ( ) . len ( ) != next_expr . syntax ( ) . text_range ( ) . len ( ) {
47
- return None ;
44
+ enum CursorPos {
45
+ InExpr ( TextUnit ) ,
46
+ InPat ( TextUnit ) ,
48
47
}
49
- if current_expr. syntax ( ) . text ( ) != next_expr. syntax ( ) . text ( ) {
48
+ let cursor_pos = ctx. frange . range . start ( ) ;
49
+ let cursor_pos = if current_expr. syntax ( ) . text_range ( ) . contains ( cursor_pos) {
50
+ CursorPos :: InExpr ( current_text_range. end ( ) - cursor_pos)
51
+ } else {
52
+ CursorPos :: InPat ( cursor_pos)
53
+ } ;
54
+
55
+ // We check if the following match arms match this one. We could, but don't,
56
+ // compare to the previous match arm as well.
57
+ let arms_to_merge = successors ( Some ( current_arm) , next_arm)
58
+ . take_while ( |arm| {
59
+ if arm. guard ( ) . is_some ( ) {
60
+ return false ;
61
+ }
62
+ match arm. expr ( ) {
63
+ Some ( expr) => expr. syntax ( ) . text ( ) == current_expr. syntax ( ) . text ( ) ,
64
+ None => false ,
65
+ }
66
+ } )
67
+ . collect :: < Vec < _ > > ( ) ;
68
+
69
+ if arms_to_merge. len ( ) <= 1 {
50
70
return None ;
51
71
}
52
72
53
- let cursor_to_end = current_arm. syntax ( ) . text_range ( ) . end ( ) - ctx. frange . range . start ( ) ;
54
-
55
73
ctx. add_assist ( AssistId ( "merge_match_arms" ) , "Merge match arms" , |edit| {
56
- fn contains_placeholder ( a : & MatchArm ) -> bool {
57
- a. pats ( ) . any ( |x| match x {
58
- ra_syntax:: ast:: Pat :: PlaceholderPat ( ..) => true ,
59
- _ => false ,
60
- } )
61
- }
62
-
63
- let pats = if contains_placeholder ( & current_arm) || contains_placeholder ( & next_arm) {
74
+ let pats = if arms_to_merge. iter ( ) . any ( contains_placeholder) {
64
75
"_" . into ( )
65
76
} else {
66
- let ps: Vec < String > = current_arm
67
- . pats ( )
77
+ arms_to_merge
78
+ . iter ( )
79
+ . flat_map ( ast:: MatchArm :: pats)
68
80
. map ( |x| x. syntax ( ) . to_string ( ) )
69
- . chain ( next_arm. pats ( ) . map ( |x| x. syntax ( ) . to_string ( ) ) )
70
- . collect ( ) ;
71
- ps. join ( " | " )
81
+ . collect :: < Vec < String > > ( )
82
+ . join ( " | " )
72
83
} ;
73
84
74
85
let arm = format ! ( "{} => {}" , pats, current_expr. syntax( ) . text( ) ) ;
75
- let offset = TextUnit :: from_usize ( arm. len ( ) ) - cursor_to_end;
76
86
77
- let start = current_arm . syntax ( ) . text_range ( ) . start ( ) ;
78
- let end = next_arm . syntax ( ) . text_range ( ) . end ( ) ;
87
+ let start = arms_to_merge . first ( ) . unwrap ( ) . syntax ( ) . text_range ( ) . start ( ) ;
88
+ let end = arms_to_merge . last ( ) . unwrap ( ) . syntax ( ) . text_range ( ) . end ( ) ;
79
89
80
- edit. target ( current_arm. syntax ( ) . text_range ( ) ) ;
90
+ edit. target ( current_text_range) ;
91
+ edit. set_cursor ( match cursor_pos {
92
+ CursorPos :: InExpr ( back_offset) => start + TextUnit :: from_usize ( arm. len ( ) ) - back_offset,
93
+ CursorPos :: InPat ( offset) => offset,
94
+ } ) ;
81
95
edit. replace ( TextRange :: from_to ( start, end) , arm) ;
82
- edit. set_cursor ( start + offset) ;
83
96
} )
84
97
}
85
98
99
+ fn contains_placeholder ( a : & ast:: MatchArm ) -> bool {
100
+ a. pats ( ) . any ( |x| match x {
101
+ ra_syntax:: ast:: Pat :: PlaceholderPat ( ..) => true ,
102
+ _ => false ,
103
+ } )
104
+ }
105
+
106
+ fn next_arm ( arm : & ast:: MatchArm ) -> Option < ast:: MatchArm > {
107
+ arm. syntax ( ) . siblings ( Direction :: Next ) . skip ( 1 ) . find_map ( ast:: MatchArm :: cast)
108
+ }
109
+
86
110
#[ cfg( test) ]
87
111
mod tests {
88
112
use super :: merge_match_arms;
@@ -184,6 +208,37 @@ mod tests {
184
208
) ;
185
209
}
186
210
211
+ #[ test]
212
+ fn merges_all_subsequent_arms ( ) {
213
+ check_assist (
214
+ merge_match_arms,
215
+ r#"
216
+ enum X { A, B, C, D, E }
217
+
218
+ fn main() {
219
+ match X::A {
220
+ X::A<|> => 92,
221
+ X::B => 92,
222
+ X::C => 92,
223
+ X::D => 62,
224
+ _ => panic!(),
225
+ }
226
+ }
227
+ "# ,
228
+ r#"
229
+ enum X { A, B, C, D, E }
230
+
231
+ fn main() {
232
+ match X::A {
233
+ X::A<|> | X::B | X::C => 92,
234
+ X::D => 62,
235
+ _ => panic!(),
236
+ }
237
+ }
238
+ "# ,
239
+ )
240
+ }
241
+
187
242
#[ test]
188
243
fn merge_match_arms_rejects_guards ( ) {
189
244
check_assist_not_applicable (
0 commit comments