aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_editor/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_editor/src')
-rw-r--r--crates/ra_editor/src/assists.rs421
-rw-r--r--crates/ra_editor/src/assists/add_derive.rs97
-rw-r--r--crates/ra_editor/src/assists/add_impl.rs78
-rw-r--r--crates/ra_editor/src/assists/change_visibility.rs90
-rw-r--r--crates/ra_editor/src/assists/flip_comma.rs45
-rw-r--r--crates/ra_editor/src/assists/introduce_variable.rs156
-rw-r--r--crates/ra_editor/src/lib.rs2
7 files changed, 487 insertions, 402 deletions
diff --git a/crates/ra_editor/src/assists.rs b/crates/ra_editor/src/assists.rs
index 7615f37a6..b6e6dd628 100644
--- a/crates/ra_editor/src/assists.rs
+++ b/crates/ra_editor/src/assists.rs
@@ -1,15 +1,25 @@
1use join_to_string::join; 1//! This modules contains various "assits": suggestions for source code edits
2 2//! which are likely to occur at a given cursor positon. For example, if the
3use ra_syntax::{ 3//! cursor is on the `,`, a possible assist is swapping the elments around the
4 algo::{find_covering_node, find_leaf_at_offset}, 4//! comma.
5 ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner}, 5
6 Direction, SourceFileNode, 6mod flip_comma;
7 SyntaxKind::{COMMA, WHITESPACE, COMMENT, VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF}, 7mod add_derive;
8 SyntaxNodeRef, TextRange, TextUnit, 8mod add_impl;
9mod introduce_variable;
10mod change_visibility;
11
12use ra_text_edit::TextEdit;
13use ra_syntax::{Direction, SyntaxNodeRef, TextUnit};
14
15pub use self::{
16 flip_comma::flip_comma,
17 add_derive::add_derive,
18 add_impl::add_impl,
19 introduce_variable::introduce_variable,
20 change_visibility::change_visibility,
9}; 21};
10 22
11use crate::{find_node_at_offset, TextEdit, TextEditBuilder};
12
13#[derive(Debug)] 23#[derive(Debug)]
14pub struct LocalEdit { 24pub struct LocalEdit {
15 pub label: String, 25 pub label: String,
@@ -17,399 +27,8 @@ pub struct LocalEdit {
17 pub cursor_position: Option<TextUnit>, 27 pub cursor_position: Option<TextUnit>,
18} 28}
19 29
20pub fn flip_comma<'a>(
21 file: &'a SourceFileNode,
22 offset: TextUnit,
23) -> Option<impl FnOnce() -> LocalEdit + 'a> {
24 let syntax = file.syntax();
25
26 let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?;
27 let prev = non_trivia_sibling(comma, Direction::Prev)?;
28 let next = non_trivia_sibling(comma, Direction::Next)?;
29 Some(move || {
30 let mut edit = TextEditBuilder::new();
31 edit.replace(prev.range(), next.text().to_string());
32 edit.replace(next.range(), prev.text().to_string());
33 LocalEdit {
34 label: "flip comma".to_string(),
35 edit: edit.finish(),
36 cursor_position: None,
37 }
38 })
39}
40
41pub fn add_derive<'a>(
42 file: &'a SourceFileNode,
43 offset: TextUnit,
44) -> Option<impl FnOnce() -> LocalEdit + 'a> {
45 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
46 let node_start = derive_insertion_offset(nominal)?;
47 return Some(move || {
48 let derive_attr = nominal
49 .attrs()
50 .filter_map(|x| x.as_call())
51 .filter(|(name, _arg)| name == "derive")
52 .map(|(_name, arg)| arg)
53 .next();
54 let mut edit = TextEditBuilder::new();
55 let offset = match derive_attr {
56 None => {
57 edit.insert(node_start, "#[derive()]\n".to_string());
58 node_start + TextUnit::of_str("#[derive(")
59 }
60 Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'),
61 };
62 LocalEdit {
63 label: "add `#[derive]`".to_string(),
64 edit: edit.finish(),
65 cursor_position: Some(offset),
66 }
67 });
68
69 // Insert `derive` after doc comments.
70 fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> {
71 let non_ws_child = nominal
72 .syntax()
73 .children()
74 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
75 Some(non_ws_child.range().start())
76 }
77}
78
79pub fn add_impl<'a>(
80 file: &'a SourceFileNode,
81 offset: TextUnit,
82) -> Option<impl FnOnce() -> LocalEdit + 'a> {
83 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
84 let name = nominal.name()?;
85
86 Some(move || {
87 let type_params = nominal.type_param_list();
88 let mut edit = TextEditBuilder::new();
89 let start_offset = nominal.syntax().range().end();
90 let mut buf = String::new();
91 buf.push_str("\n\nimpl");
92 if let Some(type_params) = type_params {
93 type_params.syntax().text().push_to(&mut buf);
94 }
95 buf.push_str(" ");
96 buf.push_str(name.text().as_str());
97 if let Some(type_params) = type_params {
98 let lifetime_params = type_params
99 .lifetime_params()
100 .filter_map(|it| it.lifetime())
101 .map(|it| it.text());
102 let type_params = type_params
103 .type_params()
104 .filter_map(|it| it.name())
105 .map(|it| it.text());
106 join(lifetime_params.chain(type_params))
107 .surround_with("<", ">")
108 .to_buf(&mut buf);
109 }
110 buf.push_str(" {\n");
111 let offset = start_offset + TextUnit::of_str(&buf);
112 buf.push_str("\n}");
113 edit.insert(start_offset, buf);
114 LocalEdit {
115 label: "add impl".to_string(),
116 edit: edit.finish(),
117 cursor_position: Some(offset),
118 }
119 })
120}
121
122pub fn introduce_variable<'a>(
123 file: &'a SourceFileNode,
124 range: TextRange,
125) -> Option<impl FnOnce() -> LocalEdit + 'a> {
126 let node = find_covering_node(file.syntax(), range);
127 let expr = node.ancestors().filter_map(ast::Expr::cast).next()?;
128
129 let anchor_stmt = anchor_stmt(expr)?;
130 let indent = anchor_stmt.prev_sibling()?;
131 if indent.kind() != WHITESPACE {
132 return None;
133 }
134 return Some(move || {
135 let mut buf = String::new();
136 let mut edit = TextEditBuilder::new();
137
138 buf.push_str("let var_name = ");
139 expr.syntax().text().push_to(&mut buf);
140 let is_full_stmt = if let Some(expr_stmt) = ast::ExprStmt::cast(anchor_stmt) {
141 Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax())
142 } else {
143 false
144 };
145 if is_full_stmt {
146 edit.replace(expr.syntax().range(), buf);
147 } else {
148 buf.push_str(";");
149 indent.text().push_to(&mut buf);
150 edit.replace(expr.syntax().range(), "var_name".to_string());
151 edit.insert(anchor_stmt.range().start(), buf);
152 }
153 let cursor_position = anchor_stmt.range().start() + TextUnit::of_str("let ");
154 LocalEdit {
155 label: "introduce variable".to_string(),
156 edit: edit.finish(),
157 cursor_position: Some(cursor_position),
158 }
159 });
160
161 /// Statement or last in the block expression, which will follow
162 /// the freshly introduced var.
163 fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> {
164 expr.syntax().ancestors().find(|&node| {
165 if ast::Stmt::cast(node).is_some() {
166 return true;
167 }
168 if let Some(expr) = node
169 .parent()
170 .and_then(ast::Block::cast)
171 .and_then(|it| it.expr())
172 {
173 if expr.syntax() == node {
174 return true;
175 }
176 }
177 false
178 })
179 }
180}
181
182pub fn make_pub_crate<'a>(
183 file: &'a SourceFileNode,
184 offset: TextUnit,
185) -> Option<impl FnOnce() -> LocalEdit + 'a> {
186 let syntax = file.syntax();
187
188 let keyword = find_leaf_at_offset(syntax, offset).find(|leaf| match leaf.kind() {
189 FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true,
190 _ => false,
191 })?;
192 let parent = keyword.parent()?;
193 let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
194 let node_start = parent.range().start();
195 Some(move || {
196 let mut edit = TextEditBuilder::new();
197
198 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind())
199 || parent.children().any(|child| child.kind() == VISIBILITY)
200 {
201 return LocalEdit {
202 label: "make pub crate".to_string(),
203 edit: edit.finish(),
204 cursor_position: Some(offset),
205 };
206 }
207
208 edit.insert(node_start, "pub(crate) ".to_string());
209 LocalEdit {
210 label: "make pub crate".to_string(),
211 edit: edit.finish(),
212 cursor_position: Some(node_start),
213 }
214 })
215}
216
217fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> { 30fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> {
218 node.siblings(direction) 31 node.siblings(direction)
219 .skip(1) 32 .skip(1)
220 .find(|node| !node.kind().is_trivia()) 33 .find(|node| !node.kind().is_trivia())
221} 34}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::test_utils::{check_action, check_action_range};
227
228 #[test]
229 fn test_swap_comma() {
230 check_action(
231 "fn foo(x: i32,<|> y: Result<(), ()>) {}",
232 "fn foo(y: Result<(), ()>,<|> x: i32) {}",
233 |file, off| flip_comma(file, off).map(|f| f()),
234 )
235 }
236
237 #[test]
238 fn add_derive_new() {
239 check_action(
240 "struct Foo { a: i32, <|>}",
241 "#[derive(<|>)]\nstruct Foo { a: i32, }",
242 |file, off| add_derive(file, off).map(|f| f()),
243 );
244 check_action(
245 "struct Foo { <|> a: i32, }",
246 "#[derive(<|>)]\nstruct Foo { a: i32, }",
247 |file, off| add_derive(file, off).map(|f| f()),
248 );
249 }
250
251 #[test]
252 fn add_derive_existing() {
253 check_action(
254 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
255 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
256 |file, off| add_derive(file, off).map(|f| f()),
257 );
258 }
259
260 #[test]
261 fn add_derive_new_with_doc_comment() {
262 check_action(
263 "
264/// `Foo` is a pretty important struct.
265/// It does stuff.
266struct Foo { a: i32<|>, }
267 ",
268 "
269/// `Foo` is a pretty important struct.
270/// It does stuff.
271#[derive(<|>)]
272struct Foo { a: i32, }
273 ",
274 |file, off| add_derive(file, off).map(|f| f()),
275 );
276 }
277
278 #[test]
279 fn test_add_impl() {
280 check_action(
281 "struct Foo {<|>}\n",
282 "struct Foo {}\n\nimpl Foo {\n<|>\n}\n",
283 |file, off| add_impl(file, off).map(|f| f()),
284 );
285 check_action(
286 "struct Foo<T: Clone> {<|>}",
287 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}",
288 |file, off| add_impl(file, off).map(|f| f()),
289 );
290 check_action(
291 "struct Foo<'a, T: Foo<'a>> {<|>}",
292 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}",
293 |file, off| add_impl(file, off).map(|f| f()),
294 );
295 }
296
297 #[test]
298 fn test_introduce_var_simple() {
299 check_action_range(
300 "
301fn foo() {
302 foo(<|>1 + 1<|>);
303}",
304 "
305fn foo() {
306 let <|>var_name = 1 + 1;
307 foo(var_name);
308}",
309 |file, range| introduce_variable(file, range).map(|f| f()),
310 );
311 }
312
313 #[test]
314 fn test_introduce_var_expr_stmt() {
315 check_action_range(
316 "
317fn foo() {
318 <|>1 + 1<|>;
319}",
320 "
321fn foo() {
322 let <|>var_name = 1 + 1;
323}",
324 |file, range| introduce_variable(file, range).map(|f| f()),
325 );
326 }
327
328 #[test]
329 fn test_introduce_var_part_of_expr_stmt() {
330 check_action_range(
331 "
332fn foo() {
333 <|>1<|> + 1;
334}",
335 "
336fn foo() {
337 let <|>var_name = 1;
338 var_name + 1;
339}",
340 |file, range| introduce_variable(file, range).map(|f| f()),
341 );
342 }
343
344 #[test]
345 fn test_introduce_var_last_expr() {
346 check_action_range(
347 "
348fn foo() {
349 bar(<|>1 + 1<|>)
350}",
351 "
352fn foo() {
353 let <|>var_name = 1 + 1;
354 bar(var_name)
355}",
356 |file, range| introduce_variable(file, range).map(|f| f()),
357 );
358 }
359
360 #[test]
361 fn test_introduce_var_last_full_expr() {
362 check_action_range(
363 "
364fn foo() {
365 <|>bar(1 + 1)<|>
366}",
367 "
368fn foo() {
369 let <|>var_name = bar(1 + 1);
370 var_name
371}",
372 |file, range| introduce_variable(file, range).map(|f| f()),
373 );
374 }
375
376 #[test]
377 fn test_make_pub_crate() {
378 check_action(
379 "<|>fn foo() {}",
380 "<|>pub(crate) fn foo() {}",
381 |file, off| make_pub_crate(file, off).map(|f| f()),
382 );
383 check_action(
384 "f<|>n foo() {}",
385 "<|>pub(crate) fn foo() {}",
386 |file, off| make_pub_crate(file, off).map(|f| f()),
387 );
388 check_action(
389 "<|>struct Foo {}",
390 "<|>pub(crate) struct Foo {}",
391 |file, off| make_pub_crate(file, off).map(|f| f()),
392 );
393 check_action("<|>mod foo {}", "<|>pub(crate) mod foo {}", |file, off| {
394 make_pub_crate(file, off).map(|f| f())
395 });
396 check_action(
397 "<|>trait Foo {}",
398 "<|>pub(crate) trait Foo {}",
399 |file, off| make_pub_crate(file, off).map(|f| f()),
400 );
401 check_action("m<|>od {}", "<|>pub(crate) mod {}", |file, off| {
402 make_pub_crate(file, off).map(|f| f())
403 });
404 check_action(
405 "pub(crate) f<|>n foo() {}",
406 "pub(crate) f<|>n foo() {}",
407 |file, off| make_pub_crate(file, off).map(|f| f()),
408 );
409 check_action(
410 "unsafe f<|>n foo() {}",
411 "<|>pub(crate) unsafe fn foo() {}",
412 |file, off| make_pub_crate(file, off).map(|f| f()),
413 );
414 }
415}
diff --git a/crates/ra_editor/src/assists/add_derive.rs b/crates/ra_editor/src/assists/add_derive.rs
new file mode 100644
index 000000000..33d9d2c31
--- /dev/null
+++ b/crates/ra_editor/src/assists/add_derive.rs
@@ -0,0 +1,97 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{
3 ast::{self, AstNode, AttrsOwner},
4 SourceFileNode,
5 SyntaxKind::{WHITESPACE, COMMENT},
6 TextUnit,
7};
8
9use crate::{
10 find_node_at_offset,
11 assists::LocalEdit,
12};
13
14pub fn add_derive<'a>(
15 file: &'a SourceFileNode,
16 offset: TextUnit,
17) -> Option<impl FnOnce() -> LocalEdit + 'a> {
18 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
19 let node_start = derive_insertion_offset(nominal)?;
20 return Some(move || {
21 let derive_attr = nominal
22 .attrs()
23 .filter_map(|x| x.as_call())
24 .filter(|(name, _arg)| name == "derive")
25 .map(|(_name, arg)| arg)
26 .next();
27 let mut edit = TextEditBuilder::new();
28 let offset = match derive_attr {
29 None => {
30 edit.insert(node_start, "#[derive()]\n".to_string());
31 node_start + TextUnit::of_str("#[derive(")
32 }
33 Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'),
34 };
35 LocalEdit {
36 label: "add `#[derive]`".to_string(),
37 edit: edit.finish(),
38 cursor_position: Some(offset),
39 }
40 });
41
42 // Insert `derive` after doc comments.
43 fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> {
44 let non_ws_child = nominal
45 .syntax()
46 .children()
47 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
48 Some(non_ws_child.range().start())
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::test_utils::check_action;
56
57 #[test]
58 fn add_derive_new() {
59 check_action(
60 "struct Foo { a: i32, <|>}",
61 "#[derive(<|>)]\nstruct Foo { a: i32, }",
62 |file, off| add_derive(file, off).map(|f| f()),
63 );
64 check_action(
65 "struct Foo { <|> a: i32, }",
66 "#[derive(<|>)]\nstruct Foo { a: i32, }",
67 |file, off| add_derive(file, off).map(|f| f()),
68 );
69 }
70
71 #[test]
72 fn add_derive_existing() {
73 check_action(
74 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
75 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
76 |file, off| add_derive(file, off).map(|f| f()),
77 );
78 }
79
80 #[test]
81 fn add_derive_new_with_doc_comment() {
82 check_action(
83 "
84/// `Foo` is a pretty important struct.
85/// It does stuff.
86struct Foo { a: i32<|>, }
87 ",
88 "
89/// `Foo` is a pretty important struct.
90/// It does stuff.
91#[derive(<|>)]
92struct Foo { a: i32, }
93 ",
94 |file, off| add_derive(file, off).map(|f| f()),
95 );
96 }
97}
diff --git a/crates/ra_editor/src/assists/add_impl.rs b/crates/ra_editor/src/assists/add_impl.rs
new file mode 100644
index 000000000..50e00688e
--- /dev/null
+++ b/crates/ra_editor/src/assists/add_impl.rs
@@ -0,0 +1,78 @@
1use join_to_string::join;
2use ra_text_edit::TextEditBuilder;
3use ra_syntax::{
4 ast::{self, AstNode, NameOwner, TypeParamsOwner},
5 SourceFileNode,
6 TextUnit,
7};
8
9use crate::{find_node_at_offset, assists::LocalEdit};
10
11pub fn add_impl<'a>(
12 file: &'a SourceFileNode,
13 offset: TextUnit,
14) -> Option<impl FnOnce() -> LocalEdit + 'a> {
15 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
16 let name = nominal.name()?;
17
18 Some(move || {
19 let type_params = nominal.type_param_list();
20 let mut edit = TextEditBuilder::new();
21 let start_offset = nominal.syntax().range().end();
22 let mut buf = String::new();
23 buf.push_str("\n\nimpl");
24 if let Some(type_params) = type_params {
25 type_params.syntax().text().push_to(&mut buf);
26 }
27 buf.push_str(" ");
28 buf.push_str(name.text().as_str());
29 if let Some(type_params) = type_params {
30 let lifetime_params = type_params
31 .lifetime_params()
32 .filter_map(|it| it.lifetime())
33 .map(|it| it.text());
34 let type_params = type_params
35 .type_params()
36 .filter_map(|it| it.name())
37 .map(|it| it.text());
38 join(lifetime_params.chain(type_params))
39 .surround_with("<", ">")
40 .to_buf(&mut buf);
41 }
42 buf.push_str(" {\n");
43 let offset = start_offset + TextUnit::of_str(&buf);
44 buf.push_str("\n}");
45 edit.insert(start_offset, buf);
46 LocalEdit {
47 label: "add impl".to_string(),
48 edit: edit.finish(),
49 cursor_position: Some(offset),
50 }
51 })
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use crate::test_utils::check_action;
58
59 #[test]
60 fn test_add_impl() {
61 check_action(
62 "struct Foo {<|>}\n",
63 "struct Foo {}\n\nimpl Foo {\n<|>\n}\n",
64 |file, off| add_impl(file, off).map(|f| f()),
65 );
66 check_action(
67 "struct Foo<T: Clone> {<|>}",
68 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}",
69 |file, off| add_impl(file, off).map(|f| f()),
70 );
71 check_action(
72 "struct Foo<'a, T: Foo<'a>> {<|>}",
73 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}",
74 |file, off| add_impl(file, off).map(|f| f()),
75 );
76 }
77
78}
diff --git a/crates/ra_editor/src/assists/change_visibility.rs b/crates/ra_editor/src/assists/change_visibility.rs
new file mode 100644
index 000000000..98c218f32
--- /dev/null
+++ b/crates/ra_editor/src/assists/change_visibility.rs
@@ -0,0 +1,90 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{
3 SourceFileNode,
4 algo::find_leaf_at_offset,
5 SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF},
6 TextUnit,
7};
8
9use crate::assists::LocalEdit;
10
11pub fn change_visibility<'a>(
12 file: &'a SourceFileNode,
13 offset: TextUnit,
14) -> Option<impl FnOnce() -> LocalEdit + 'a> {
15 let syntax = file.syntax();
16
17 let keyword = find_leaf_at_offset(syntax, offset).find(|leaf| match leaf.kind() {
18 FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true,
19 _ => false,
20 })?;
21 let parent = keyword.parent()?;
22 let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
23 let node_start = parent.range().start();
24 Some(move || {
25 let mut edit = TextEditBuilder::new();
26
27 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind())
28 || parent.children().any(|child| child.kind() == VISIBILITY)
29 {
30 return LocalEdit {
31 label: "make pub crate".to_string(),
32 edit: edit.finish(),
33 cursor_position: Some(offset),
34 };
35 }
36
37 edit.insert(node_start, "pub(crate) ".to_string());
38 LocalEdit {
39 label: "make pub crate".to_string(),
40 edit: edit.finish(),
41 cursor_position: Some(node_start),
42 }
43 })
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use crate::test_utils::check_action;
50
51 #[test]
52 fn test_change_visibility() {
53 check_action(
54 "<|>fn foo() {}",
55 "<|>pub(crate) fn foo() {}",
56 |file, off| change_visibility(file, off).map(|f| f()),
57 );
58 check_action(
59 "f<|>n foo() {}",
60 "<|>pub(crate) fn foo() {}",
61 |file, off| change_visibility(file, off).map(|f| f()),
62 );
63 check_action(
64 "<|>struct Foo {}",
65 "<|>pub(crate) struct Foo {}",
66 |file, off| change_visibility(file, off).map(|f| f()),
67 );
68 check_action("<|>mod foo {}", "<|>pub(crate) mod foo {}", |file, off| {
69 change_visibility(file, off).map(|f| f())
70 });
71 check_action(
72 "<|>trait Foo {}",
73 "<|>pub(crate) trait Foo {}",
74 |file, off| change_visibility(file, off).map(|f| f()),
75 );
76 check_action("m<|>od {}", "<|>pub(crate) mod {}", |file, off| {
77 change_visibility(file, off).map(|f| f())
78 });
79 check_action(
80 "pub(crate) f<|>n foo() {}",
81 "pub(crate) f<|>n foo() {}",
82 |file, off| change_visibility(file, off).map(|f| f()),
83 );
84 check_action(
85 "unsafe f<|>n foo() {}",
86 "<|>pub(crate) unsafe fn foo() {}",
87 |file, off| change_visibility(file, off).map(|f| f()),
88 );
89 }
90}
diff --git a/crates/ra_editor/src/assists/flip_comma.rs b/crates/ra_editor/src/assists/flip_comma.rs
new file mode 100644
index 000000000..d8727db0d
--- /dev/null
+++ b/crates/ra_editor/src/assists/flip_comma.rs
@@ -0,0 +1,45 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{
3 algo::find_leaf_at_offset,
4 Direction, SourceFileNode,
5 SyntaxKind::COMMA,
6 TextUnit,
7};
8
9use crate::assists::{LocalEdit, non_trivia_sibling};
10
11pub fn flip_comma<'a>(
12 file: &'a SourceFileNode,
13 offset: TextUnit,
14) -> Option<impl FnOnce() -> LocalEdit + 'a> {
15 let syntax = file.syntax();
16
17 let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?;
18 let prev = non_trivia_sibling(comma, Direction::Prev)?;
19 let next = non_trivia_sibling(comma, Direction::Next)?;
20 Some(move || {
21 let mut edit = TextEditBuilder::new();
22 edit.replace(prev.range(), next.text().to_string());
23 edit.replace(next.range(), prev.text().to_string());
24 LocalEdit {
25 label: "flip comma".to_string(),
26 edit: edit.finish(),
27 cursor_position: None,
28 }
29 })
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35 use crate::test_utils::check_action;
36
37 #[test]
38 fn test_swap_comma() {
39 check_action(
40 "fn foo(x: i32,<|> y: Result<(), ()>) {}",
41 "fn foo(y: Result<(), ()>,<|> x: i32) {}",
42 |file, off| flip_comma(file, off).map(|f| f()),
43 )
44 }
45}
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}
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs
index 46d521e4b..ac283e2e0 100644
--- a/crates/ra_editor/src/lib.rs
+++ b/crates/ra_editor/src/lib.rs
@@ -19,7 +19,7 @@ pub use self::{
19 typing::{join_lines, on_enter, on_eq_typed}, 19 typing::{join_lines, on_enter, on_eq_typed},
20 diagnostics::diagnostics 20 diagnostics::diagnostics
21}; 21};
22use ra_text_edit::{TextEdit, TextEditBuilder}; 22use ra_text_edit::TextEditBuilder;
23use ra_syntax::{ 23use ra_syntax::{
24 algo::find_leaf_at_offset, 24 algo::find_leaf_at_offset,
25 ast::{self, AstNode}, 25 ast::{self, AstNode},