diff options
Diffstat (limited to 'crates/ra_editor/src/code_actions.rs')
-rw-r--r-- | crates/ra_editor/src/code_actions.rs | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/crates/ra_editor/src/code_actions.rs b/crates/ra_editor/src/code_actions.rs new file mode 100644 index 000000000..83f7956d2 --- /dev/null +++ b/crates/ra_editor/src/code_actions.rs | |||
@@ -0,0 +1,218 @@ | |||
1 | use join_to_string::join; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | File, TextUnit, TextRange, | ||
5 | ast::{self, AstNode, AttrsOwner, TypeParamsOwner, NameOwner}, | ||
6 | SyntaxKind::{COMMA, WHITESPACE}, | ||
7 | SyntaxNodeRef, | ||
8 | algo::{ | ||
9 | Direction, siblings, | ||
10 | find_leaf_at_offset, | ||
11 | find_covering_node, | ||
12 | ancestors, | ||
13 | }, | ||
14 | }; | ||
15 | |||
16 | use {EditBuilder, Edit, find_node_at_offset}; | ||
17 | |||
18 | #[derive(Debug)] | ||
19 | pub struct LocalEdit { | ||
20 | pub edit: Edit, | ||
21 | pub cursor_position: Option<TextUnit>, | ||
22 | } | ||
23 | |||
24 | pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
25 | let syntax = file.syntax(); | ||
26 | |||
27 | let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?; | ||
28 | let left = non_trivia_sibling(comma, Direction::Backward)?; | ||
29 | let right = non_trivia_sibling(comma, Direction::Forward)?; | ||
30 | Some(move || { | ||
31 | let mut edit = EditBuilder::new(); | ||
32 | edit.replace(left.range(), right.text().to_string()); | ||
33 | edit.replace(right.range(), left.text().to_string()); | ||
34 | LocalEdit { | ||
35 | edit: edit.finish(), | ||
36 | cursor_position: None, | ||
37 | } | ||
38 | }) | ||
39 | } | ||
40 | |||
41 | pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
42 | let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; | ||
43 | Some(move || { | ||
44 | let derive_attr = nominal | ||
45 | .attrs() | ||
46 | .filter_map(|x| x.as_call()) | ||
47 | .filter(|(name, _arg)| name == "derive") | ||
48 | .map(|(_name, arg)| arg) | ||
49 | .next(); | ||
50 | let mut edit = EditBuilder::new(); | ||
51 | let offset = match derive_attr { | ||
52 | None => { | ||
53 | let node_start = nominal.syntax().range().start(); | ||
54 | edit.insert(node_start, "#[derive()]\n".to_string()); | ||
55 | node_start + TextUnit::of_str("#[derive(") | ||
56 | } | ||
57 | Some(tt) => { | ||
58 | tt.syntax().range().end() - TextUnit::of_char(')') | ||
59 | } | ||
60 | }; | ||
61 | LocalEdit { | ||
62 | edit: edit.finish(), | ||
63 | cursor_position: Some(offset), | ||
64 | } | ||
65 | }) | ||
66 | } | ||
67 | |||
68 | pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
69 | let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; | ||
70 | let name = nominal.name()?; | ||
71 | |||
72 | Some(move || { | ||
73 | let type_params = nominal.type_param_list(); | ||
74 | let mut edit = EditBuilder::new(); | ||
75 | let start_offset = nominal.syntax().range().end(); | ||
76 | let mut buf = String::new(); | ||
77 | buf.push_str("\n\nimpl"); | ||
78 | if let Some(type_params) = type_params { | ||
79 | type_params.syntax().text() | ||
80 | .push_to(&mut buf); | ||
81 | } | ||
82 | buf.push_str(" "); | ||
83 | buf.push_str(name.text().as_str()); | ||
84 | if let Some(type_params) = type_params { | ||
85 | let lifetime_params = type_params.lifetime_params().filter_map(|it| it.lifetime()).map(|it| it.text()); | ||
86 | let type_params = type_params.type_params().filter_map(|it| it.name()).map(|it| it.text()); | ||
87 | join(lifetime_params.chain(type_params)) | ||
88 | .surround_with("<", ">") | ||
89 | .to_buf(&mut buf); | ||
90 | } | ||
91 | buf.push_str(" {\n"); | ||
92 | let offset = start_offset + TextUnit::of_str(&buf); | ||
93 | buf.push_str("\n}"); | ||
94 | edit.insert(start_offset, buf); | ||
95 | LocalEdit { | ||
96 | edit: edit.finish(), | ||
97 | cursor_position: Some(offset), | ||
98 | } | ||
99 | }) | ||
100 | } | ||
101 | |||
102 | pub fn introduce_variable<'a>(file: &'a File, range: TextRange) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
103 | let node = find_covering_node(file.syntax(), range); | ||
104 | let expr = ancestors(node).filter_map(ast::Expr::cast).next()?; | ||
105 | let anchor_stmt = ancestors(expr.syntax()).filter_map(ast::Stmt::cast).next()?; | ||
106 | let indent = anchor_stmt.syntax().prev_sibling()?; | ||
107 | if indent.kind() != WHITESPACE { | ||
108 | return None; | ||
109 | } | ||
110 | Some(move || { | ||
111 | let mut buf = String::new(); | ||
112 | let mut edit = EditBuilder::new(); | ||
113 | |||
114 | buf.push_str("let var_name = "); | ||
115 | expr.syntax().text().push_to(&mut buf); | ||
116 | if expr.syntax().range().start() == anchor_stmt.syntax().range().start() { | ||
117 | edit.replace(expr.syntax().range(), buf); | ||
118 | } else { | ||
119 | buf.push_str(";"); | ||
120 | indent.text().push_to(&mut buf); | ||
121 | edit.replace(expr.syntax().range(), "var_name".to_string()); | ||
122 | edit.insert(anchor_stmt.syntax().range().start(), buf); | ||
123 | } | ||
124 | let cursor_position = anchor_stmt.syntax().range().start() + TextUnit::of_str("let "); | ||
125 | LocalEdit { | ||
126 | edit: edit.finish(), | ||
127 | cursor_position: Some(cursor_position), | ||
128 | } | ||
129 | }) | ||
130 | } | ||
131 | |||
132 | fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> { | ||
133 | siblings(node, direction) | ||
134 | .skip(1) | ||
135 | .find(|node| !node.kind().is_trivia()) | ||
136 | } | ||
137 | |||
138 | #[cfg(test)] | ||
139 | mod tests { | ||
140 | use super::*; | ||
141 | use test_utils::{check_action, check_action_range}; | ||
142 | |||
143 | #[test] | ||
144 | fn test_swap_comma() { | ||
145 | check_action( | ||
146 | "fn foo(x: i32,<|> y: Result<(), ()>) {}", | ||
147 | "fn foo(y: Result<(), ()>,<|> x: i32) {}", | ||
148 | |file, off| flip_comma(file, off).map(|f| f()), | ||
149 | ) | ||
150 | } | ||
151 | |||
152 | #[test] | ||
153 | fn test_add_derive() { | ||
154 | check_action( | ||
155 | "struct Foo { a: i32, <|>}", | ||
156 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
157 | |file, off| add_derive(file, off).map(|f| f()), | ||
158 | ); | ||
159 | check_action( | ||
160 | "struct Foo { <|> a: i32, }", | ||
161 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
162 | |file, off| add_derive(file, off).map(|f| f()), | ||
163 | ); | ||
164 | check_action( | ||
165 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", | ||
166 | "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", | ||
167 | |file, off| add_derive(file, off).map(|f| f()), | ||
168 | ); | ||
169 | } | ||
170 | |||
171 | #[test] | ||
172 | fn test_add_impl() { | ||
173 | check_action( | ||
174 | "struct Foo {<|>}\n", | ||
175 | "struct Foo {}\n\nimpl Foo {\n<|>\n}\n", | ||
176 | |file, off| add_impl(file, off).map(|f| f()), | ||
177 | ); | ||
178 | check_action( | ||
179 | "struct Foo<T: Clone> {<|>}", | ||
180 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", | ||
181 | |file, off| add_impl(file, off).map(|f| f()), | ||
182 | ); | ||
183 | check_action( | ||
184 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
185 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", | ||
186 | |file, off| add_impl(file, off).map(|f| f()), | ||
187 | ); | ||
188 | } | ||
189 | |||
190 | #[test] | ||
191 | fn test_intrdoduce_var_simple() { | ||
192 | check_action_range( | ||
193 | " | ||
194 | fn foo() { | ||
195 | foo(<|>1 + 1<|>); | ||
196 | }", " | ||
197 | fn foo() { | ||
198 | let <|>var_name = 1 + 1; | ||
199 | foo(var_name); | ||
200 | }", | ||
201 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
202 | ); | ||
203 | } | ||
204 | #[test] | ||
205 | fn test_intrdoduce_var_expr_stmt() { | ||
206 | check_action_range( | ||
207 | " | ||
208 | fn foo() { | ||
209 | <|>1 + 1<|>; | ||
210 | }", " | ||
211 | fn foo() { | ||
212 | let <|>var_name = 1 + 1; | ||
213 | }", | ||
214 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
215 | ); | ||
216 | } | ||
217 | |||
218 | } | ||