aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/introduce_variable.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers/introduce_variable.rs')
-rw-r--r--crates/ra_assists/src/handlers/introduce_variable.rs140
1 files changed, 77 insertions, 63 deletions
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs
index eda9ac296..31d6539f7 100644
--- a/crates/ra_assists/src/handlers/introduce_variable.rs
+++ b/crates/ra_assists/src/handlers/introduce_variable.rs
@@ -4,12 +4,12 @@ use ra_syntax::{
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, 5 WHITESPACE,
6 }, 6 },
7 SyntaxNode, TextSize, 7 SyntaxNode,
8}; 8};
9use stdx::format_to; 9use stdx::format_to;
10use test_utils::tested_by; 10use test_utils::mark;
11 11
12use crate::{Assist, AssistCtx, AssistId}; 12use crate::{AssistContext, AssistId, Assists};
13 13
14// Assist: introduce_variable 14// Assist: introduce_variable
15// 15//
@@ -23,17 +23,17 @@ use crate::{Assist, AssistCtx, AssistId};
23// -> 23// ->
24// ``` 24// ```
25// fn main() { 25// fn main() {
26// let var_name = (1 + 2); 26// let $0var_name = (1 + 2);
27// var_name * 4; 27// var_name * 4;
28// } 28// }
29// ``` 29// ```
30pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> { 30pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 if ctx.frange.range.is_empty() { 31 if ctx.frange.range.is_empty() {
32 return None; 32 return None;
33 } 33 }
34 let node = ctx.covering_element(); 34 let node = ctx.covering_element();
35 if node.kind() == COMMENT { 35 if node.kind() == COMMENT {
36 tested_by!(introduce_var_in_comment_is_not_applicable); 36 mark::hit!(introduce_var_in_comment_is_not_applicable);
37 return None; 37 return None;
38 } 38 }
39 let expr = node.ancestors().find_map(valid_target_expr)?; 39 let expr = node.ancestors().find_map(valid_target_expr)?;
@@ -42,17 +42,17 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
42 if indent.kind() != WHITESPACE { 42 if indent.kind() != WHITESPACE {
43 return None; 43 return None;
44 } 44 }
45 ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", move |edit| { 45 let target = expr.syntax().text_range();
46 acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| {
46 let mut buf = String::new(); 47 let mut buf = String::new();
47 48
48 let cursor_offset = if wrap_in_block { 49 if wrap_in_block {
49 buf.push_str("{ let var_name = "); 50 buf.push_str("{ let var_name = ");
50 TextSize::of("{ let ")
51 } else { 51 } else {
52 buf.push_str("let var_name = "); 52 buf.push_str("let var_name = ");
53 TextSize::of("let ")
54 }; 53 };
55 format_to!(buf, "{}", expr.syntax()); 54 format_to!(buf, "{}", expr.syntax());
55
56 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); 56 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
57 let is_full_stmt = if let Some(expr_stmt) = &full_stmt { 57 let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
58 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) 58 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
@@ -60,33 +60,47 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
60 false 60 false
61 }; 61 };
62 if is_full_stmt { 62 if is_full_stmt {
63 tested_by!(test_introduce_var_expr_stmt); 63 mark::hit!(test_introduce_var_expr_stmt);
64 if full_stmt.unwrap().semicolon_token().is_none() { 64 if full_stmt.unwrap().semicolon_token().is_none() {
65 buf.push_str(";"); 65 buf.push_str(";");
66 } 66 }
67 edit.replace(expr.syntax().text_range(), buf); 67 let offset = expr.syntax().text_range();
68 } else { 68 match ctx.config.snippet_cap {
69 buf.push_str(";"); 69 Some(cap) => {
70 70 let snip = buf.replace("let var_name", "let $0var_name");
71 // We want to maintain the indent level, 71 edit.replace_snippet(cap, offset, snip)
72 // but we do not want to duplicate possible 72 }
73 // extra newlines in the indent block 73 None => edit.replace(offset, buf),
74 let text = indent.text();
75 if text.starts_with('\n') {
76 buf.push_str("\n");
77 buf.push_str(text.trim_start_matches('\n'));
78 } else {
79 buf.push_str(text);
80 } 74 }
75 return;
76 }
81 77
82 edit.target(expr.syntax().text_range()); 78 buf.push_str(";");
83 edit.replace(expr.syntax().text_range(), "var_name".to_string()); 79
84 edit.insert(anchor_stmt.text_range().start(), buf); 80 // We want to maintain the indent level,
85 if wrap_in_block { 81 // but we do not want to duplicate possible
86 edit.insert(anchor_stmt.text_range().end(), " }"); 82 // extra newlines in the indent block
83 let text = indent.text();
84 if text.starts_with('\n') {
85 buf.push_str("\n");
86 buf.push_str(text.trim_start_matches('\n'));
87 } else {
88 buf.push_str(text);
89 }
90
91 edit.replace(expr.syntax().text_range(), "var_name".to_string());
92 let offset = anchor_stmt.text_range().start();
93 match ctx.config.snippet_cap {
94 Some(cap) => {
95 let snip = buf.replace("let var_name", "let $0var_name");
96 edit.insert_snippet(cap, offset, snip)
87 } 97 }
98 None => edit.insert(offset, buf),
99 }
100
101 if wrap_in_block {
102 edit.insert(anchor_stmt.text_range().end(), " }");
88 } 103 }
89 edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset);
90 }) 104 })
91} 105}
92 106
@@ -111,9 +125,9 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
111/// expression like a lambda or match arm. 125/// expression like a lambda or match arm.
112fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { 126fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
113 expr.syntax().ancestors().find_map(|node| { 127 expr.syntax().ancestors().find_map(|node| {
114 if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) { 128 if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) {
115 if expr.syntax() == &node { 129 if expr.syntax() == &node {
116 tested_by!(test_introduce_var_last_expr); 130 mark::hit!(test_introduce_var_last_expr);
117 return Some((node, false)); 131 return Some((node, false));
118 } 132 }
119 } 133 }
@@ -134,9 +148,9 @@ fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
134 148
135#[cfg(test)] 149#[cfg(test)]
136mod tests { 150mod tests {
137 use test_utils::covers; 151 use test_utils::mark;
138 152
139 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 153 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
140 154
141 use super::*; 155 use super::*;
142 156
@@ -144,37 +158,37 @@ mod tests {
144 fn test_introduce_var_simple() { 158 fn test_introduce_var_simple() {
145 check_assist( 159 check_assist(
146 introduce_variable, 160 introduce_variable,
147 " 161 r#"
148fn foo() { 162fn foo() {
149 foo(<|>1 + 1<|>); 163 foo(<|>1 + 1<|>);
150}", 164}"#,
151 " 165 r#"
152fn foo() { 166fn foo() {
153 let <|>var_name = 1 + 1; 167 let $0var_name = 1 + 1;
154 foo(var_name); 168 foo(var_name);
155}", 169}"#,
156 ); 170 );
157 } 171 }
158 172
159 #[test] 173 #[test]
160 fn introduce_var_in_comment_is_not_applicable() { 174 fn introduce_var_in_comment_is_not_applicable() {
161 covers!(introduce_var_in_comment_is_not_applicable); 175 mark::check!(introduce_var_in_comment_is_not_applicable);
162 check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); 176 check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }");
163 } 177 }
164 178
165 #[test] 179 #[test]
166 fn test_introduce_var_expr_stmt() { 180 fn test_introduce_var_expr_stmt() {
167 covers!(test_introduce_var_expr_stmt); 181 mark::check!(test_introduce_var_expr_stmt);
168 check_assist( 182 check_assist(
169 introduce_variable, 183 introduce_variable,
170 " 184 r#"
171fn foo() { 185fn foo() {
172 <|>1 + 1<|>; 186 <|>1 + 1<|>;
173}", 187}"#,
174 " 188 r#"
175fn foo() { 189fn foo() {
176 let <|>var_name = 1 + 1; 190 let $0var_name = 1 + 1;
177}", 191}"#,
178 ); 192 );
179 check_assist( 193 check_assist(
180 introduce_variable, 194 introduce_variable,
@@ -185,7 +199,7 @@ fn foo() {
185}", 199}",
186 " 200 "
187fn foo() { 201fn foo() {
188 let <|>var_name = { let x = 0; x }; 202 let $0var_name = { let x = 0; x };
189 something_else(); 203 something_else();
190}", 204}",
191 ); 205 );
@@ -201,7 +215,7 @@ fn foo() {
201}", 215}",
202 " 216 "
203fn foo() { 217fn foo() {
204 let <|>var_name = 1; 218 let $0var_name = 1;
205 var_name + 1; 219 var_name + 1;
206}", 220}",
207 ); 221 );
@@ -209,7 +223,7 @@ fn foo() {
209 223
210 #[test] 224 #[test]
211 fn test_introduce_var_last_expr() { 225 fn test_introduce_var_last_expr() {
212 covers!(test_introduce_var_last_expr); 226 mark::check!(test_introduce_var_last_expr);
213 check_assist( 227 check_assist(
214 introduce_variable, 228 introduce_variable,
215 " 229 "
@@ -218,7 +232,7 @@ fn foo() {
218}", 232}",
219 " 233 "
220fn foo() { 234fn foo() {
221 let <|>var_name = 1 + 1; 235 let $0var_name = 1 + 1;
222 bar(var_name) 236 bar(var_name)
223}", 237}",
224 ); 238 );
@@ -230,7 +244,7 @@ fn foo() {
230}", 244}",
231 " 245 "
232fn foo() { 246fn foo() {
233 let <|>var_name = bar(1 + 1); 247 let $0var_name = bar(1 + 1);
234 var_name 248 var_name
235}", 249}",
236 ) 250 )
@@ -253,7 +267,7 @@ fn main() {
253fn main() { 267fn main() {
254 let x = true; 268 let x = true;
255 let tuple = match x { 269 let tuple = match x {
256 true => { let <|>var_name = 2 + 2; (var_name, true) } 270 true => { let $0var_name = 2 + 2; (var_name, true) }
257 _ => (0, false) 271 _ => (0, false)
258 }; 272 };
259} 273}
@@ -283,7 +297,7 @@ fn main() {
283 let tuple = match x { 297 let tuple = match x {
284 true => { 298 true => {
285 let y = 1; 299 let y = 1;
286 let <|>var_name = 2 + y; 300 let $0var_name = 2 + y;
287 (var_name, true) 301 (var_name, true)
288 } 302 }
289 _ => (0, false) 303 _ => (0, false)
@@ -304,7 +318,7 @@ fn main() {
304", 318",
305 " 319 "
306fn main() { 320fn main() {
307 let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; 321 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
308} 322}
309", 323",
310 ); 324 );
@@ -321,7 +335,7 @@ fn main() {
321", 335",
322 " 336 "
323fn main() { 337fn main() {
324 let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; 338 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
325} 339}
326", 340",
327 ); 341 );
@@ -338,7 +352,7 @@ fn main() {
338", 352",
339 " 353 "
340fn main() { 354fn main() {
341 let <|>var_name = Some(true); 355 let $0var_name = Some(true);
342 let o = var_name; 356 let o = var_name;
343} 357}
344", 358",
@@ -356,7 +370,7 @@ fn main() {
356", 370",
357 " 371 "
358fn main() { 372fn main() {
359 let <|>var_name = bar.foo(); 373 let $0var_name = bar.foo();
360 let v = var_name; 374 let v = var_name;
361} 375}
362", 376",
@@ -374,7 +388,7 @@ fn foo() -> u32 {
374", 388",
375 " 389 "
376fn foo() -> u32 { 390fn foo() -> u32 {
377 let <|>var_name = 2 + 2; 391 let $0var_name = 2 + 2;
378 return var_name; 392 return var_name;
379} 393}
380", 394",
@@ -396,7 +410,7 @@ fn foo() -> u32 {
396fn foo() -> u32 { 410fn foo() -> u32 {
397 411
398 412
399 let <|>var_name = 2 + 2; 413 let $0var_name = 2 + 2;
400 return var_name; 414 return var_name;
401} 415}
402", 416",
@@ -413,7 +427,7 @@ fn foo() -> u32 {
413 " 427 "
414fn foo() -> u32 { 428fn foo() -> u32 {
415 429
416 let <|>var_name = 2 + 2; 430 let $0var_name = 2 + 2;
417 return var_name; 431 return var_name;
418} 432}
419", 433",
@@ -438,7 +452,7 @@ fn foo() -> u32 {
438 // bar 452 // bar
439 453
440 454
441 let <|>var_name = 2 + 2; 455 let $0var_name = 2 + 2;
442 return var_name; 456 return var_name;
443} 457}
444", 458",
@@ -459,7 +473,7 @@ fn main() {
459 " 473 "
460fn main() { 474fn main() {
461 let result = loop { 475 let result = loop {
462 let <|>var_name = 2 + 2; 476 let $0var_name = 2 + 2;
463 break var_name; 477 break var_name;
464 }; 478 };
465} 479}
@@ -478,7 +492,7 @@ fn main() {
478", 492",
479 " 493 "
480fn main() { 494fn main() {
481 let <|>var_name = 0f32 as u32; 495 let $0var_name = 0f32 as u32;
482 let v = var_name; 496 let v = var_name;
483} 497}
484", 498",