aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/assists/merge_match_arms.rs127
-rw-r--r--crates/ra_assists/src/assists/replace_if_let_with_match.rs49
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 @@
1use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; 1use std::iter::successors;
2
2use hir::db::HirDatabase; 3use hir::db::HirDatabase;
3use ra_syntax::ast::{AstNode, MatchArm}; 4use ra_syntax::{
5 ast::{self, AstNode},
6 Direction, TextUnit,
7};
8
9use 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// ```
29pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 35pub(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(&current_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
99fn 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
106fn 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)]
87mod tests { 111mod 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 @@
1use format_buf::format;
2use hir::db::HirDatabase; 1use hir::db::HirDatabase;
3use ra_fmt::extract_trivial_expression; 2use ra_fmt::unwrap_trivial_block;
4use ra_syntax::{ast, AstNode}; 3use ra_syntax::{
4 ast::{self, make},
5 AstNode,
6};
5 7
6use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
9use 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
53fn 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
67fn 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)]