diff options
Diffstat (limited to 'crates/ra_editor/src/assists')
-rw-r--r-- | crates/ra_editor/src/assists/add_derive.rs | 97 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/add_impl.rs | 78 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/change_visibility.rs | 90 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/flip_comma.rs | 45 | ||||
-rw-r--r-- | crates/ra_editor/src/assists/introduce_variable.rs | 156 |
5 files changed, 466 insertions, 0 deletions
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 @@ | |||
1 | use ra_text_edit::TextEditBuilder; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, AttrsOwner}, | ||
4 | SourceFileNode, | ||
5 | SyntaxKind::{WHITESPACE, COMMENT}, | ||
6 | TextUnit, | ||
7 | }; | ||
8 | |||
9 | use crate::{ | ||
10 | find_node_at_offset, | ||
11 | assists::LocalEdit, | ||
12 | }; | ||
13 | |||
14 | pub 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)] | ||
53 | mod 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. | ||
86 | struct Foo { a: i32<|>, } | ||
87 | ", | ||
88 | " | ||
89 | /// `Foo` is a pretty important struct. | ||
90 | /// It does stuff. | ||
91 | #[derive(<|>)] | ||
92 | struct 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 @@ | |||
1 | use join_to_string::join; | ||
2 | use ra_text_edit::TextEditBuilder; | ||
3 | use ra_syntax::{ | ||
4 | ast::{self, AstNode, NameOwner, TypeParamsOwner}, | ||
5 | SourceFileNode, | ||
6 | TextUnit, | ||
7 | }; | ||
8 | |||
9 | use crate::{find_node_at_offset, assists::LocalEdit}; | ||
10 | |||
11 | pub 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)] | ||
55 | mod 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 @@ | |||
1 | use ra_text_edit::TextEditBuilder; | ||
2 | use 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 | |||
9 | use crate::assists::LocalEdit; | ||
10 | |||
11 | pub 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)] | ||
47 | mod 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 @@ | |||
1 | use ra_text_edit::TextEditBuilder; | ||
2 | use ra_syntax::{ | ||
3 | algo::find_leaf_at_offset, | ||
4 | Direction, SourceFileNode, | ||
5 | SyntaxKind::COMMA, | ||
6 | TextUnit, | ||
7 | }; | ||
8 | |||
9 | use crate::assists::{LocalEdit, non_trivia_sibling}; | ||
10 | |||
11 | pub 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)] | ||
33 | mod 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 @@ | |||
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 | } | ||