diff options
Diffstat (limited to 'crates/ra_editor/src/assists/introduce_variable.rs')
-rw-r--r-- | crates/ra_editor/src/assists/introduce_variable.rs | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/crates/ra_editor/src/assists/introduce_variable.rs b/crates/ra_editor/src/assists/introduce_variable.rs new file mode 100644 index 000000000..17ab521fa --- /dev/null +++ b/crates/ra_editor/src/assists/introduce_variable.rs | |||
@@ -0,0 +1,156 @@ | |||
1 | use ra_text_edit::TextEditBuilder; | ||
2 | use ra_syntax::{ | ||
3 | algo::{find_covering_node}, | ||
4 | ast::{self, AstNode}, | ||
5 | SourceFileNode, | ||
6 | SyntaxKind::{WHITESPACE}, | ||
7 | SyntaxNodeRef, TextRange, TextUnit, | ||
8 | }; | ||
9 | |||
10 | use crate::assists::LocalEdit; | ||
11 | |||
12 | pub fn introduce_variable<'a>( | ||
13 | file: &'a SourceFileNode, | ||
14 | range: TextRange, | ||
15 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
16 | let node = find_covering_node(file.syntax(), range); | ||
17 | let expr = node.ancestors().filter_map(ast::Expr::cast).next()?; | ||
18 | |||
19 | let anchor_stmt = anchor_stmt(expr)?; | ||
20 | let indent = anchor_stmt.prev_sibling()?; | ||
21 | if indent.kind() != WHITESPACE { | ||
22 | return None; | ||
23 | } | ||
24 | return Some(move || { | ||
25 | let mut buf = String::new(); | ||
26 | let mut edit = TextEditBuilder::new(); | ||
27 | |||
28 | buf.push_str("let var_name = "); | ||
29 | expr.syntax().text().push_to(&mut buf); | ||
30 | let is_full_stmt = if let Some(expr_stmt) = ast::ExprStmt::cast(anchor_stmt) { | ||
31 | Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax()) | ||
32 | } else { | ||
33 | false | ||
34 | }; | ||
35 | if is_full_stmt { | ||
36 | edit.replace(expr.syntax().range(), buf); | ||
37 | } else { | ||
38 | buf.push_str(";"); | ||
39 | indent.text().push_to(&mut buf); | ||
40 | edit.replace(expr.syntax().range(), "var_name".to_string()); | ||
41 | edit.insert(anchor_stmt.range().start(), buf); | ||
42 | } | ||
43 | let cursor_position = anchor_stmt.range().start() + TextUnit::of_str("let "); | ||
44 | LocalEdit { | ||
45 | label: "introduce variable".to_string(), | ||
46 | edit: edit.finish(), | ||
47 | cursor_position: Some(cursor_position), | ||
48 | } | ||
49 | }); | ||
50 | |||
51 | /// Statement or last in the block expression, which will follow | ||
52 | /// the freshly introduced var. | ||
53 | fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> { | ||
54 | expr.syntax().ancestors().find(|&node| { | ||
55 | if ast::Stmt::cast(node).is_some() { | ||
56 | return true; | ||
57 | } | ||
58 | if let Some(expr) = node | ||
59 | .parent() | ||
60 | .and_then(ast::Block::cast) | ||
61 | .and_then(|it| it.expr()) | ||
62 | { | ||
63 | if expr.syntax() == node { | ||
64 | return true; | ||
65 | } | ||
66 | } | ||
67 | false | ||
68 | }) | ||
69 | } | ||
70 | } | ||
71 | |||
72 | #[cfg(test)] | ||
73 | mod tests { | ||
74 | use super::*; | ||
75 | use crate::test_utils::check_action_range; | ||
76 | |||
77 | #[test] | ||
78 | fn test_introduce_var_simple() { | ||
79 | check_action_range( | ||
80 | " | ||
81 | fn foo() { | ||
82 | foo(<|>1 + 1<|>); | ||
83 | }", | ||
84 | " | ||
85 | fn foo() { | ||
86 | let <|>var_name = 1 + 1; | ||
87 | foo(var_name); | ||
88 | }", | ||
89 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
90 | ); | ||
91 | } | ||
92 | |||
93 | #[test] | ||
94 | fn test_introduce_var_expr_stmt() { | ||
95 | check_action_range( | ||
96 | " | ||
97 | fn foo() { | ||
98 | <|>1 + 1<|>; | ||
99 | }", | ||
100 | " | ||
101 | fn foo() { | ||
102 | let <|>var_name = 1 + 1; | ||
103 | }", | ||
104 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
105 | ); | ||
106 | } | ||
107 | |||
108 | #[test] | ||
109 | fn test_introduce_var_part_of_expr_stmt() { | ||
110 | check_action_range( | ||
111 | " | ||
112 | fn foo() { | ||
113 | <|>1<|> + 1; | ||
114 | }", | ||
115 | " | ||
116 | fn foo() { | ||
117 | let <|>var_name = 1; | ||
118 | var_name + 1; | ||
119 | }", | ||
120 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
121 | ); | ||
122 | } | ||
123 | |||
124 | #[test] | ||
125 | fn test_introduce_var_last_expr() { | ||
126 | check_action_range( | ||
127 | " | ||
128 | fn foo() { | ||
129 | bar(<|>1 + 1<|>) | ||
130 | }", | ||
131 | " | ||
132 | fn foo() { | ||
133 | let <|>var_name = 1 + 1; | ||
134 | bar(var_name) | ||
135 | }", | ||
136 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
137 | ); | ||
138 | } | ||
139 | |||
140 | #[test] | ||
141 | fn test_introduce_var_last_full_expr() { | ||
142 | check_action_range( | ||
143 | " | ||
144 | fn foo() { | ||
145 | <|>bar(1 + 1)<|> | ||
146 | }", | ||
147 | " | ||
148 | fn foo() { | ||
149 | let <|>var_name = bar(1 + 1); | ||
150 | var_name | ||
151 | }", | ||
152 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
153 | ); | ||
154 | } | ||
155 | |||
156 | } | ||