diff options
author | Aleksey Kladov <[email protected]> | 2020-02-05 10:53:33 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-02-05 11:26:43 +0000 |
commit | 28acd01c638590a64a06148021ae60c0ccef8bb6 (patch) | |
tree | 14fcfce46985eb77539fd9d5445559911818f7e0 /crates/ra_assists | |
parent | 45dd90b0e8d54fe0467f9e0f886c7c05d2400eb0 (diff) |
Merge match arms works with many arms
Diffstat (limited to 'crates/ra_assists')
-rw-r--r-- | crates/ra_assists/src/assists/merge_match_arms.rs | 99 |
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 @@ | |||
1 | use std::iter::successors; | ||
2 | |||
1 | use hir::db::HirDatabase; | 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 | // Assist: merge_match_arms | 11 | // Assist: merge_match_arms |
7 | // | 12 | // |
@@ -29,51 +34,52 @@ use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; | |||
29 | // ``` | 34 | // ``` |
30 | 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> { |
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(¤t_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 | ||
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 | #[cfg(test)] | 97 | #[cfg(test)] |
88 | mod tests { | 98 | mod 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, |