diff options
Diffstat (limited to 'crates/ra_assists/src/handlers')
-rw-r--r-- | crates/ra_assists/src/handlers/extract_variable.rs | 103 |
1 files changed, 55 insertions, 48 deletions
diff --git a/crates/ra_assists/src/handlers/extract_variable.rs b/crates/ra_assists/src/handlers/extract_variable.rs index f5637a740..098adf078 100644 --- a/crates/ra_assists/src/handlers/extract_variable.rs +++ b/crates/ra_assists/src/handlers/extract_variable.rs | |||
@@ -2,7 +2,6 @@ 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 | }; |
@@ -36,22 +35,20 @@ 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)?; | 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; | ||
44 | } | ||
45 | let target = expr.syntax().text_range(); | ||
46 | acc.add( | 42 | acc.add( |
47 | AssistId("extract_variable", AssistKind::RefactorExtract), | 43 | AssistId("extract_variable", AssistKind::RefactorExtract), |
48 | "Extract into variable", | 44 | "Extract into variable", |
49 | target, | 45 | target, |
50 | move |edit| { | 46 | move |edit| { |
51 | let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) { | 47 | let field_shorthand = |
52 | Some(field) => field.name_ref(), | 48 | match to_extract.syntax().parent().and_then(ast::RecordField::cast) { |
53 | None => None, | 49 | Some(field) => field.name_ref(), |
54 | }; | 50 | None => None, |
51 | }; | ||
55 | 52 | ||
56 | let mut buf = String::new(); | 53 | let mut buf = String::new(); |
57 | 54 | ||
@@ -60,26 +57,20 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
60 | None => "var_name".to_string(), | 57 | None => "var_name".to_string(), |
61 | }; | 58 | }; |
62 | let expr_range = match &field_shorthand { | 59 | let expr_range = match &field_shorthand { |
63 | Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()), | 60 | Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()), |
64 | None => expr.syntax().text_range(), | 61 | None => to_extract.syntax().text_range(), |
65 | }; | 62 | }; |
66 | 63 | ||
67 | if wrap_in_block { | 64 | if let Anchor::WrapInBlock(_) = anchor { |
68 | format_to!(buf, "{{ let {} = ", var_name); | 65 | format_to!(buf, "{{ let {} = ", var_name); |
69 | } else { | 66 | } else { |
70 | format_to!(buf, "let {} = ", var_name); | 67 | format_to!(buf, "let {} = ", var_name); |
71 | }; | 68 | }; |
72 | format_to!(buf, "{}", expr.syntax()); | 69 | format_to!(buf, "{}", to_extract.syntax()); |
73 | 70 | ||
74 | let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); | 71 | if let Anchor::Replace(stmt) = anchor { |
75 | let is_full_stmt = if let Some(expr_stmt) = &full_stmt { | ||
76 | Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) | ||
77 | } else { | ||
78 | false | ||
79 | }; | ||
80 | if is_full_stmt { | ||
81 | mark::hit!(test_extract_var_expr_stmt); | 72 | mark::hit!(test_extract_var_expr_stmt); |
82 | if full_stmt.unwrap().semicolon_token().is_none() { | 73 | if stmt.semicolon_token().is_none() { |
83 | buf.push_str(";"); | 74 | buf.push_str(";"); |
84 | } | 75 | } |
85 | match ctx.config.snippet_cap { | 76 | match ctx.config.snippet_cap { |
@@ -107,7 +98,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
107 | } | 98 | } |
108 | 99 | ||
109 | edit.replace(expr_range, var_name.clone()); | 100 | edit.replace(expr_range, var_name.clone()); |
110 | let offset = anchor_stmt.text_range().start(); | 101 | let offset = anchor.syntax().text_range().start(); |
111 | match ctx.config.snippet_cap { | 102 | match ctx.config.snippet_cap { |
112 | Some(cap) => { | 103 | Some(cap) => { |
113 | let snip = | 104 | let snip = |
@@ -117,8 +108,8 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
117 | None => edit.insert(offset, buf), | 108 | None => edit.insert(offset, buf), |
118 | } | 109 | } |
119 | 110 | ||
120 | if wrap_in_block { | 111 | if let Anchor::WrapInBlock(_) = anchor { |
121 | edit.insert(anchor_stmt.text_range().end(), " }"); | 112 | edit.insert(anchor.syntax().text_range().end(), " }"); |
122 | } | 113 | } |
123 | }, | 114 | }, |
124 | ) | 115 | ) |
@@ -138,32 +129,48 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> { | |||
138 | } | 129 | } |
139 | } | 130 | } |
140 | 131 | ||
141 | /// Returns the syntax node which will follow the freshly extractd var | 132 | enum Anchor { |
142 | /// and a boolean indicating whether we have to wrap it within a { } block | 133 | Before(SyntaxNode), |
143 | /// to produce correct code. | 134 | Replace(ast::ExprStmt), |
144 | /// It can be a statement, the last in a block expression or a wanna be block | 135 | WrapInBlock(SyntaxNode), |
145 | /// expression like a lambda or match arm. | 136 | } |
146 | fn anchor_stmt(expr: &ast::Expr) -> Option<(SyntaxNode, bool)> { | 137 | |
147 | expr.syntax().ancestors().find_map(|node| { | 138 | impl Anchor { |
148 | 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> { |
149 | if expr.syntax() == &node { | 140 | to_extract.syntax().ancestors().find_map(|node| { |
150 | mark::hit!(test_extract_var_last_expr); | 141 | if let Some(expr) = |
151 | 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 | } | ||
152 | } | 148 | } |
153 | } | ||
154 | 149 | ||
155 | if let Some(parent) = node.parent() { | 150 | if let Some(parent) = node.parent() { |
156 | if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { | 151 | if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { |
157 | return Some((node, true)); | 152 | return Some(Anchor::WrapInBlock(node)); |
153 | } | ||
158 | } | 154 | } |
159 | } | ||
160 | 155 | ||
161 | if ast::Stmt::cast(node.clone()).is_some() { | 156 | if let Some(stmt) = ast::Stmt::cast(node.clone()) { |
162 | return Some((node, false)); | 157 | if let ast::Stmt::ExprStmt(stmt) = stmt { |
163 | } | 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 | } | ||
164 | 167 | ||
165 | None | 168 | fn syntax(&self) -> &SyntaxNode { |
166 | }) | 169 | match self { |
170 | Anchor::Before(it) | Anchor::WrapInBlock(it) => it, | ||
171 | Anchor::Replace(stmt) => stmt.syntax(), | ||
172 | } | ||
173 | } | ||
167 | } | 174 | } |
168 | 175 | ||
169 | #[cfg(test)] | 176 | #[cfg(test)] |