aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_editor/src/assists.rs23
-rw-r--r--crates/ra_editor/src/assists/replace_if_let_with_match.rs92
-rw-r--r--crates/ra_editor/src/test_utils.rs5
-rw-r--r--crates/ra_syntax/src/ast.rs2
4 files changed, 120 insertions, 2 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;
9mod introduce_variable; 9mod introduce_variable;
10mod change_visibility; 10mod change_visibility;
11mod split_import; 11mod split_import;
12mod replace_if_let_with_match;
12 13
13use ra_text_edit::{TextEdit, TextEditBuilder}; 14use ra_text_edit::{TextEdit, TextEditBuilder};
14use ra_syntax::{ 15use 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};
20use itertools::Itertools;
18 21
19use crate::find_node_at_offset; 22use 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
187fn 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
193fn 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)]
176fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) { 199fn 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 @@
1use ra_syntax::{
2 AstNode, SyntaxKind::{L_CURLY, R_CURLY, WHITESPACE},
3 ast,
4};
5
6use crate::assists::{AssistCtx, Assist};
7
8pub 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
23fn 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
41fn 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
48fn 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)]
63mod 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 "
72impl VariantData {
73 pub fn is_struct(&self) -> bool {
74 if <|>let VariantData::Struct(..) = *self {
75 true
76 } else {
77 false
78 }
79 }
80} ",
81 "
82impl 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);
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index 96879ae5a..d25b5642b 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -225,7 +225,7 @@ impl Whitespace {
225 } 225 }
226 226
227 pub fn has_newlines(&self) -> bool { 227 pub fn has_newlines(&self) -> bool {
228 self.count_newlines_lazy().count() > 0 228 self.text().contains('\n')
229 } 229 }
230} 230}
231 231