aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/assists/merge_match_arms.rs99
1 files changed, 70 insertions, 29 deletions
diff --git a/crates/ra_assists/src/assists/merge_match_arms.rs b/crates/ra_assists/src/assists/merge_match_arms.rs
index 885d5036d..8af30866c 100644
--- a/crates/ra_assists/src/assists/merge_match_arms.rs
+++ b/crates/ra_assists/src/assists/merge_match_arms.rs
@@ -1,7 +1,12 @@
1use std::iter::successors;
2
1use hir::db::HirDatabase; 3use hir::db::HirDatabase;
2use ra_syntax::ast::{self, AstNode}; 4use ra_syntax::{
5 ast::{self, AstNode},
6 Direction, TextUnit,
7};
3 8
4use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; 9use crate::{Assist, AssistCtx, AssistId, TextRange};
5 10
6// Assist: merge_match_arms 11// Assist: merge_match_arms
7// 12//
@@ -29,51 +34,52 @@ use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit};
29// ``` 34// ```
30pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 35pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
31 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?; 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 // Don't try to handle arms with guards for now - can add support for this later 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 return None; 39 return None;
41 } 40 }
42
43 let current_expr = current_arm.expr()?; 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 45 // We check if the following match arms match this one. We could, but don't,
47 if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() { 46 // compare to the previous match arm as well.
48 return None; 47 let arms_to_merge = successors(Some(current_arm), next_arm)
49 } 48 .take_while(|arm| {
50 if current_expr.syntax().text() != next_expr.syntax().text() { 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 return None; 60 return None;
52 } 61 }
53 62
54 let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start();
55
56 ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| { 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 "_".into() 65 "_".into()
59 } else { 66 } else {
60 let ps: Vec<String> = current_arm 67 arms_to_merge
61 .pats() 68 .iter()
69 .flat_map(ast::MatchArm::pats)
62 .map(|x| x.syntax().to_string()) 70 .map(|x| x.syntax().to_string())
63 .chain(next_arm.pats().map(|x| x.syntax().to_string())) 71 .collect::<Vec<String>>()
64 .collect(); 72 .join(" | ")
65 ps.join(" | ")
66 }; 73 };
67 74
68 let arm = format!("{} => {}", pats, current_expr.syntax().text()); 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(); 77 let start = arms_to_merge.first().unwrap().syntax().text_range().start();
72 let end = next_arm.syntax().text_range().end(); 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 edit.replace(TextRange::from_to(start, end), arm); 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
93fn next_arm(arm: &ast::MatchArm) -> Option<ast::MatchArm> {
94 arm.syntax().siblings(Direction::Next).skip(1).find_map(ast::MatchArm::cast)
95}
96
87#[cfg(test)] 97#[cfg(test)]
88mod tests { 98mod tests {
89 use super::merge_match_arms; 99 use super::merge_match_arms;
@@ -186,6 +196,37 @@ mod tests {
186 } 196 }
187 197
188 #[test] 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
229 #[test]
189 fn merge_match_arms_rejects_guards() { 230 fn merge_match_arms_rejects_guards() {
190 check_assist_not_applicable( 231 check_assist_not_applicable(
191 merge_match_arms, 232 merge_match_arms,