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