aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_editor/src/assists/introduce_variable.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_editor/src/assists/introduce_variable.rs')
-rw-r--r--crates/ra_editor/src/assists/introduce_variable.rs156
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 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{
3 algo::{find_covering_node},
4 ast::{self, AstNode},
5 SourceFileNode,
6 SyntaxKind::{WHITESPACE},
7 SyntaxNodeRef, TextRange, TextUnit,
8};
9
10use crate::assists::LocalEdit;
11
12pub 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)]
73mod 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 "
81fn foo() {
82 foo(<|>1 + 1<|>);
83}",
84 "
85fn 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 "
97fn foo() {
98 <|>1 + 1<|>;
99}",
100 "
101fn 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 "
112fn foo() {
113 <|>1<|> + 1;
114}",
115 "
116fn 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 "
128fn foo() {
129 bar(<|>1 + 1<|>)
130}",
131 "
132fn 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 "
144fn foo() {
145 <|>bar(1 + 1)<|>
146}",
147 "
148fn 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}