diff options
-rw-r--r-- | crates/ra_assists/src/assists/merge_match_arms.rs | 127 |
1 files changed, 91 insertions, 36 deletions
diff --git a/crates/ra_assists/src/assists/merge_match_arms.rs b/crates/ra_assists/src/assists/merge_match_arms.rs index aca391155..64c9379da 100644 --- a/crates/ra_assists/src/assists/merge_match_arms.rs +++ b/crates/ra_assists/src/assists/merge_match_arms.rs | |||
@@ -1,6 +1,12 @@ | |||
1 | use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; | 1 | use std::iter::successors; |
2 | |||
2 | use hir::db::HirDatabase; | 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 | // Assist: merge_match_arms | 11 | // Assist: merge_match_arms |
6 | // | 12 | // |
@@ -27,62 +33,80 @@ use ra_syntax::ast::{AstNode, MatchArm}; | |||
27 | // } | 33 | // } |
28 | // ``` | 34 | // ``` |
29 | pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 35 | pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
30 | let current_arm = ctx.find_node_at_offset::<MatchArm>()?; | 36 | let current_arm = ctx.find_node_at_offset::<ast::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 | |||
37 | // 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 |
38 | if current_arm.guard().is_some() || next_arm.guard().is_some() { | 38 | if current_arm.guard().is_some() { |
39 | return None; | 39 | return None; |
40 | } | 40 | } |
41 | |||
42 | let current_expr = current_arm.expr()?; | 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 | 44 | enum CursorPos { |
46 | if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() { | 45 | InExpr(TextUnit), |
47 | return None; | 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 | return None; | 70 | return None; |
51 | } | 71 | } |
52 | 72 | ||
53 | let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start(); | ||
54 | |||
55 | ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| { | 73 | ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| { |
56 | fn contains_placeholder(a: &MatchArm) -> bool { | 74 | let pats = if arms_to_merge.iter().any(contains_placeholder) { |
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(¤t_arm) || contains_placeholder(&next_arm) { | ||
64 | "_".into() | 75 | "_".into() |
65 | } else { | 76 | } else { |
66 | let ps: Vec<String> = current_arm | 77 | arms_to_merge |
67 | .pats() | 78 | .iter() |
79 | .flat_map(ast::MatchArm::pats) | ||
68 | .map(|x| x.syntax().to_string()) | 80 | .map(|x| x.syntax().to_string()) |
69 | .chain(next_arm.pats().map(|x| x.syntax().to_string())) | 81 | .collect::<Vec<String>>() |
70 | .collect(); | 82 | .join(" | ") |
71 | ps.join(" | ") | ||
72 | }; | 83 | }; |
73 | 84 | ||
74 | let arm = format!("{} => {}", pats, current_expr.syntax().text()); | 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(); | 87 | let start = arms_to_merge.first().unwrap().syntax().text_range().start(); |
78 | let end = next_arm.syntax().text_range().end(); | 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 | edit.replace(TextRange::from_to(start, end), arm); | 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 | #[cfg(test)] | 110 | #[cfg(test)] |
87 | mod tests { | 111 | mod tests { |
88 | use super::merge_match_arms; | 112 | use super::merge_match_arms; |
@@ -185,6 +209,37 @@ mod tests { | |||
185 | } | 209 | } |
186 | 210 | ||
187 | #[test] | 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 | |||
242 | #[test] | ||
188 | fn merge_match_arms_rejects_guards() { | 243 | fn merge_match_arms_rejects_guards() { |
189 | check_assist_not_applicable( | 244 | check_assist_not_applicable( |
190 | merge_match_arms, | 245 | merge_match_arms, |