diff options
Diffstat (limited to 'crates/ra_assists')
-rw-r--r-- | crates/ra_assists/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ra_assists/src/add_missing_impl_members.rs | 149 | ||||
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 4 | ||||
-rw-r--r-- | crates/ra_assists/src/ast_editor.rs | 333 | ||||
-rw-r--r-- | crates/ra_assists/src/fill_struct_fields.rs | 142 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 3 |
6 files changed, 444 insertions, 189 deletions
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml index d4056a349..29d9ceb59 100644 --- a/crates/ra_assists/Cargo.toml +++ b/crates/ra_assists/Cargo.toml | |||
@@ -5,8 +5,10 @@ version = "0.1.0" | |||
5 | authors = ["rust-analyzer developers"] | 5 | authors = ["rust-analyzer developers"] |
6 | 6 | ||
7 | [dependencies] | 7 | [dependencies] |
8 | lazy_static = "1.3.0" | ||
8 | join_to_string = "0.1.3" | 9 | join_to_string = "0.1.3" |
9 | itertools = "0.8.0" | 10 | itertools = "0.8.0" |
11 | arrayvec = "0.4.10" | ||
10 | 12 | ||
11 | ra_syntax = { path = "../ra_syntax" } | 13 | ra_syntax = { path = "../ra_syntax" } |
12 | ra_text_edit = { path = "../ra_text_edit" } | 14 | ra_text_edit = { path = "../ra_text_edit" } |
diff --git a/crates/ra_assists/src/add_missing_impl_members.rs b/crates/ra_assists/src/add_missing_impl_members.rs index c82447b84..17c2af899 100644 --- a/crates/ra_assists/src/add_missing_impl_members.rs +++ b/crates/ra_assists/src/add_missing_impl_members.rs | |||
@@ -1,14 +1,9 @@ | |||
1 | use std::fmt::Write; | 1 | use crate::{Assist, AssistId, AssistCtx, ast_editor::{AstEditor, AstBuilder}}; |
2 | |||
3 | use crate::{Assist, AssistId, AssistCtx}; | ||
4 | 2 | ||
5 | use hir::db::HirDatabase; | 3 | use hir::db::HirDatabase; |
6 | use ra_syntax::{SmolStr, SyntaxKind, TextRange, TextUnit, TreeArc}; | 4 | use ra_syntax::{SmolStr, TreeArc}; |
7 | use ra_syntax::ast::{self, AstNode, AstToken, FnDef, ImplItem, ImplItemKind, NameOwner}; | 5 | use ra_syntax::ast::{self, AstNode, FnDef, ImplItem, ImplItemKind, NameOwner}; |
8 | use ra_db::FilePosition; | 6 | use ra_db::FilePosition; |
9 | use ra_fmt::{leading_indent, reindent}; | ||
10 | |||
11 | use itertools::Itertools; | ||
12 | 7 | ||
13 | enum AddMissingImplMembersMode { | 8 | enum AddMissingImplMembersMode { |
14 | DefaultMethodsOnly, | 9 | DefaultMethodsOnly, |
@@ -76,48 +71,35 @@ fn add_missing_impl_members_inner( | |||
76 | } | 71 | } |
77 | 72 | ||
78 | ctx.add_action(AssistId(assist_id), label, |edit| { | 73 | ctx.add_action(AssistId(assist_id), label, |edit| { |
79 | let (parent_indent, indent) = { | 74 | let n_existing_items = impl_item_list.impl_items().count(); |
80 | // FIXME: Find a way to get the indent already used in the file. | 75 | let fns = missing_fns.into_iter().map(add_body_and_strip_docstring).collect::<Vec<_>>(); |
81 | // Now, we copy the indent of first item or indent with 4 spaces relative to impl block | 76 | |
82 | const DEFAULT_INDENT: &str = " "; | 77 | let mut ast_editor = AstEditor::new(impl_item_list); |
83 | let first_item = impl_item_list.impl_items().next(); | 78 | if n_existing_items == 0 { |
84 | let first_item_indent = | 79 | ast_editor.make_multiline(); |
85 | first_item.and_then(|i| leading_indent(i.syntax())).map(ToOwned::to_owned); | 80 | } |
86 | let impl_block_indent = leading_indent(impl_node.syntax()).unwrap_or_default(); | 81 | ast_editor.append_functions(fns.iter().map(|it| &**it)); |
87 | 82 | let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap(); | |
88 | ( | 83 | let cursor_poisition = first_new_item.syntax().range().start(); |
89 | impl_block_indent.to_owned(), | 84 | ast_editor.into_text_edit(edit.text_edit_builder()); |
90 | first_item_indent.unwrap_or_else(|| impl_block_indent.to_owned() + DEFAULT_INDENT), | 85 | |
91 | ) | 86 | edit.set_cursor(cursor_poisition); |
92 | }; | ||
93 | |||
94 | let changed_range = { | ||
95 | let children = impl_item_list.syntax().children_with_tokens(); | ||
96 | let last_whitespace = | ||
97 | children.filter_map(|it| ast::Whitespace::cast(it.as_token()?)).last(); | ||
98 | |||
99 | last_whitespace.map(|w| w.syntax().range()).unwrap_or_else(|| { | ||
100 | let in_brackets = impl_item_list.syntax().range().end() - TextUnit::of_str("}"); | ||
101 | TextRange::from_to(in_brackets, in_brackets) | ||
102 | }) | ||
103 | }; | ||
104 | |||
105 | let func_bodies = format!("\n{}", missing_fns.into_iter().map(build_func_body).join("\n")); | ||
106 | let trailing_whitespace = format!("\n{}", parent_indent); | ||
107 | let func_bodies = reindent(&func_bodies, &indent) + &trailing_whitespace; | ||
108 | |||
109 | let replaced_text_range = TextUnit::of_str(&func_bodies); | ||
110 | |||
111 | edit.replace(changed_range, func_bodies); | ||
112 | // FIXME: place the cursor on the first unimplemented? | ||
113 | edit.set_cursor( | ||
114 | changed_range.start() + replaced_text_range - TextUnit::of_str(&trailing_whitespace), | ||
115 | ); | ||
116 | }); | 87 | }); |
117 | 88 | ||
118 | ctx.build() | 89 | ctx.build() |
119 | } | 90 | } |
120 | 91 | ||
92 | fn add_body_and_strip_docstring(fn_def: &ast::FnDef) -> TreeArc<ast::FnDef> { | ||
93 | let mut ast_editor = AstEditor::new(fn_def); | ||
94 | if fn_def.body().is_none() { | ||
95 | ast_editor.set_body(&AstBuilder::<ast::Block>::single_expr( | ||
96 | &AstBuilder::<ast::Expr>::unimplemented(), | ||
97 | )); | ||
98 | } | ||
99 | ast_editor.strip_attrs_and_docs(); | ||
100 | ast_editor.ast().to_owned() | ||
101 | } | ||
102 | |||
121 | /// Given an `ast::ImplBlock`, resolves the target trait (the one being | 103 | /// Given an `ast::ImplBlock`, resolves the target trait (the one being |
122 | /// implemented) to a `ast::TraitDef`. | 104 | /// implemented) to a `ast::TraitDef`. |
123 | fn resolve_target_trait_def( | 105 | fn resolve_target_trait_def( |
@@ -134,22 +116,6 @@ fn resolve_target_trait_def( | |||
134 | } | 116 | } |
135 | } | 117 | } |
136 | 118 | ||
137 | fn build_func_body(def: &ast::FnDef) -> String { | ||
138 | let mut buf = String::new(); | ||
139 | |||
140 | for child in def.syntax().children_with_tokens() { | ||
141 | match (child.prev_sibling_or_token().map(|c| c.kind()), child.kind()) { | ||
142 | (_, SyntaxKind::SEMI) => buf.push_str(" {\n unimplemented!()\n}"), | ||
143 | (_, SyntaxKind::ATTR) | (_, SyntaxKind::COMMENT) => {} | ||
144 | (Some(SyntaxKind::ATTR), SyntaxKind::WHITESPACE) | ||
145 | | (Some(SyntaxKind::COMMENT), SyntaxKind::WHITESPACE) => {} | ||
146 | _ => write!(buf, "{}", child).unwrap(), | ||
147 | }; | ||
148 | } | ||
149 | |||
150 | buf.trim_end().to_string() | ||
151 | } | ||
152 | |||
153 | #[cfg(test)] | 119 | #[cfg(test)] |
154 | mod tests { | 120 | mod tests { |
155 | use super::*; | 121 | use super::*; |
@@ -170,7 +136,7 @@ struct S; | |||
170 | 136 | ||
171 | impl Foo for S { | 137 | impl Foo for S { |
172 | fn bar(&self) {} | 138 | fn bar(&self) {} |
173 | <|> | 139 | <|> |
174 | }", | 140 | }", |
175 | " | 141 | " |
176 | trait Foo { | 142 | trait Foo { |
@@ -183,12 +149,9 @@ struct S; | |||
183 | 149 | ||
184 | impl Foo for S { | 150 | impl Foo for S { |
185 | fn bar(&self) {} | 151 | fn bar(&self) {} |
186 | fn foo(&self) { | 152 | <|>fn foo(&self) { unimplemented!() } |
187 | unimplemented!() | 153 | fn baz(&self) { unimplemented!() } |
188 | } | 154 | |
189 | fn baz(&self) { | ||
190 | unimplemented!() | ||
191 | }<|> | ||
192 | }", | 155 | }", |
193 | ); | 156 | ); |
194 | } | 157 | } |
@@ -208,7 +171,7 @@ struct S; | |||
208 | 171 | ||
209 | impl Foo for S { | 172 | impl Foo for S { |
210 | fn bar(&self) {} | 173 | fn bar(&self) {} |
211 | <|> | 174 | <|> |
212 | }", | 175 | }", |
213 | " | 176 | " |
214 | trait Foo { | 177 | trait Foo { |
@@ -221,9 +184,8 @@ struct S; | |||
221 | 184 | ||
222 | impl Foo for S { | 185 | impl Foo for S { |
223 | fn bar(&self) {} | 186 | fn bar(&self) {} |
224 | fn foo(&self) { | 187 | <|>fn foo(&self) { unimplemented!() } |
225 | unimplemented!() | 188 | |
226 | }<|> | ||
227 | }", | 189 | }", |
228 | ); | 190 | ); |
229 | } | 191 | } |
@@ -240,9 +202,7 @@ impl Foo for S { <|> }", | |||
240 | trait Foo { fn foo(&self); } | 202 | trait Foo { fn foo(&self); } |
241 | struct S; | 203 | struct S; |
242 | impl Foo for S { | 204 | impl Foo for S { |
243 | fn foo(&self) { | 205 | <|>fn foo(&self) { unimplemented!() } |
244 | unimplemented!() | ||
245 | }<|> | ||
246 | }", | 206 | }", |
247 | ); | 207 | ); |
248 | } | 208 | } |
@@ -259,9 +219,7 @@ impl Foo for S {}<|>", | |||
259 | trait Foo { fn foo(&self); } | 219 | trait Foo { fn foo(&self); } |
260 | struct S; | 220 | struct S; |
261 | impl Foo for S { | 221 | impl Foo for S { |
262 | fn foo(&self) { | 222 | <|>fn foo(&self) { unimplemented!() } |
263 | unimplemented!() | ||
264 | }<|> | ||
265 | }", | 223 | }", |
266 | ) | 224 | ) |
267 | } | 225 | } |
@@ -292,35 +250,6 @@ impl Foo for S { <|> }", | |||
292 | } | 250 | } |
293 | 251 | ||
294 | #[test] | 252 | #[test] |
295 | fn test_indented_impl_block() { | ||
296 | check_assist( | ||
297 | add_missing_impl_members, | ||
298 | " | ||
299 | trait Foo { | ||
300 | fn valid(some: u32) -> bool; | ||
301 | } | ||
302 | struct S; | ||
303 | |||
304 | mod my_mod { | ||
305 | impl crate::Foo for S { <|> } | ||
306 | }", | ||
307 | " | ||
308 | trait Foo { | ||
309 | fn valid(some: u32) -> bool; | ||
310 | } | ||
311 | struct S; | ||
312 | |||
313 | mod my_mod { | ||
314 | impl crate::Foo for S { | ||
315 | fn valid(some: u32) -> bool { | ||
316 | unimplemented!() | ||
317 | }<|> | ||
318 | } | ||
319 | }", | ||
320 | ) | ||
321 | } | ||
322 | |||
323 | #[test] | ||
324 | fn test_with_docstring_and_attrs() { | 253 | fn test_with_docstring_and_attrs() { |
325 | check_assist( | 254 | check_assist( |
326 | add_missing_impl_members, | 255 | add_missing_impl_members, |
@@ -342,9 +271,7 @@ trait Foo { | |||
342 | } | 271 | } |
343 | struct S; | 272 | struct S; |
344 | impl Foo for S { | 273 | impl Foo for S { |
345 | fn foo(&self) { | 274 | <|>fn foo(&self) { unimplemented!() } |
346 | unimplemented!() | ||
347 | }<|> | ||
348 | }"#, | 275 | }"#, |
349 | ) | 276 | ) |
350 | } | 277 | } |
@@ -367,7 +294,7 @@ trait Foo { | |||
367 | } | 294 | } |
368 | struct S; | 295 | struct S; |
369 | impl Foo for S { | 296 | impl Foo for S { |
370 | fn valid(some: u32) -> bool { false }<|> | 297 | <|>fn valid(some: u32) -> bool { false } |
371 | }", | 298 | }", |
372 | ) | 299 | ) |
373 | } | 300 | } |
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index 17a9041c5..e744e82d0 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -165,6 +165,10 @@ impl AssistBuilder { | |||
165 | self.target = Some(target) | 165 | self.target = Some(target) |
166 | } | 166 | } |
167 | 167 | ||
168 | pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { | ||
169 | &mut self.edit | ||
170 | } | ||
171 | |||
168 | fn build(self) -> AssistAction { | 172 | fn build(self) -> AssistAction { |
169 | AssistAction { | 173 | AssistAction { |
170 | edit: self.edit.finish(), | 174 | edit: self.edit.finish(), |
diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs new file mode 100644 index 000000000..6854294ae --- /dev/null +++ b/crates/ra_assists/src/ast_editor.rs | |||
@@ -0,0 +1,333 @@ | |||
1 | use std::{iter, ops::RangeInclusive}; | ||
2 | |||
3 | use arrayvec::ArrayVec; | ||
4 | use ra_text_edit::TextEditBuilder; | ||
5 | use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction}; | ||
6 | use ra_fmt::leading_indent; | ||
7 | |||
8 | pub struct AstEditor<N: AstNode> { | ||
9 | original_ast: TreeArc<N>, | ||
10 | ast: TreeArc<N>, | ||
11 | } | ||
12 | |||
13 | impl<N: AstNode> AstEditor<N> { | ||
14 | pub fn new(node: &N) -> AstEditor<N> { | ||
15 | AstEditor { original_ast: node.to_owned(), ast: node.to_owned() } | ||
16 | } | ||
17 | |||
18 | pub fn into_text_edit(self, builder: &mut TextEditBuilder) { | ||
19 | // FIXME: compute a more fine-grained diff here. | ||
20 | // If *you* know a nice algorithm to compute diff between two syntax | ||
21 | // tree, tell me about it! | ||
22 | builder.replace(self.original_ast.syntax().range(), self.ast().syntax().text().to_string()); | ||
23 | } | ||
24 | |||
25 | pub fn ast(&self) -> &N { | ||
26 | &*self.ast | ||
27 | } | ||
28 | |||
29 | #[must_use] | ||
30 | fn insert_children<'a>( | ||
31 | &self, | ||
32 | position: InsertPosition<SyntaxElement<'_>>, | ||
33 | to_insert: impl Iterator<Item = SyntaxElement<'a>>, | ||
34 | ) -> TreeArc<N> { | ||
35 | let new_syntax = self.ast().syntax().insert_children(position, to_insert); | ||
36 | N::cast(&new_syntax).unwrap().to_owned() | ||
37 | } | ||
38 | |||
39 | #[must_use] | ||
40 | fn replace_children<'a>( | ||
41 | &self, | ||
42 | to_delete: RangeInclusive<SyntaxElement<'_>>, | ||
43 | to_insert: impl Iterator<Item = SyntaxElement<'a>>, | ||
44 | ) -> TreeArc<N> { | ||
45 | let new_syntax = self.ast().syntax().replace_children(to_delete, to_insert); | ||
46 | N::cast(&new_syntax).unwrap().to_owned() | ||
47 | } | ||
48 | |||
49 | fn do_make_multiline(&mut self) { | ||
50 | let l_curly = | ||
51 | match self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY) { | ||
52 | Some(it) => it, | ||
53 | None => return, | ||
54 | }; | ||
55 | let sibling = match l_curly.next_sibling_or_token() { | ||
56 | Some(it) => it, | ||
57 | None => return, | ||
58 | }; | ||
59 | let existing_ws = match sibling.as_token() { | ||
60 | None => None, | ||
61 | Some(tok) if tok.kind() != WHITESPACE => None, | ||
62 | Some(ws) => { | ||
63 | if ws.text().contains('\n') { | ||
64 | return; | ||
65 | } | ||
66 | Some(ws) | ||
67 | } | ||
68 | }; | ||
69 | |||
70 | let indent = leading_indent(self.ast().syntax()).unwrap_or(""); | ||
71 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
72 | let to_insert = iter::once(ws.ws().into()); | ||
73 | self.ast = match existing_ws { | ||
74 | None => self.insert_children(InsertPosition::After(l_curly), to_insert), | ||
75 | Some(ws) => self.replace_children(RangeInclusive::new(ws.into(), ws.into()), to_insert), | ||
76 | }; | ||
77 | } | ||
78 | } | ||
79 | |||
80 | impl AstEditor<ast::NamedFieldList> { | ||
81 | pub fn append_field(&mut self, field: &ast::NamedField) { | ||
82 | self.insert_field(InsertPosition::Last, field) | ||
83 | } | ||
84 | |||
85 | pub fn make_multiline(&mut self) { | ||
86 | self.do_make_multiline() | ||
87 | } | ||
88 | |||
89 | pub fn insert_field( | ||
90 | &mut self, | ||
91 | position: InsertPosition<&'_ ast::NamedField>, | ||
92 | field: &ast::NamedField, | ||
93 | ) { | ||
94 | let is_multiline = self.ast().syntax().text().contains('\n'); | ||
95 | let ws; | ||
96 | let space = if is_multiline { | ||
97 | ws = tokens::WsBuilder::new(&format!( | ||
98 | "\n{} ", | ||
99 | leading_indent(self.ast().syntax()).unwrap_or("") | ||
100 | )); | ||
101 | ws.ws() | ||
102 | } else { | ||
103 | tokens::single_space() | ||
104 | }; | ||
105 | |||
106 | let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new(); | ||
107 | to_insert.push(space.into()); | ||
108 | to_insert.push(field.syntax().into()); | ||
109 | to_insert.push(tokens::comma().into()); | ||
110 | |||
111 | macro_rules! after_l_curly { | ||
112 | () => {{ | ||
113 | let anchor = match self.l_curly() { | ||
114 | Some(it) => it, | ||
115 | None => return, | ||
116 | }; | ||
117 | InsertPosition::After(anchor) | ||
118 | }}; | ||
119 | } | ||
120 | |||
121 | macro_rules! after_field { | ||
122 | ($anchor:expr) => { | ||
123 | if let Some(comma) = $anchor | ||
124 | .syntax() | ||
125 | .siblings_with_tokens(Direction::Next) | ||
126 | .find(|it| it.kind() == COMMA) | ||
127 | { | ||
128 | InsertPosition::After(comma) | ||
129 | } else { | ||
130 | to_insert.insert(0, tokens::comma().into()); | ||
131 | InsertPosition::After($anchor.syntax().into()) | ||
132 | } | ||
133 | }; | ||
134 | }; | ||
135 | |||
136 | let position = match position { | ||
137 | InsertPosition::First => after_l_curly!(), | ||
138 | InsertPosition::Last => { | ||
139 | if !is_multiline { | ||
140 | // don't insert comma before curly | ||
141 | to_insert.pop(); | ||
142 | } | ||
143 | match self.ast().fields().last() { | ||
144 | Some(it) => after_field!(it), | ||
145 | None => after_l_curly!(), | ||
146 | } | ||
147 | } | ||
148 | InsertPosition::Before(anchor) => InsertPosition::Before(anchor.syntax().into()), | ||
149 | InsertPosition::After(anchor) => after_field!(anchor), | ||
150 | }; | ||
151 | |||
152 | self.ast = self.insert_children(position, to_insert.iter().cloned()); | ||
153 | } | ||
154 | |||
155 | fn l_curly(&self) -> Option<SyntaxElement> { | ||
156 | self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY) | ||
157 | } | ||
158 | } | ||
159 | |||
160 | impl AstEditor<ast::ItemList> { | ||
161 | pub fn make_multiline(&mut self) { | ||
162 | self.do_make_multiline() | ||
163 | } | ||
164 | |||
165 | pub fn append_functions<'a>(&mut self, fns: impl Iterator<Item = &'a ast::FnDef>) { | ||
166 | fns.for_each(|it| self.append_function(it)) | ||
167 | } | ||
168 | |||
169 | pub fn append_function(&mut self, fn_def: &ast::FnDef) { | ||
170 | let (indent, position) = match self.ast().impl_items().last() { | ||
171 | Some(it) => ( | ||
172 | leading_indent(it.syntax()).unwrap_or("").to_string(), | ||
173 | InsertPosition::After(it.syntax().into()), | ||
174 | ), | ||
175 | None => match self.l_curly() { | ||
176 | Some(it) => ( | ||
177 | " ".to_string() + leading_indent(self.ast().syntax()).unwrap_or(""), | ||
178 | InsertPosition::After(it), | ||
179 | ), | ||
180 | None => return, | ||
181 | }, | ||
182 | }; | ||
183 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
184 | let to_insert: ArrayVec<[SyntaxElement; 2]> = | ||
185 | [ws.ws().into(), fn_def.syntax().into()].into(); | ||
186 | self.ast = self.insert_children(position, to_insert.into_iter()); | ||
187 | } | ||
188 | |||
189 | fn l_curly(&self) -> Option<SyntaxElement> { | ||
190 | self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY) | ||
191 | } | ||
192 | } | ||
193 | |||
194 | impl AstEditor<ast::FnDef> { | ||
195 | pub fn set_body(&mut self, body: &ast::Block) { | ||
196 | let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new(); | ||
197 | let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.ast().body() { | ||
198 | old_body.syntax().into() | ||
199 | } else if let Some(semi) = self.ast().semicolon_token() { | ||
200 | to_insert.push(tokens::single_space().into()); | ||
201 | semi.into() | ||
202 | } else { | ||
203 | to_insert.push(tokens::single_space().into()); | ||
204 | to_insert.push(body.syntax().into()); | ||
205 | self.ast = self.insert_children(InsertPosition::Last, to_insert.into_iter()); | ||
206 | return; | ||
207 | }; | ||
208 | to_insert.push(body.syntax().into()); | ||
209 | let replace_range = RangeInclusive::new(old_body_or_semi, old_body_or_semi); | ||
210 | self.ast = self.replace_children(replace_range, to_insert.into_iter()) | ||
211 | } | ||
212 | |||
213 | pub fn strip_attrs_and_docs(&mut self) { | ||
214 | loop { | ||
215 | if let Some(start) = self | ||
216 | .ast() | ||
217 | .syntax() | ||
218 | .children_with_tokens() | ||
219 | .find(|it| it.kind() == ATTR || it.kind() == COMMENT) | ||
220 | { | ||
221 | let end = match start.next_sibling_or_token() { | ||
222 | Some(el) if el.kind() == WHITESPACE => el, | ||
223 | Some(_) | None => start, | ||
224 | }; | ||
225 | self.ast = self.replace_children(RangeInclusive::new(start, end), iter::empty()); | ||
226 | } else { | ||
227 | break; | ||
228 | } | ||
229 | } | ||
230 | } | ||
231 | } | ||
232 | |||
233 | pub struct AstBuilder<N: AstNode> { | ||
234 | _phantom: std::marker::PhantomData<N>, | ||
235 | } | ||
236 | |||
237 | impl AstBuilder<ast::NamedField> { | ||
238 | fn from_text(text: &str) -> TreeArc<ast::NamedField> { | ||
239 | ast_node_from_file_text(&format!("fn f() {{ S {{ {}, }} }}", text)) | ||
240 | } | ||
241 | |||
242 | pub fn from_pieces(name: &ast::NameRef, expr: Option<&ast::Expr>) -> TreeArc<ast::NamedField> { | ||
243 | match expr { | ||
244 | Some(expr) => Self::from_text(&format!("{}: {}", name.syntax(), expr.syntax())), | ||
245 | None => Self::from_text(&name.syntax().to_string()), | ||
246 | } | ||
247 | } | ||
248 | } | ||
249 | |||
250 | impl AstBuilder<ast::Block> { | ||
251 | fn from_text(text: &str) -> TreeArc<ast::Block> { | ||
252 | ast_node_from_file_text(&format!("fn f() {}", text)) | ||
253 | } | ||
254 | |||
255 | pub fn single_expr(e: &ast::Expr) -> TreeArc<ast::Block> { | ||
256 | Self::from_text(&format!("{{ {} }}", e.syntax())) | ||
257 | } | ||
258 | } | ||
259 | |||
260 | impl AstBuilder<ast::Expr> { | ||
261 | fn from_text(text: &str) -> TreeArc<ast::Expr> { | ||
262 | ast_node_from_file_text(&format!("fn f() {{ {}; }}", text)) | ||
263 | } | ||
264 | |||
265 | pub fn unit() -> TreeArc<ast::Expr> { | ||
266 | Self::from_text("()") | ||
267 | } | ||
268 | |||
269 | pub fn unimplemented() -> TreeArc<ast::Expr> { | ||
270 | Self::from_text("unimplemented!()") | ||
271 | } | ||
272 | } | ||
273 | |||
274 | impl AstBuilder<ast::NameRef> { | ||
275 | pub fn new(text: &str) -> TreeArc<ast::NameRef> { | ||
276 | ast_node_from_file_text(&format!("fn f() {{ {}; }}", text)) | ||
277 | } | ||
278 | } | ||
279 | |||
280 | fn ast_node_from_file_text<N: AstNode>(text: &str) -> TreeArc<N> { | ||
281 | let file = SourceFile::parse(text); | ||
282 | let res = file.syntax().descendants().find_map(N::cast).unwrap().to_owned(); | ||
283 | res | ||
284 | } | ||
285 | |||
286 | mod tokens { | ||
287 | use lazy_static::lazy_static; | ||
288 | use ra_syntax::{AstNode, SourceFile, TreeArc, SyntaxToken, SyntaxKind::*}; | ||
289 | |||
290 | lazy_static! { | ||
291 | static ref SOURCE_FILE: TreeArc<SourceFile> = SourceFile::parse(",\n; ;"); | ||
292 | } | ||
293 | |||
294 | pub(crate) fn comma() -> SyntaxToken<'static> { | ||
295 | SOURCE_FILE | ||
296 | .syntax() | ||
297 | .descendants_with_tokens() | ||
298 | .filter_map(|it| it.as_token()) | ||
299 | .find(|it| it.kind() == COMMA) | ||
300 | .unwrap() | ||
301 | } | ||
302 | |||
303 | pub(crate) fn single_space() -> SyntaxToken<'static> { | ||
304 | SOURCE_FILE | ||
305 | .syntax() | ||
306 | .descendants_with_tokens() | ||
307 | .filter_map(|it| it.as_token()) | ||
308 | .find(|it| it.kind() == WHITESPACE && it.text().as_str() == " ") | ||
309 | .unwrap() | ||
310 | } | ||
311 | |||
312 | #[allow(unused)] | ||
313 | pub(crate) fn single_newline() -> SyntaxToken<'static> { | ||
314 | SOURCE_FILE | ||
315 | .syntax() | ||
316 | .descendants_with_tokens() | ||
317 | .filter_map(|it| it.as_token()) | ||
318 | .find(|it| it.kind() == WHITESPACE && it.text().as_str() == "\n") | ||
319 | .unwrap() | ||
320 | } | ||
321 | |||
322 | pub(crate) struct WsBuilder(TreeArc<SourceFile>); | ||
323 | |||
324 | impl WsBuilder { | ||
325 | pub(crate) fn new(text: &str) -> WsBuilder { | ||
326 | WsBuilder(SourceFile::parse(text)) | ||
327 | } | ||
328 | pub(crate) fn ws(&self) -> SyntaxToken<'_> { | ||
329 | self.0.syntax().first_child_or_token().unwrap().as_token().unwrap() | ||
330 | } | ||
331 | } | ||
332 | |||
333 | } | ||
diff --git a/crates/ra_assists/src/fill_struct_fields.rs b/crates/ra_assists/src/fill_struct_fields.rs index 663b4f669..54b70e17d 100644 --- a/crates/ra_assists/src/fill_struct_fields.rs +++ b/crates/ra_assists/src/fill_struct_fields.rs | |||
@@ -1,94 +1,55 @@ | |||
1 | use std::fmt::Write; | ||
2 | |||
3 | use hir::{AdtDef, db::HirDatabase}; | 1 | use hir::{AdtDef, db::HirDatabase}; |
4 | 2 | ||
5 | use ra_syntax::ast::{self, AstNode}; | 3 | use ra_syntax::ast::{self, AstNode}; |
6 | 4 | ||
7 | use crate::{AssistCtx, Assist, AssistId}; | 5 | use crate::{AssistCtx, Assist, AssistId, ast_editor::{AstEditor, AstBuilder}}; |
8 | 6 | ||
9 | pub(crate) fn fill_struct_fields(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 7 | pub(crate) fn fill_struct_fields(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
10 | let struct_lit = ctx.node_at_offset::<ast::StructLit>()?; | 8 | let struct_lit = ctx.node_at_offset::<ast::StructLit>()?; |
11 | let mut fsf = FillStructFields { | 9 | let named_field_list = struct_lit.named_field_list()?; |
12 | ctx: &mut ctx, | ||
13 | named_field_list: struct_lit.named_field_list()?, | ||
14 | struct_fields: vec![], | ||
15 | struct_lit, | ||
16 | }; | ||
17 | fsf.evaluate_struct_def_fields()?; | ||
18 | if fsf.struct_lit_and_def_have_the_same_number_of_fields() { | ||
19 | return None; | ||
20 | } | ||
21 | fsf.remove_already_included_fields()?; | ||
22 | fsf.add_action()?; | ||
23 | ctx.build() | ||
24 | } | ||
25 | 10 | ||
26 | struct FillStructFields<'a, 'b: 'a, DB> { | 11 | // Collect all fields from struct definition |
27 | ctx: &'a mut AssistCtx<'b, DB>, | 12 | let mut fields = { |
28 | named_field_list: &'a ast::NamedFieldList, | 13 | let analyzer = |
29 | struct_fields: Vec<(String, String)>, | 14 | hir::SourceAnalyzer::new(ctx.db, ctx.frange.file_id, struct_lit.syntax(), None); |
30 | struct_lit: &'a ast::StructLit, | 15 | let struct_lit_ty = analyzer.type_of(ctx.db, struct_lit.into())?; |
31 | } | ||
32 | |||
33 | impl<DB> FillStructFields<'_, '_, DB> | ||
34 | where | ||
35 | DB: HirDatabase, | ||
36 | { | ||
37 | fn add_action(&mut self) -> Option<()> { | ||
38 | let named_field_list = self.named_field_list; | ||
39 | let struct_fields_string = self.struct_fields_string()?; | ||
40 | let struct_lit = self.struct_lit; | ||
41 | self.ctx.add_action(AssistId("fill_struct_fields"), "fill struct fields", |edit| { | ||
42 | edit.target(struct_lit.syntax().range()); | ||
43 | edit.set_cursor(struct_lit.syntax().range().start()); | ||
44 | edit.replace_node_and_indent(named_field_list.syntax(), struct_fields_string); | ||
45 | }); | ||
46 | Some(()) | ||
47 | } | ||
48 | |||
49 | fn struct_lit_and_def_have_the_same_number_of_fields(&self) -> bool { | ||
50 | self.named_field_list.fields().count() == self.struct_fields.len() | ||
51 | } | ||
52 | |||
53 | fn evaluate_struct_def_fields(&mut self) -> Option<()> { | ||
54 | let analyzer = hir::SourceAnalyzer::new( | ||
55 | self.ctx.db, | ||
56 | self.ctx.frange.file_id, | ||
57 | self.struct_lit.syntax(), | ||
58 | None, | ||
59 | ); | ||
60 | let struct_lit_ty = analyzer.type_of(self.ctx.db, self.struct_lit.into())?; | ||
61 | let struct_def = match struct_lit_ty.as_adt() { | 16 | let struct_def = match struct_lit_ty.as_adt() { |
62 | Some((AdtDef::Struct(s), _)) => s, | 17 | Some((AdtDef::Struct(s), _)) => s, |
63 | _ => return None, | 18 | _ => return None, |
64 | }; | 19 | }; |
65 | self.struct_fields = struct_def | 20 | struct_def.fields(ctx.db) |
66 | .fields(self.ctx.db) | 21 | }; |
67 | .into_iter() | ||
68 | .map(|f| (f.name(self.ctx.db).to_string(), "()".into())) | ||
69 | .collect(); | ||
70 | Some(()) | ||
71 | } | ||
72 | 22 | ||
73 | fn remove_already_included_fields(&mut self) -> Option<()> { | 23 | // Filter out existing fields |
74 | for ast_field in self.named_field_list.fields() { | 24 | for ast_field in named_field_list.fields() { |
75 | let expr = ast_field.expr()?.syntax().text().to_string(); | 25 | let name_from_ast = ast_field.name_ref()?.text().to_string(); |
76 | let name_from_ast = ast_field.name_ref()?.text().to_string(); | 26 | fields.retain(|field| field.name(ctx.db).to_string() != name_from_ast); |
77 | if let Some(idx) = self.struct_fields.iter().position(|(n, _)| n == &name_from_ast) { | 27 | } |
78 | self.struct_fields[idx] = (name_from_ast, expr); | 28 | if fields.is_empty() { |
79 | } | 29 | return None; |
80 | } | ||
81 | Some(()) | ||
82 | } | 30 | } |
83 | 31 | ||
84 | fn struct_fields_string(&mut self) -> Option<String> { | 32 | let db = ctx.db; |
85 | let mut buf = String::from("{\n"); | 33 | ctx.add_action(AssistId("fill_struct_fields"), "fill struct fields", |edit| { |
86 | for (name, expr) in &self.struct_fields { | 34 | let mut ast_editor = AstEditor::new(named_field_list); |
87 | write!(&mut buf, " {}: {},\n", name, expr).unwrap(); | 35 | if named_field_list.fields().count() == 0 && fields.len() > 2 { |
36 | ast_editor.make_multiline(); | ||
37 | }; | ||
38 | |||
39 | for field in fields { | ||
40 | let field = AstBuilder::<ast::NamedField>::from_pieces( | ||
41 | &AstBuilder::<ast::NameRef>::new(&field.name(db).to_string()), | ||
42 | Some(&AstBuilder::<ast::Expr>::unit()), | ||
43 | ); | ||
44 | ast_editor.append_field(&field); | ||
88 | } | 45 | } |
89 | buf.push_str("}"); | 46 | |
90 | Some(buf) | 47 | edit.target(struct_lit.syntax().range()); |
91 | } | 48 | edit.set_cursor(struct_lit.syntax().range().start()); |
49 | |||
50 | ast_editor.into_text_edit(edit.text_edit_builder()); | ||
51 | }); | ||
52 | ctx.build() | ||
92 | } | 53 | } |
93 | 54 | ||
94 | #[cfg(test)] | 55 | #[cfg(test)] |
@@ -225,14 +186,41 @@ mod tests { | |||
225 | 186 | ||
226 | fn main() { | 187 | fn main() { |
227 | let s = <|>S { | 188 | let s = <|>S { |
189 | c: (1, 2), | ||
190 | e: "foo", | ||
228 | a: (), | 191 | a: (), |
229 | b: (), | 192 | b: (), |
230 | c: (1, 2), | ||
231 | d: (), | 193 | d: (), |
232 | e: "foo", | ||
233 | } | 194 | } |
234 | } | 195 | } |
235 | "#, | 196 | "#, |
236 | ); | 197 | ); |
237 | } | 198 | } |
199 | |||
200 | #[test] | ||
201 | fn fill_struct_short() { | ||
202 | check_assist( | ||
203 | fill_struct_fields, | ||
204 | r#" | ||
205 | struct S { | ||
206 | foo: u32, | ||
207 | bar: String, | ||
208 | } | ||
209 | |||
210 | fn main() { | ||
211 | let s = S {<|> }; | ||
212 | } | ||
213 | "#, | ||
214 | r#" | ||
215 | struct S { | ||
216 | foo: u32, | ||
217 | bar: String, | ||
218 | } | ||
219 | |||
220 | fn main() { | ||
221 | let s = <|>S { foo: (), bar: () }; | ||
222 | } | ||
223 | "#, | ||
224 | ); | ||
225 | } | ||
238 | } | 226 | } |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 173c004cd..4c330c907 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -7,6 +7,7 @@ | |||
7 | 7 | ||
8 | mod assist_ctx; | 8 | mod assist_ctx; |
9 | mod marks; | 9 | mod marks; |
10 | pub mod ast_editor; | ||
10 | 11 | ||
11 | use itertools::Itertools; | 12 | use itertools::Itertools; |
12 | 13 | ||
@@ -36,7 +37,7 @@ pub struct AssistAction { | |||
36 | pub target: Option<TextRange>, | 37 | pub target: Option<TextRange>, |
37 | } | 38 | } |
38 | 39 | ||
39 | /// Return all the assists applicable at the given position. | 40 | /// Return all the assists eapplicable at the given position. |
40 | /// | 41 | /// |
41 | /// Assists are returned in the "unresolved" state, that is only labels are | 42 | /// Assists are returned in the "unresolved" state, that is only labels are |
42 | /// returned, without actual edits. | 43 | /// returned, without actual edits. |