aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists')
-rw-r--r--crates/ra_assists/src/flip_comma.rs30
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_assists/src/merge_match_arms.rs188
3 files changed, 220 insertions, 0 deletions
diff --git a/crates/ra_assists/src/flip_comma.rs b/crates/ra_assists/src/flip_comma.rs
index 34489329c..5ee7561bc 100644
--- a/crates/ra_assists/src/flip_comma.rs
+++ b/crates/ra_assists/src/flip_comma.rs
@@ -7,6 +7,13 @@ pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist>
7 let comma = ctx.token_at_offset().find(|leaf| leaf.kind() == T![,])?; 7 let comma = ctx.token_at_offset().find(|leaf| leaf.kind() == T![,])?;
8 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; 8 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
9 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; 9 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
10
11 // Don't apply a "flip" in case of a last comma
12 // that typically comes before punctuation
13 if next.kind().is_punct() {
14 return None;
15 }
16
10 ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| { 17 ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| {
11 edit.target(comma.text_range()); 18 edit.target(comma.text_range());
12 edit.replace(prev.text_range(), next.to_string()); 19 edit.replace(prev.text_range(), next.to_string());
@@ -35,4 +42,27 @@ mod tests {
35 fn flip_comma_target() { 42 fn flip_comma_target() {
36 check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",") 43 check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",")
37 } 44 }
45
46 #[test]
47 #[should_panic]
48 fn flip_comma_before_punct() {
49 // See https://github.com/rust-analyzer/rust-analyzer/issues/1619
50 // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct
51 // declaration body.
52 check_assist_target(
53 flip_comma,
54 "pub enum Test { \
55 A,<|> \
56 }",
57 ",",
58 );
59
60 check_assist_target(
61 flip_comma,
62 "pub struct Test { \
63 foo: usize,<|> \
64 }",
65 ",",
66 );
67 }
38} 68}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 0d848629d..03eec73ad 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -93,6 +93,7 @@ mod flip_comma;
93mod flip_binexpr; 93mod flip_binexpr;
94mod change_visibility; 94mod change_visibility;
95mod fill_match_arms; 95mod fill_match_arms;
96mod merge_match_arms;
96mod introduce_variable; 97mod introduce_variable;
97mod inline_local_variable; 98mod inline_local_variable;
98mod replace_if_let_with_match; 99mod replace_if_let_with_match;
@@ -109,6 +110,7 @@ fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assis
109 add_impl::add_impl, 110 add_impl::add_impl,
110 change_visibility::change_visibility, 111 change_visibility::change_visibility,
111 fill_match_arms::fill_match_arms, 112 fill_match_arms::fill_match_arms,
113 merge_match_arms::merge_match_arms,
112 flip_comma::flip_comma, 114 flip_comma::flip_comma,
113 flip_binexpr::flip_binexpr, 115 flip_binexpr::flip_binexpr,
114 introduce_variable::introduce_variable, 116 introduce_variable::introduce_variable,
diff --git a/crates/ra_assists/src/merge_match_arms.rs b/crates/ra_assists/src/merge_match_arms.rs
new file mode 100644
index 000000000..bc5f6f17c
--- /dev/null
+++ b/crates/ra_assists/src/merge_match_arms.rs
@@ -0,0 +1,188 @@
1use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit};
2use hir::db::HirDatabase;
3use ra_syntax::ast::{AstNode, MatchArm};
4
5pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
6 let current_arm = ctx.node_at_offset::<MatchArm>()?;
7
8 // We check if the following match arm matches this one. We could, but don't,
9 // compare to the previous match arm as well.
10 let next = current_arm.syntax().next_sibling();
11 let next_arm = MatchArm::cast(next?.clone())?;
12
13 // Don't try to handle arms with guards for now - can add support for this later
14 if current_arm.guard().is_some() || next_arm.guard().is_some() {
15 return None;
16 }
17
18 let current_expr = current_arm.expr()?;
19 let next_expr = next_arm.expr()?;
20
21 // Check for match arm equality by comparing lengths and then string contents
22 if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() {
23 return None;
24 }
25 if current_expr.syntax().text() != next_expr.syntax().text() {
26 return None;
27 }
28
29 let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start();
30
31 ctx.add_action(AssistId("merge_match_arms"), "merge match arms", |edit| {
32 fn contains_placeholder(a: &MatchArm) -> bool {
33 a.pats().any(|x| match x.kind() {
34 ra_syntax::ast::PatKind::PlaceholderPat(..) => true,
35 _ => false,
36 })
37 }
38
39 let pats = if contains_placeholder(&current_arm) || contains_placeholder(&next_arm) {
40 "_".into()
41 } else {
42 let ps: Vec<String> = current_arm
43 .pats()
44 .map(|x| x.syntax().to_string())
45 .chain(next_arm.pats().map(|x| x.syntax().to_string()))
46 .collect();
47 ps.join(" | ")
48 };
49
50 let arm = format!("{} => {}", pats, current_expr.syntax().text());
51 let offset = TextUnit::from_usize(arm.len()) - cursor_to_end;
52
53 let start = current_arm.syntax().text_range().start();
54 let end = next_arm.syntax().text_range().end();
55
56 edit.target(current_arm.syntax().text_range());
57 edit.replace(TextRange::from_to(start, end), arm);
58 edit.set_cursor(start + offset);
59 });
60
61 ctx.build()
62}
63
64#[cfg(test)]
65mod tests {
66 use super::merge_match_arms;
67 use crate::helpers::{check_assist, check_assist_not_applicable};
68
69 #[test]
70 fn merge_match_arms_single_patterns() {
71 check_assist(
72 merge_match_arms,
73 r#"
74 #[derive(Debug)]
75 enum X { A, B, C }
76
77 fn main() {
78 let x = X::A;
79 let y = match x {
80 X::A => { 1i32<|> }
81 X::B => { 1i32 }
82 X::C => { 2i32 }
83 }
84 }
85 "#,
86 r#"
87 #[derive(Debug)]
88 enum X { A, B, C }
89
90 fn main() {
91 let x = X::A;
92 let y = match x {
93 X::A | X::B => { 1i32<|> }
94 X::C => { 2i32 }
95 }
96 }
97 "#,
98 );
99 }
100
101 #[test]
102 fn merge_match_arms_multiple_patterns() {
103 check_assist(
104 merge_match_arms,
105 r#"
106 #[derive(Debug)]
107 enum X { A, B, C, D, E }
108
109 fn main() {
110 let x = X::A;
111 let y = match x {
112 X::A | X::B => {<|> 1i32 },
113 X::C | X::D => { 1i32 },
114 X::E => { 2i32 },
115 }
116 }
117 "#,
118 r#"
119 #[derive(Debug)]
120 enum X { A, B, C, D, E }
121
122 fn main() {
123 let x = X::A;
124 let y = match x {
125 X::A | X::B | X::C | X::D => {<|> 1i32 },
126 X::E => { 2i32 },
127 }
128 }
129 "#,
130 );
131 }
132
133 #[test]
134 fn merge_match_arms_placeholder_pattern() {
135 check_assist(
136 merge_match_arms,
137 r#"
138 #[derive(Debug)]
139 enum X { A, B, C, D, E }
140
141 fn main() {
142 let x = X::A;
143 let y = match x {
144 X::A => { 1i32 },
145 X::B => { 2i<|>32 },
146 _ => { 2i32 }
147 }
148 }
149 "#,
150 r#"
151 #[derive(Debug)]
152 enum X { A, B, C, D, E }
153
154 fn main() {
155 let x = X::A;
156 let y = match x {
157 X::A => { 1i32 },
158 _ => { 2i<|>32 }
159 }
160 }
161 "#,
162 );
163 }
164
165 #[test]
166 fn merge_match_arms_rejects_guards() {
167 check_assist_not_applicable(
168 merge_match_arms,
169 r#"
170 #[derive(Debug)]
171 enum X {
172 A(i32),
173 B,
174 C
175 }
176
177 fn main() {
178 let x = X::A;
179 let y = match x {
180 X::A(a) if a > 5 => { <|>1i32 },
181 X::B => { 1i32 },
182 X::C => { 2i32 }
183 }
184 }
185 "#,
186 );
187 }
188}