diff options
author | Aleksey Kladov <[email protected]> | 2019-01-08 11:21:29 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-01-08 11:21:29 +0000 |
commit | 96236a9be542be461550083373be3d0cb0bd8406 (patch) | |
tree | 82cfc789fcfeea452e6fb729c6af3d7c840be173 /crates/ra_editor | |
parent | 1e0948a509e8f6ec7cbb5e2ef77669325fee0637 (diff) |
assist to convert if-let to match
Diffstat (limited to 'crates/ra_editor')
-rw-r--r-- | crates/ra_editor/src/assists.rs | 23 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/replace_if_let_with_match.rs | 92 | ||||
-rw-r--r-- | crates/ra_editor/src/test_utils.rs | 5 |
3 files changed, 119 insertions, 1 deletions
diff --git a/crates/ra_editor/src/assists.rs b/crates/ra_editor/src/assists.rs index a320caabf..f839f6a7a 100644 --- a/crates/ra_editor/src/assists.rs +++ b/crates/ra_editor/src/assists.rs | |||
@@ -9,12 +9,15 @@ mod add_impl; | |||
9 | mod introduce_variable; | 9 | mod introduce_variable; |
10 | mod change_visibility; | 10 | mod change_visibility; |
11 | mod split_import; | 11 | mod split_import; |
12 | mod replace_if_let_with_match; | ||
12 | 13 | ||
13 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 14 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
14 | use ra_syntax::{ | 15 | use ra_syntax::{ |
15 | Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode, | 16 | Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode, |
16 | algo::{find_leaf_at_offset, find_covering_node, LeafAtOffset}, | 17 | algo::{find_leaf_at_offset, find_covering_node, LeafAtOffset}, |
18 | ast::{self, AstToken}, | ||
17 | }; | 19 | }; |
20 | use itertools::Itertools; | ||
18 | 21 | ||
19 | use crate::find_node_at_offset; | 22 | use crate::find_node_at_offset; |
20 | 23 | ||
@@ -25,6 +28,7 @@ pub use self::{ | |||
25 | introduce_variable::introduce_variable, | 28 | introduce_variable::introduce_variable, |
26 | change_visibility::change_visibility, | 29 | change_visibility::change_visibility, |
27 | split_import::split_import, | 30 | split_import::split_import, |
31 | replace_if_let_with_match::replace_if_let_with_match, | ||
28 | }; | 32 | }; |
29 | 33 | ||
30 | /// Return all the assists applicable at the given position. | 34 | /// Return all the assists applicable at the given position. |
@@ -37,6 +41,7 @@ pub fn assists(file: &SourceFile, range: TextRange) -> Vec<LocalEdit> { | |||
37 | introduce_variable, | 41 | introduce_variable, |
38 | change_visibility, | 42 | change_visibility, |
39 | split_import, | 43 | split_import, |
44 | replace_if_let_with_match, | ||
40 | ] | 45 | ] |
41 | .iter() | 46 | .iter() |
42 | .filter_map(|&assist| ctx.clone().apply(assist)) | 47 | .filter_map(|&assist| ctx.clone().apply(assist)) |
@@ -160,6 +165,13 @@ impl AssistBuilder { | |||
160 | fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | 165 | fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { |
161 | self.edit.replace(range, replace_with.into()) | 166 | self.edit.replace(range, replace_with.into()) |
162 | } | 167 | } |
168 | fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) { | ||
169 | let mut replace_with = replace_with.into(); | ||
170 | if let Some(indent) = calc_indent(node) { | ||
171 | replace_with = reindent(&replace_with, indent) | ||
172 | } | ||
173 | self.replace(node.range(), replace_with) | ||
174 | } | ||
163 | #[allow(unused)] | 175 | #[allow(unused)] |
164 | fn delete(&mut self, range: TextRange) { | 176 | fn delete(&mut self, range: TextRange) { |
165 | self.edit.delete(range) | 177 | self.edit.delete(range) |
@@ -172,6 +184,17 @@ impl AssistBuilder { | |||
172 | } | 184 | } |
173 | } | 185 | } |
174 | 186 | ||
187 | fn calc_indent(node: &SyntaxNode) -> Option<&str> { | ||
188 | let prev = node.prev_sibling()?; | ||
189 | let ws_text = ast::Whitespace::cast(prev)?.text(); | ||
190 | ws_text.rfind('\n').map(|pos| &ws_text[pos + 1..]) | ||
191 | } | ||
192 | |||
193 | fn reindent(text: &str, indent: &str) -> String { | ||
194 | let indent = format!("\n{}", indent); | ||
195 | text.lines().intersperse(&indent).collect() | ||
196 | } | ||
197 | |||
175 | #[cfg(test)] | 198 | #[cfg(test)] |
176 | fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) { | 199 | fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) { |
177 | crate::test_utils::check_action(before, after, |file, off| { | 200 | crate::test_utils::check_action(before, after, |file, off| { |
diff --git a/crates/ra_editor/src/assists/replace_if_let_with_match.rs b/crates/ra_editor/src/assists/replace_if_let_with_match.rs new file mode 100644 index 000000000..30c371480 --- /dev/null +++ b/crates/ra_editor/src/assists/replace_if_let_with_match.rs | |||
@@ -0,0 +1,92 @@ | |||
1 | use ra_syntax::{ | ||
2 | AstNode, SyntaxKind::{L_CURLY, R_CURLY, WHITESPACE}, | ||
3 | ast, | ||
4 | }; | ||
5 | |||
6 | use crate::assists::{AssistCtx, Assist}; | ||
7 | |||
8 | pub fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { | ||
9 | let if_expr: &ast::IfExpr = ctx.node_at_offset()?; | ||
10 | let cond = if_expr.condition()?; | ||
11 | let pat = cond.pat()?; | ||
12 | let expr = cond.expr()?; | ||
13 | let then_block = if_expr.then_branch()?; | ||
14 | let else_block = if_expr.else_branch()?; | ||
15 | |||
16 | ctx.build("replace with match", |edit| { | ||
17 | let match_expr = build_match_expr(expr, pat, then_block, else_block); | ||
18 | edit.replace_node_and_indent(if_expr.syntax(), match_expr); | ||
19 | edit.set_cursor(if_expr.syntax().range().start()) | ||
20 | }) | ||
21 | } | ||
22 | |||
23 | fn build_match_expr( | ||
24 | expr: &ast::Expr, | ||
25 | pat1: &ast::Pat, | ||
26 | arm1: &ast::Block, | ||
27 | arm2: &ast::Block, | ||
28 | ) -> String { | ||
29 | let mut buf = String::new(); | ||
30 | buf.push_str(&format!("match {} {{\n", expr.syntax().text())); | ||
31 | buf.push_str(&format!( | ||
32 | " {} => {}\n", | ||
33 | pat1.syntax().text(), | ||
34 | format_arm(arm1) | ||
35 | )); | ||
36 | buf.push_str(&format!(" _ => {}\n", format_arm(arm2))); | ||
37 | buf.push_str("}"); | ||
38 | buf | ||
39 | } | ||
40 | |||
41 | fn format_arm(block: &ast::Block) -> String { | ||
42 | match extract_expression(block) { | ||
43 | None => block.syntax().text().to_string(), | ||
44 | Some(e) => format!("{},", e.syntax().text()), | ||
45 | } | ||
46 | } | ||
47 | |||
48 | fn extract_expression(block: &ast::Block) -> Option<&ast::Expr> { | ||
49 | let expr = block.expr()?; | ||
50 | let non_trivial_children = block.syntax().children().filter(|it| { | ||
51 | !(it == &expr.syntax() | ||
52 | || it.kind() == L_CURLY | ||
53 | || it.kind() == R_CURLY | ||
54 | || it.kind() == WHITESPACE) | ||
55 | }); | ||
56 | if non_trivial_children.count() > 0 { | ||
57 | return None; | ||
58 | } | ||
59 | Some(expr) | ||
60 | } | ||
61 | |||
62 | #[cfg(test)] | ||
63 | mod tests { | ||
64 | use super::*; | ||
65 | use crate::assists::check_assist; | ||
66 | |||
67 | #[test] | ||
68 | fn test_replace_if_let_with_match_unwraps_simple_expressions() { | ||
69 | check_assist( | ||
70 | replace_if_let_with_match, | ||
71 | " | ||
72 | impl VariantData { | ||
73 | pub fn is_struct(&self) -> bool { | ||
74 | if <|>let VariantData::Struct(..) = *self { | ||
75 | true | ||
76 | } else { | ||
77 | false | ||
78 | } | ||
79 | } | ||
80 | } ", | ||
81 | " | ||
82 | impl VariantData { | ||
83 | pub fn is_struct(&self) -> bool { | ||
84 | <|>match *self { | ||
85 | VariantData::Struct(..) => true, | ||
86 | _ => false, | ||
87 | } | ||
88 | } | ||
89 | } ", | ||
90 | ) | ||
91 | } | ||
92 | } | ||
diff --git a/crates/ra_editor/src/test_utils.rs b/crates/ra_editor/src/test_utils.rs index bf40c92c0..dc2470aa3 100644 --- a/crates/ra_editor/src/test_utils.rs +++ b/crates/ra_editor/src/test_utils.rs | |||
@@ -13,7 +13,10 @@ pub fn check_action<F: Fn(&SourceFile, TextUnit) -> Option<LocalEdit>>( | |||
13 | let result = f(&file, before_cursor_pos).expect("code action is not applicable"); | 13 | let result = f(&file, before_cursor_pos).expect("code action is not applicable"); |
14 | let actual = result.edit.apply(&before); | 14 | let actual = result.edit.apply(&before); |
15 | let actual_cursor_pos = match result.cursor_position { | 15 | let actual_cursor_pos = match result.cursor_position { |
16 | None => result.edit.apply_to_offset(before_cursor_pos).unwrap(), | 16 | None => result |
17 | .edit | ||
18 | .apply_to_offset(before_cursor_pos) | ||
19 | .expect("cursor position is affected by the edit"), | ||
17 | Some(off) => off, | 20 | Some(off) => off, |
18 | }; | 21 | }; |
19 | let actual = add_cursor(&actual, actual_cursor_pos); | 22 | let actual = add_cursor(&actual, actual_cursor_pos); |