diff options
Diffstat (limited to 'crates/ra_assists')
-rw-r--r-- | crates/ra_assists/src/assists/merge_match_arms.rs | 127 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/replace_if_let_with_match.rs | 49 |
2 files changed, 113 insertions, 63 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, |
diff --git a/crates/ra_assists/src/assists/replace_if_let_with_match.rs b/crates/ra_assists/src/assists/replace_if_let_with_match.rs index c9b62e5ff..c8b13b7b3 100644 --- a/crates/ra_assists/src/assists/replace_if_let_with_match.rs +++ b/crates/ra_assists/src/assists/replace_if_let_with_match.rs | |||
@@ -1,9 +1,12 @@ | |||
1 | use format_buf::format; | ||
2 | use hir::db::HirDatabase; | 1 | use hir::db::HirDatabase; |
3 | use ra_fmt::extract_trivial_expression; | 2 | use ra_fmt::unwrap_trivial_block; |
4 | use ra_syntax::{ast, AstNode}; | 3 | use ra_syntax::{ |
4 | ast::{self, make}, | ||
5 | AstNode, | ||
6 | }; | ||
5 | 7 | ||
6 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
9 | use ast::edit::IndentLevel; | ||
7 | 10 | ||
8 | // Assist: replace_if_let_with_match | 11 | // Assist: replace_if_let_with_match |
9 | // | 12 | // |
@@ -43,32 +46,24 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Opt | |||
43 | }; | 46 | }; |
44 | 47 | ||
45 | ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", |edit| { | 48 | ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", |edit| { |
46 | let match_expr = build_match_expr(expr, pat, then_block, else_block); | 49 | let match_expr = { |
47 | edit.target(if_expr.syntax().text_range()); | 50 | let then_arm = { |
48 | edit.replace_node_and_indent(if_expr.syntax(), match_expr); | 51 | let then_expr = unwrap_trivial_block(then_block); |
49 | edit.set_cursor(if_expr.syntax().text_range().start()) | 52 | make::match_arm(vec![pat], then_expr) |
50 | }) | 53 | }; |
51 | } | 54 | let else_arm = { |
55 | let else_expr = unwrap_trivial_block(else_block); | ||
56 | make::match_arm(vec![make::placeholder_pat().into()], else_expr) | ||
57 | }; | ||
58 | make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) | ||
59 | }; | ||
52 | 60 | ||
53 | fn build_match_expr( | 61 | let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr); |
54 | expr: ast::Expr, | ||
55 | pat1: ast::Pat, | ||
56 | arm1: ast::BlockExpr, | ||
57 | arm2: ast::BlockExpr, | ||
58 | ) -> String { | ||
59 | let mut buf = String::new(); | ||
60 | format!(buf, "match {} {{\n", expr.syntax().text()); | ||
61 | format!(buf, " {} => {}\n", pat1.syntax().text(), format_arm(&arm1)); | ||
62 | format!(buf, " _ => {}\n", format_arm(&arm2)); | ||
63 | buf.push_str("}"); | ||
64 | buf | ||
65 | } | ||
66 | 62 | ||
67 | fn format_arm(block: &ast::BlockExpr) -> String { | 63 | edit.target(if_expr.syntax().text_range()); |
68 | match extract_trivial_expression(block) { | 64 | edit.set_cursor(if_expr.syntax().text_range().start()); |
69 | Some(e) if !e.syntax().text().contains_char('\n') => format!("{},", e.syntax().text()), | 65 | edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr.into()); |
70 | _ => block.syntax().text().to_string(), | 66 | }) |
71 | } | ||
72 | } | 67 | } |
73 | 68 | ||
74 | #[cfg(test)] | 69 | #[cfg(test)] |