diff options
author | Aleksey Kladov <[email protected]> | 2019-04-22 11:01:33 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-04-22 11:01:33 +0100 |
commit | 268e739c94d2e9edbb45374dfcc252b1648d3181 (patch) | |
tree | 25820553d53e8a638003e315076c6c074464fd9e /crates/ra_assists/src | |
parent | b811922a530b7b22469b386a521946f1003911c7 (diff) |
move add_missing_members to structured editing API
Currently, this is more code, and we also loose auto-indenting of
bodies, but, long-term, this is the right approach
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r-- | crates/ra_assists/src/add_missing_impl_members.rs | 149 | ||||
-rw-r--r-- | crates/ra_assists/src/ast_editor.rs | 158 |
2 files changed, 178 insertions, 129 deletions
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/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index 13ee82879..283b280b6 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use std::iter; | 1 | use std::{iter, ops::RangeInclusive}; |
2 | 2 | ||
3 | use arrayvec::ArrayVec; | 3 | use arrayvec::ArrayVec; |
4 | use ra_text_edit::TextEditBuilder; | 4 | use ra_text_edit::TextEditBuilder; |
@@ -26,6 +26,7 @@ impl<N: AstNode> AstEditor<N> { | |||
26 | &*self.ast | 26 | &*self.ast |
27 | } | 27 | } |
28 | 28 | ||
29 | #[must_use] | ||
29 | fn insert_children<'a>( | 30 | fn insert_children<'a>( |
30 | &self, | 31 | &self, |
31 | position: InsertPosition<SyntaxElement<'_>>, | 32 | position: InsertPosition<SyntaxElement<'_>>, |
@@ -34,31 +35,55 @@ impl<N: AstNode> AstEditor<N> { | |||
34 | let new_syntax = self.ast().syntax().insert_children(position, to_insert); | 35 | let new_syntax = self.ast().syntax().insert_children(position, to_insert); |
35 | N::cast(&new_syntax).unwrap().to_owned() | 36 | N::cast(&new_syntax).unwrap().to_owned() |
36 | } | 37 | } |
37 | } | ||
38 | 38 | ||
39 | impl AstEditor<ast::NamedFieldList> { | 39 | #[must_use] |
40 | pub fn append_field(&mut self, field: &ast::NamedField) { | 40 | fn replace_children<'a>( |
41 | self.insert_field(InsertPosition::Last, field) | 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() | ||
42 | } | 47 | } |
43 | 48 | ||
44 | pub fn make_multiline(&mut self) { | 49 | fn do_make_multiline(&mut self) { |
45 | let l_curly = match self.l_curly() { | 50 | let l_curly = |
46 | Some(it) => it, | 51 | match self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY) { |
47 | None => return, | 52 | Some(it) => it, |
48 | }; | 53 | None => return, |
54 | }; | ||
49 | let sibling = match l_curly.next_sibling_or_token() { | 55 | let sibling = match l_curly.next_sibling_or_token() { |
50 | Some(it) => it, | 56 | Some(it) => it, |
51 | None => return, | 57 | None => return, |
52 | }; | 58 | }; |
53 | if sibling.as_token().map(|it| it.text().contains('\n')) == Some(true) { | 59 | let existing_ws = match sibling.as_token() { |
54 | return; | 60 | None => None, |
55 | } | 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 | } | ||
56 | 84 | ||
57 | let ws = tokens::WsBuilder::new(&format!( | 85 | pub fn make_multiline(&mut self) { |
58 | "\n{}", | 86 | self.do_make_multiline() |
59 | leading_indent(self.ast().syntax()).unwrap_or("") | ||
60 | )); | ||
61 | self.ast = self.insert_children(InsertPosition::After(l_curly), iter::once(ws.ws().into())); | ||
62 | } | 87 | } |
63 | 88 | ||
64 | pub fn insert_field( | 89 | pub fn insert_field( |
@@ -132,6 +157,79 @@ impl AstEditor<ast::NamedFieldList> { | |||
132 | } | 157 | } |
133 | } | 158 | } |
134 | 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 | |||
135 | pub struct AstBuilder<N: AstNode> { | 233 | pub struct AstBuilder<N: AstNode> { |
136 | _phantom: std::marker::PhantomData<N>, | 234 | _phantom: std::marker::PhantomData<N>, |
137 | } | 235 | } |
@@ -149,6 +247,16 @@ impl AstBuilder<ast::NamedField> { | |||
149 | } | 247 | } |
150 | } | 248 | } |
151 | 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 | |||
152 | impl AstBuilder<ast::Expr> { | 260 | impl AstBuilder<ast::Expr> { |
153 | fn from_text(text: &str) -> TreeArc<ast::Expr> { | 261 | fn from_text(text: &str) -> TreeArc<ast::Expr> { |
154 | ast_node_from_file_text(&format!("fn f() {{ {}; }}", text)) | 262 | ast_node_from_file_text(&format!("fn f() {{ {}; }}", text)) |
@@ -157,6 +265,10 @@ impl AstBuilder<ast::Expr> { | |||
157 | pub fn unit() -> TreeArc<ast::Expr> { | 265 | pub fn unit() -> TreeArc<ast::Expr> { |
158 | Self::from_text("()") | 266 | Self::from_text("()") |
159 | } | 267 | } |
268 | |||
269 | pub fn unimplemented() -> TreeArc<ast::Expr> { | ||
270 | Self::from_text("unimplemented!()") | ||
271 | } | ||
160 | } | 272 | } |
161 | 273 | ||
162 | impl AstBuilder<ast::NameRef> { | 274 | impl AstBuilder<ast::NameRef> { |
@@ -197,6 +309,16 @@ mod tokens { | |||
197 | .unwrap() | 309 | .unwrap() |
198 | } | 310 | } |
199 | 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 | |||
200 | pub(crate) struct WsBuilder(TreeArc<SourceFile>); | 322 | pub(crate) struct WsBuilder(TreeArc<SourceFile>); |
201 | 323 | ||
202 | impl WsBuilder { | 324 | impl WsBuilder { |