diff options
author | Aleksey Kladov <[email protected]> | 2020-02-07 14:53:31 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-02-07 16:28:02 +0000 |
commit | 561b4b11ff1d87ea1ff2477dcba6ae1f396573a3 (patch) | |
tree | 0da58d08d5a2ff27f43c3eb6163ba9aced2f5782 /crates/ra_assists/src/handlers/merge_match_arms.rs | |
parent | aa64a84b493aa9c0b22f36b472a445d622cd2172 (diff) |
Name assist handlers
Diffstat (limited to 'crates/ra_assists/src/handlers/merge_match_arms.rs')
-rw-r--r-- | crates/ra_assists/src/handlers/merge_match_arms.rs | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs new file mode 100644 index 000000000..670614dd8 --- /dev/null +++ b/crates/ra_assists/src/handlers/merge_match_arms.rs | |||
@@ -0,0 +1,264 @@ | |||
1 | use std::iter::successors; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | ast::{self, AstNode}, | ||
5 | Direction, TextUnit, | ||
6 | }; | ||
7 | |||
8 | use crate::{Assist, AssistCtx, AssistId, TextRange}; | ||
9 | |||
10 | // Assist: merge_match_arms | ||
11 | // | ||
12 | // Merges identical match arms. | ||
13 | // | ||
14 | // ``` | ||
15 | // enum Action { Move { distance: u32 }, Stop } | ||
16 | // | ||
17 | // fn handle(action: Action) { | ||
18 | // match action { | ||
19 | // <|>Action::Move(..) => foo(), | ||
20 | // Action::Stop => foo(), | ||
21 | // } | ||
22 | // } | ||
23 | // ``` | ||
24 | // -> | ||
25 | // ``` | ||
26 | // enum Action { Move { distance: u32 }, Stop } | ||
27 | // | ||
28 | // fn handle(action: Action) { | ||
29 | // match action { | ||
30 | // Action::Move(..) | Action::Stop => foo(), | ||
31 | // } | ||
32 | // } | ||
33 | // ``` | ||
34 | pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> { | ||
35 | let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?; | ||
36 | // Don't try to handle arms with guards for now - can add support for this later | ||
37 | if current_arm.guard().is_some() { | ||
38 | return None; | ||
39 | } | ||
40 | let current_expr = current_arm.expr()?; | ||
41 | let current_text_range = current_arm.syntax().text_range(); | ||
42 | |||
43 | enum CursorPos { | ||
44 | InExpr(TextUnit), | ||
45 | InPat(TextUnit), | ||
46 | } | ||
47 | let cursor_pos = ctx.frange.range.start(); | ||
48 | let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) { | ||
49 | CursorPos::InExpr(current_text_range.end() - cursor_pos) | ||
50 | } else { | ||
51 | CursorPos::InPat(cursor_pos) | ||
52 | }; | ||
53 | |||
54 | // We check if the following match arms match this one. We could, but don't, | ||
55 | // compare to the previous match arm as well. | ||
56 | let arms_to_merge = successors(Some(current_arm), next_arm) | ||
57 | .take_while(|arm| { | ||
58 | if arm.guard().is_some() { | ||
59 | return false; | ||
60 | } | ||
61 | match arm.expr() { | ||
62 | Some(expr) => expr.syntax().text() == current_expr.syntax().text(), | ||
63 | None => false, | ||
64 | } | ||
65 | }) | ||
66 | .collect::<Vec<_>>(); | ||
67 | |||
68 | if arms_to_merge.len() <= 1 { | ||
69 | return None; | ||
70 | } | ||
71 | |||
72 | ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| { | ||
73 | let pats = if arms_to_merge.iter().any(contains_placeholder) { | ||
74 | "_".into() | ||
75 | } else { | ||
76 | arms_to_merge | ||
77 | .iter() | ||
78 | .flat_map(ast::MatchArm::pats) | ||
79 | .map(|x| x.syntax().to_string()) | ||
80 | .collect::<Vec<String>>() | ||
81 | .join(" | ") | ||
82 | }; | ||
83 | |||
84 | let arm = format!("{} => {}", pats, current_expr.syntax().text()); | ||
85 | |||
86 | let start = arms_to_merge.first().unwrap().syntax().text_range().start(); | ||
87 | let end = arms_to_merge.last().unwrap().syntax().text_range().end(); | ||
88 | |||
89 | edit.target(current_text_range); | ||
90 | edit.set_cursor(match cursor_pos { | ||
91 | CursorPos::InExpr(back_offset) => start + TextUnit::from_usize(arm.len()) - back_offset, | ||
92 | CursorPos::InPat(offset) => offset, | ||
93 | }); | ||
94 | edit.replace(TextRange::from_to(start, end), arm); | ||
95 | }) | ||
96 | } | ||
97 | |||
98 | fn contains_placeholder(a: &ast::MatchArm) -> bool { | ||
99 | a.pats().any(|x| match x { | ||
100 | ra_syntax::ast::Pat::PlaceholderPat(..) => true, | ||
101 | _ => false, | ||
102 | }) | ||
103 | } | ||
104 | |||
105 | fn next_arm(arm: &ast::MatchArm) -> Option<ast::MatchArm> { | ||
106 | arm.syntax().siblings(Direction::Next).skip(1).find_map(ast::MatchArm::cast) | ||
107 | } | ||
108 | |||
109 | #[cfg(test)] | ||
110 | mod tests { | ||
111 | use super::merge_match_arms; | ||
112 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
113 | |||
114 | #[test] | ||
115 | fn merge_match_arms_single_patterns() { | ||
116 | check_assist( | ||
117 | merge_match_arms, | ||
118 | r#" | ||
119 | #[derive(Debug)] | ||
120 | enum X { A, B, C } | ||
121 | |||
122 | fn main() { | ||
123 | let x = X::A; | ||
124 | let y = match x { | ||
125 | X::A => { 1i32<|> } | ||
126 | X::B => { 1i32 } | ||
127 | X::C => { 2i32 } | ||
128 | } | ||
129 | } | ||
130 | "#, | ||
131 | r#" | ||
132 | #[derive(Debug)] | ||
133 | enum X { A, B, C } | ||
134 | |||
135 | fn main() { | ||
136 | let x = X::A; | ||
137 | let y = match x { | ||
138 | X::A | X::B => { 1i32<|> } | ||
139 | X::C => { 2i32 } | ||
140 | } | ||
141 | } | ||
142 | "#, | ||
143 | ); | ||
144 | } | ||
145 | |||
146 | #[test] | ||
147 | fn merge_match_arms_multiple_patterns() { | ||
148 | check_assist( | ||
149 | merge_match_arms, | ||
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 | X::B => {<|> 1i32 }, | ||
158 | X::C | X::D => { 1i32 }, | ||
159 | X::E => { 2i32 }, | ||
160 | } | ||
161 | } | ||
162 | "#, | ||
163 | r#" | ||
164 | #[derive(Debug)] | ||
165 | enum X { A, B, C, D, E } | ||
166 | |||
167 | fn main() { | ||
168 | let x = X::A; | ||
169 | let y = match x { | ||
170 | X::A | X::B | X::C | X::D => {<|> 1i32 }, | ||
171 | X::E => { 2i32 }, | ||
172 | } | ||
173 | } | ||
174 | "#, | ||
175 | ); | ||
176 | } | ||
177 | |||
178 | #[test] | ||
179 | fn merge_match_arms_placeholder_pattern() { | ||
180 | check_assist( | ||
181 | merge_match_arms, | ||
182 | r#" | ||
183 | #[derive(Debug)] | ||
184 | enum X { A, B, C, D, E } | ||
185 | |||
186 | fn main() { | ||
187 | let x = X::A; | ||
188 | let y = match x { | ||
189 | X::A => { 1i32 }, | ||
190 | X::B => { 2i<|>32 }, | ||
191 | _ => { 2i32 } | ||
192 | } | ||
193 | } | ||
194 | "#, | ||
195 | r#" | ||
196 | #[derive(Debug)] | ||
197 | enum X { A, B, C, D, E } | ||
198 | |||
199 | fn main() { | ||
200 | let x = X::A; | ||
201 | let y = match x { | ||
202 | X::A => { 1i32 }, | ||
203 | _ => { 2i<|>32 } | ||
204 | } | ||
205 | } | ||
206 | "#, | ||
207 | ); | ||
208 | } | ||
209 | |||
210 | #[test] | ||
211 | fn merges_all_subsequent_arms() { | ||
212 | check_assist( | ||
213 | merge_match_arms, | ||
214 | r#" | ||
215 | enum X { A, B, C, D, E } | ||
216 | |||
217 | fn main() { | ||
218 | match X::A { | ||
219 | X::A<|> => 92, | ||
220 | X::B => 92, | ||
221 | X::C => 92, | ||
222 | X::D => 62, | ||
223 | _ => panic!(), | ||
224 | } | ||
225 | } | ||
226 | "#, | ||
227 | r#" | ||
228 | enum X { A, B, C, D, E } | ||
229 | |||
230 | fn main() { | ||
231 | match X::A { | ||
232 | X::A<|> | X::B | X::C => 92, | ||
233 | X::D => 62, | ||
234 | _ => panic!(), | ||
235 | } | ||
236 | } | ||
237 | "#, | ||
238 | ) | ||
239 | } | ||
240 | |||
241 | #[test] | ||
242 | fn merge_match_arms_rejects_guards() { | ||
243 | check_assist_not_applicable( | ||
244 | merge_match_arms, | ||
245 | r#" | ||
246 | #[derive(Debug)] | ||
247 | enum X { | ||
248 | A(i32), | ||
249 | B, | ||
250 | C | ||
251 | } | ||
252 | |||
253 | fn main() { | ||
254 | let x = X::A; | ||
255 | let y = match x { | ||
256 | X::A(a) if a > 5 => { <|>1i32 }, | ||
257 | X::B => { 1i32 }, | ||
258 | X::C => { 2i32 } | ||
259 | } | ||
260 | } | ||
261 | "#, | ||
262 | ); | ||
263 | } | ||
264 | } | ||