aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_editor/src/code_actions.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-09-16 10:54:24 +0100
committerAleksey Kladov <[email protected]>2018-09-16 11:07:39 +0100
commitb5021411a84822cb3f1e3aeffad9550dd15bdeb6 (patch)
tree9dca564f8e51b298dced01c4ce669c756dce3142 /crates/ra_editor/src/code_actions.rs
parentba0bfeee12e19da40b5eabc8d0408639af10e96f (diff)
rename all things
Diffstat (limited to 'crates/ra_editor/src/code_actions.rs')
-rw-r--r--crates/ra_editor/src/code_actions.rs218
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 @@
1use join_to_string::join;
2
3use 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
16use {EditBuilder, Edit, find_node_at_offset};
17
18#[derive(Debug)]
19pub struct LocalEdit {
20 pub edit: Edit,
21 pub cursor_position: Option<TextUnit>,
22}
23
24pub 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
41pub 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
68pub 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
102pub 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
132fn 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)]
139mod 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 "
194fn foo() {
195 foo(<|>1 + 1<|>);
196}", "
197fn 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() {
206check_action_range(
207 "
208fn foo() {
209 <|>1 + 1<|>;
210}", "
211fn foo() {
212 let <|>var_name = 1 + 1;
213}",
214 |file, range| introduce_variable(file, range).map(|f| f()),
215 );
216 }
217
218}