diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-07-31 03:12:44 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-07-31 03:12:44 +0100 |
commit | f05d7b41a719d848844b054a16477b29d0f063c6 (patch) | |
tree | 0a8a0946e8aef2ce64d4c13d0035ba41cce2daf3 /crates/ra_assists/src/handlers/extract_variable.rs | |
parent | 73ff610e41959e3e7c78a2b4b25b086883132956 (diff) | |
parent | 6b7cb8b5ab539fc4333ce34bc29bf77c976f232a (diff) |
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Hasn't fixed tests yet.
Diffstat (limited to 'crates/ra_assists/src/handlers/extract_variable.rs')
-rw-r--r-- | crates/ra_assists/src/handlers/extract_variable.rs | 202 |
1 files changed, 107 insertions, 95 deletions
diff --git a/crates/ra_assists/src/handlers/extract_variable.rs b/crates/ra_assists/src/handlers/extract_variable.rs index c4150d2bb..b925a2884 100644 --- a/crates/ra_assists/src/handlers/extract_variable.rs +++ b/crates/ra_assists/src/handlers/extract_variable.rs | |||
@@ -2,14 +2,13 @@ use ra_syntax::{ | |||
2 | ast::{self, AstNode}, | 2 | ast::{self, AstNode}, |
3 | SyntaxKind::{ | 3 | SyntaxKind::{ |
4 | BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, | 4 | BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, |
5 | WHITESPACE, | ||
6 | }, | 5 | }, |
7 | SyntaxNode, | 6 | SyntaxNode, |
8 | }; | 7 | }; |
9 | use stdx::format_to; | 8 | use stdx::format_to; |
10 | use test_utils::mark; | 9 | use test_utils::mark; |
11 | 10 | ||
12 | use crate::{AssistContext, AssistId, Assists}; | 11 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
13 | 12 | ||
14 | // Assist: extract_variable | 13 | // Assist: extract_variable |
15 | // | 14 | // |
@@ -36,87 +35,84 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
36 | mark::hit!(extract_var_in_comment_is_not_applicable); | 35 | mark::hit!(extract_var_in_comment_is_not_applicable); |
37 | return None; | 36 | return None; |
38 | } | 37 | } |
39 | let expr = node.ancestors().find_map(valid_target_expr)?; | 38 | let to_extract = node.ancestors().find_map(valid_target_expr)?; |
40 | let (anchor_stmt, wrap_in_block) = anchor_stmt(expr.clone())?; | 39 | let anchor = Anchor::from(&to_extract)?; |
41 | let indent = anchor_stmt.prev_sibling_or_token()?.as_token()?.clone(); | 40 | let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone(); |
42 | if indent.kind() != WHITESPACE { | 41 | let target = to_extract.syntax().text_range(); |
43 | return None; | 42 | acc.add( |
44 | } | 43 | AssistId("extract_variable", AssistKind::RefactorExtract), |
45 | let target = expr.syntax().text_range(); | 44 | "Extract into variable", |
46 | acc.add(AssistId("extract_variable"), "Extract into variable", target, move |edit| { | 45 | target, |
47 | let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) { | 46 | move |edit| { |
48 | Some(field) => field.name_ref(), | 47 | let field_shorthand = |
49 | None => None, | 48 | match to_extract.syntax().parent().and_then(ast::RecordExprField::cast) { |
50 | }; | 49 | Some(field) => field.name_ref(), |
51 | 50 | None => None, | |
52 | let mut buf = String::new(); | 51 | }; |
53 | 52 | ||
54 | let var_name = match &field_shorthand { | 53 | let mut buf = String::new(); |
55 | Some(it) => it.to_string(), | 54 | |
56 | None => "var_name".to_string(), | 55 | let var_name = match &field_shorthand { |
57 | }; | 56 | Some(it) => it.to_string(), |
58 | let expr_range = match &field_shorthand { | 57 | None => "var_name".to_string(), |
59 | Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()), | 58 | }; |
60 | None => expr.syntax().text_range(), | 59 | let expr_range = match &field_shorthand { |
61 | }; | 60 | Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()), |
62 | 61 | None => to_extract.syntax().text_range(), | |
63 | if wrap_in_block { | 62 | }; |
64 | format_to!(buf, "{{ let {} = ", var_name); | 63 | |
65 | } else { | 64 | if let Anchor::WrapInBlock(_) = anchor { |
66 | format_to!(buf, "let {} = ", var_name); | 65 | format_to!(buf, "{{ let {} = ", var_name); |
67 | }; | 66 | } else { |
68 | format_to!(buf, "{}", expr.syntax()); | 67 | format_to!(buf, "let {} = ", var_name); |
69 | 68 | }; | |
70 | let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); | 69 | format_to!(buf, "{}", to_extract.syntax()); |
71 | let is_full_stmt = if let Some(expr_stmt) = &full_stmt { | 70 | |
72 | Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) | 71 | if let Anchor::Replace(stmt) = anchor { |
73 | } else { | 72 | mark::hit!(test_extract_var_expr_stmt); |
74 | false | 73 | if stmt.semicolon_token().is_none() { |
75 | }; | 74 | buf.push_str(";"); |
76 | if is_full_stmt { | 75 | } |
77 | mark::hit!(test_extract_var_expr_stmt); | 76 | match ctx.config.snippet_cap { |
78 | if full_stmt.unwrap().semicolon_token().is_none() { | 77 | Some(cap) => { |
79 | buf.push_str(";"); | 78 | let snip = buf |
79 | .replace(&format!("let {}", var_name), &format!("let $0{}", var_name)); | ||
80 | edit.replace_snippet(cap, expr_range, snip) | ||
81 | } | ||
82 | None => edit.replace(expr_range, buf), | ||
83 | } | ||
84 | return; | ||
80 | } | 85 | } |
86 | |||
87 | buf.push_str(";"); | ||
88 | |||
89 | // We want to maintain the indent level, | ||
90 | // but we do not want to duplicate possible | ||
91 | // extra newlines in the indent block | ||
92 | let text = indent.text(); | ||
93 | if text.starts_with('\n') { | ||
94 | buf.push_str("\n"); | ||
95 | buf.push_str(text.trim_start_matches('\n')); | ||
96 | } else { | ||
97 | buf.push_str(text); | ||
98 | } | ||
99 | |||
100 | edit.replace(expr_range, var_name.clone()); | ||
101 | let offset = anchor.syntax().text_range().start(); | ||
81 | match ctx.config.snippet_cap { | 102 | match ctx.config.snippet_cap { |
82 | Some(cap) => { | 103 | Some(cap) => { |
83 | let snip = | 104 | let snip = |
84 | buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name)); | 105 | buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name)); |
85 | edit.replace_snippet(cap, expr_range, snip) | 106 | edit.insert_snippet(cap, offset, snip) |
86 | } | 107 | } |
87 | None => edit.replace(expr_range, buf), | 108 | None => edit.insert(offset, buf), |
88 | } | 109 | } |
89 | return; | ||
90 | } | ||
91 | |||
92 | buf.push_str(";"); | ||
93 | |||
94 | // We want to maintain the indent level, | ||
95 | // but we do not want to duplicate possible | ||
96 | // extra newlines in the indent block | ||
97 | let text = indent.text(); | ||
98 | if text.starts_with('\n') { | ||
99 | buf.push_str("\n"); | ||
100 | buf.push_str(text.trim_start_matches('\n')); | ||
101 | } else { | ||
102 | buf.push_str(text); | ||
103 | } | ||
104 | 110 | ||
105 | edit.replace(expr_range, var_name.clone()); | 111 | if let Anchor::WrapInBlock(_) = anchor { |
106 | let offset = anchor_stmt.text_range().start(); | 112 | edit.insert(anchor.syntax().text_range().end(), " }"); |
107 | match ctx.config.snippet_cap { | ||
108 | Some(cap) => { | ||
109 | let snip = | ||
110 | buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name)); | ||
111 | edit.insert_snippet(cap, offset, snip) | ||
112 | } | 113 | } |
113 | None => edit.insert(offset, buf), | 114 | }, |
114 | } | 115 | ) |
115 | |||
116 | if wrap_in_block { | ||
117 | edit.insert(anchor_stmt.text_range().end(), " }"); | ||
118 | } | ||
119 | }) | ||
120 | } | 116 | } |
121 | 117 | ||
122 | /// Check whether the node is a valid expression which can be extracted to a variable. | 118 | /// Check whether the node is a valid expression which can be extracted to a variable. |
@@ -133,32 +129,48 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> { | |||
133 | } | 129 | } |
134 | } | 130 | } |
135 | 131 | ||
136 | /// Returns the syntax node which will follow the freshly extractd var | 132 | enum Anchor { |
137 | /// and a boolean indicating whether we have to wrap it within a { } block | 133 | Before(SyntaxNode), |
138 | /// to produce correct code. | 134 | Replace(ast::ExprStmt), |
139 | /// It can be a statement, the last in a block expression or a wanna be block | 135 | WrapInBlock(SyntaxNode), |
140 | /// expression like a lambda or match arm. | 136 | } |
141 | fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { | 137 | |
142 | expr.syntax().ancestors().find_map(|node| { | 138 | impl Anchor { |
143 | if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { | 139 | fn from(to_extract: &ast::Expr) -> Option<Anchor> { |
144 | if expr.syntax() == &node { | 140 | to_extract.syntax().ancestors().find_map(|node| { |
145 | mark::hit!(test_extract_var_last_expr); | 141 | if let Some(expr) = |
146 | return Some((node, false)); | 142 | node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) |
143 | { | ||
144 | if expr.syntax() == &node { | ||
145 | mark::hit!(test_extract_var_last_expr); | ||
146 | return Some(Anchor::Before(node)); | ||
147 | } | ||
147 | } | 148 | } |
148 | } | ||
149 | 149 | ||
150 | if let Some(parent) = node.parent() { | 150 | if let Some(parent) = node.parent() { |
151 | if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { | 151 | if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { |
152 | return Some((node, true)); | 152 | return Some(Anchor::WrapInBlock(node)); |
153 | } | ||
153 | } | 154 | } |
154 | } | ||
155 | 155 | ||
156 | if ast::Stmt::cast(node.clone()).is_some() { | 156 | if let Some(stmt) = ast::Stmt::cast(node.clone()) { |
157 | return Some((node, false)); | 157 | if let ast::Stmt::ExprStmt(stmt) = stmt { |
158 | } | 158 | if stmt.expr().as_ref() == Some(to_extract) { |
159 | return Some(Anchor::Replace(stmt)); | ||
160 | } | ||
161 | } | ||
162 | return Some(Anchor::Before(node)); | ||
163 | } | ||
164 | None | ||
165 | }) | ||
166 | } | ||
159 | 167 | ||
160 | None | 168 | fn syntax(&self) -> &SyntaxNode { |
161 | }) | 169 | match self { |
170 | Anchor::Before(it) | Anchor::WrapInBlock(it) => it, | ||
171 | Anchor::Replace(stmt) => stmt.syntax(), | ||
172 | } | ||
173 | } | ||
162 | } | 174 | } |
163 | 175 | ||
164 | #[cfg(test)] | 176 | #[cfg(test)] |