diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-22 11:08:18 +0100 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-22 11:08:18 +0100 |
commit | 38c0a1e3331f4b2b9efc7caa20b5927874024686 (patch) | |
tree | 321dd22a931c2ca11d2e207d734f283844af656d | |
parent | 76e0129a21661029dc6cdbea2412ab53efe33aa1 (diff) | |
parent | b73a978b95810b188090a37e002a22403a9067bd (diff) |
Merge #1184
1184: Start structured editing API r=matklad a=matklad
I think I finally understand how to provide nice, mutable structured editing API on top of red-green trees.
The problem I am trying to solve is that any modification to a particular `SyntaxNode` returns an independent new file. So, if you are editing a struct literal, and add a field, you get back a SourceFile, and you have to find the struct literal inside it yourself! This happens because our trees are immutable, but have parent pointers.
The main idea here is to introduce `AstEditor<T>` type, which abstracts away that API. So, you create an `AstEditor` for node you want to edit and call various `&mut` taking methods on it. Internally, `AstEditor` stores both the original node and the current node. All edits are applied to the current node, which is replaced by the corresponding node in the new file. In the end, `AstEditor` computes a text edit between old and new nodes.
Note that this also should sole a problem when you create an anchor pointing to a subnode and mutate the parent node, invalidating anchor. Because mutation needs `&mut`, all anchors must be killed before modification.
Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r-- | Cargo.lock | 2 | ||||
-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 | ||||
-rw-r--r-- | crates/ra_ide_api/src/completion/complete_postfix.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap | 6 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/extensions.rs | 9 | ||||
-rw-r--r-- | crates/ra_syntax/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ptr.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/syntax_node.rs | 109 |
13 files changed, 569 insertions, 196 deletions
diff --git a/Cargo.lock b/Cargo.lock index e5729f968..3be73d66d 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -903,8 +903,10 @@ version = "0.1.0" | |||
903 | name = "ra_assists" | 903 | name = "ra_assists" |
904 | version = "0.1.0" | 904 | version = "0.1.0" |
905 | dependencies = [ | 905 | dependencies = [ |
906 | "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", | ||
906 | "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", | 907 | "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", |
907 | "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | 908 | "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
909 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||
908 | "ra_db 0.1.0", | 910 | "ra_db 0.1.0", |
909 | "ra_fmt 0.1.0", | 911 | "ra_fmt 0.1.0", |
910 | "ra_hir 0.1.0", | 912 | "ra_hir 0.1.0", |
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 e80e35738..f46de61eb 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -161,6 +161,10 @@ impl AssistBuilder { | |||
161 | self.target = Some(target) | 161 | self.target = Some(target) |
162 | } | 162 | } |
163 | 163 | ||
164 | pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { | ||
165 | &mut self.edit | ||
166 | } | ||
167 | |||
164 | fn build(self) -> AssistAction { | 168 | fn build(self) -> AssistAction { |
165 | AssistAction { | 169 | AssistAction { |
166 | edit: self.edit.finish(), | 170 | 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 ded401b63..60b4d5c63 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. |
diff --git a/crates/ra_ide_api/src/completion/complete_postfix.rs b/crates/ra_ide_api/src/completion/complete_postfix.rs index 4dfa5f176..278b1b797 100644 --- a/crates/ra_ide_api/src/completion/complete_postfix.rs +++ b/crates/ra_ide_api/src/completion/complete_postfix.rs | |||
@@ -40,7 +40,7 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
40 | ctx, | 40 | ctx, |
41 | "match", | 41 | "match", |
42 | "match expr {}", | 42 | "match expr {}", |
43 | &format!("match {} {{\n${{1:_}} => {{$0\\}},\n}}", receiver_text), | 43 | &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), |
44 | ) | 44 | ) |
45 | .add_to(acc); | 45 | .add_to(acc); |
46 | postfix_snippet( | 46 | postfix_snippet( |
diff --git a/crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap b/crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap index 2b5435f0c..3bbc9e3c4 100644 --- a/crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap +++ b/crates/ra_ide_api/src/completion/snapshots/completion_item__postfix_completion_works_for_trivial_path_expression.snap | |||
@@ -1,6 +1,6 @@ | |||
1 | --- | 1 | --- |
2 | created: "2019-02-18T09:22:24.127119709Z" | 2 | created: "2019-04-22T07:37:13.981826301Z" |
3 | creator: insta@0.6.2 | 3 | creator: insta@0.7.4 |
4 | source: crates/ra_ide_api/src/completion/completion_item.rs | 4 | source: crates/ra_ide_api/src/completion/completion_item.rs |
5 | expression: kind_completions | 5 | expression: kind_completions |
6 | --- | 6 | --- |
@@ -23,7 +23,7 @@ expression: kind_completions | |||
23 | label: "match", | 23 | label: "match", |
24 | source_range: [76; 76), | 24 | source_range: [76; 76), |
25 | delete: [72; 76), | 25 | delete: [72; 76), |
26 | insert: "match bar {\n${1:_} => {$0\\},\n}", | 26 | insert: "match bar {\n ${1:_} => {$0\\},\n}", |
27 | detail: "match expr {}" | 27 | detail: "match expr {}" |
28 | }, | 28 | }, |
29 | CompletionItem { | 29 | CompletionItem { |
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs index 5c4c0ffc1..9cbd2c6b8 100644 --- a/crates/ra_syntax/src/ast/extensions.rs +++ b/crates/ra_syntax/src/ast/extensions.rs | |||
@@ -210,6 +210,15 @@ impl ast::EnumVariant { | |||
210 | } | 210 | } |
211 | } | 211 | } |
212 | 212 | ||
213 | impl ast::FnDef { | ||
214 | pub fn semicolon_token(&self) -> Option<SyntaxToken<'_>> { | ||
215 | self.syntax() | ||
216 | .last_child_or_token() | ||
217 | .and_then(|it| it.as_token()) | ||
218 | .filter(|it| it.kind() == SEMI) | ||
219 | } | ||
220 | } | ||
221 | |||
213 | impl ast::LetStmt { | 222 | impl ast::LetStmt { |
214 | pub fn has_semi(&self) -> bool { | 223 | pub fn has_semi(&self) -> bool { |
215 | match self.syntax().last_child_or_token() { | 224 | match self.syntax().last_child_or_token() { |
diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index a6ce14f06..9cb66b76b 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs | |||
@@ -38,7 +38,7 @@ pub use crate::{ | |||
38 | ast::AstNode, | 38 | ast::AstNode, |
39 | syntax_error::{SyntaxError, SyntaxErrorKind, Location}, | 39 | syntax_error::{SyntaxError, SyntaxErrorKind, Location}, |
40 | syntax_text::SyntaxText, | 40 | syntax_text::SyntaxText, |
41 | syntax_node::{Direction, SyntaxNode, WalkEvent, TreeArc, SyntaxTreeBuilder, SyntaxElement, SyntaxToken}, | 41 | syntax_node::{Direction, SyntaxNode, WalkEvent, TreeArc, SyntaxTreeBuilder, SyntaxElement, SyntaxToken, InsertPosition}, |
42 | ptr::{SyntaxNodePtr, AstPtr}, | 42 | ptr::{SyntaxNodePtr, AstPtr}, |
43 | parsing::{tokenize, classify_literal, Token}, | 43 | parsing::{tokenize, classify_literal, Token}, |
44 | }; | 44 | }; |
diff --git a/crates/ra_syntax/src/ptr.rs b/crates/ra_syntax/src/ptr.rs index 15a8b94cd..b0816b135 100644 --- a/crates/ra_syntax/src/ptr.rs +++ b/crates/ra_syntax/src/ptr.rs | |||
@@ -10,7 +10,7 @@ use crate::{ | |||
10 | /// specific node across reparses of the same file. | 10 | /// specific node across reparses of the same file. |
11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
12 | pub struct SyntaxNodePtr { | 12 | pub struct SyntaxNodePtr { |
13 | range: TextRange, | 13 | pub(crate) range: TextRange, |
14 | kind: SyntaxKind, | 14 | kind: SyntaxKind, |
15 | } | 15 | } |
16 | 16 | ||
diff --git a/crates/ra_syntax/src/syntax_node.rs b/crates/ra_syntax/src/syntax_node.rs index dc2352c76..92c15234e 100644 --- a/crates/ra_syntax/src/syntax_node.rs +++ b/crates/ra_syntax/src/syntax_node.rs | |||
@@ -7,6 +7,7 @@ | |||
7 | //! modules just wraps its API. | 7 | //! modules just wraps its API. |
8 | 8 | ||
9 | use std::{ | 9 | use std::{ |
10 | ops::RangeInclusive, | ||
10 | fmt::{self, Write}, | 11 | fmt::{self, Write}, |
11 | any::Any, | 12 | any::Any, |
12 | borrow::Borrow, | 13 | borrow::Borrow, |
@@ -17,13 +18,21 @@ use ra_parser::ParseError; | |||
17 | use rowan::{TransparentNewType, GreenNodeBuilder}; | 18 | use rowan::{TransparentNewType, GreenNodeBuilder}; |
18 | 19 | ||
19 | use crate::{ | 20 | use crate::{ |
20 | SmolStr, SyntaxKind, TextUnit, TextRange, SyntaxText, SourceFile, AstNode, | 21 | SmolStr, SyntaxKind, TextUnit, TextRange, SyntaxText, SourceFile, AstNode, SyntaxNodePtr, |
21 | syntax_error::{SyntaxError, SyntaxErrorKind}, | 22 | syntax_error::{SyntaxError, SyntaxErrorKind}, |
22 | }; | 23 | }; |
23 | 24 | ||
24 | pub use rowan::WalkEvent; | 25 | pub use rowan::WalkEvent; |
25 | pub(crate) use rowan::{GreenNode, GreenToken}; | 26 | pub(crate) use rowan::{GreenNode, GreenToken}; |
26 | 27 | ||
28 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
29 | pub enum InsertPosition<T> { | ||
30 | First, | ||
31 | Last, | ||
32 | Before(T), | ||
33 | After(T), | ||
34 | } | ||
35 | |||
27 | /// Marker trait for CST and AST nodes | 36 | /// Marker trait for CST and AST nodes |
28 | pub trait SyntaxNodeWrapper: TransparentNewType<Repr = rowan::SyntaxNode> {} | 37 | pub trait SyntaxNodeWrapper: TransparentNewType<Repr = rowan::SyntaxNode> {} |
29 | impl<T: TransparentNewType<Repr = rowan::SyntaxNode>> SyntaxNodeWrapper for T {} | 38 | impl<T: TransparentNewType<Repr = rowan::SyntaxNode>> SyntaxNodeWrapper for T {} |
@@ -309,6 +318,97 @@ impl SyntaxNode { | |||
309 | pub(crate) fn replace_with(&self, replacement: GreenNode) -> GreenNode { | 318 | pub(crate) fn replace_with(&self, replacement: GreenNode) -> GreenNode { |
310 | self.0.replace_with(replacement) | 319 | self.0.replace_with(replacement) |
311 | } | 320 | } |
321 | |||
322 | /// Adds specified children (tokens or nodes) to the current node at the | ||
323 | /// specific position. | ||
324 | /// | ||
325 | /// This is a type-unsafe low-level editing API, if you need to use it, | ||
326 | /// prefer to create a type-safe abstraction on top of it instead. | ||
327 | pub fn insert_children<'a>( | ||
328 | &self, | ||
329 | position: InsertPosition<SyntaxElement<'_>>, | ||
330 | to_insert: impl Iterator<Item = SyntaxElement<'a>>, | ||
331 | ) -> TreeArc<SyntaxNode> { | ||
332 | let mut delta = TextUnit::default(); | ||
333 | let to_insert = to_insert.map(|element| { | ||
334 | delta += element.text_len(); | ||
335 | to_green_element(element) | ||
336 | }); | ||
337 | |||
338 | let old_children = self.0.green().children(); | ||
339 | |||
340 | let new_children = match position { | ||
341 | InsertPosition::First => { | ||
342 | to_insert.chain(old_children.iter().cloned()).collect::<Box<[_]>>() | ||
343 | } | ||
344 | InsertPosition::Last => { | ||
345 | old_children.iter().cloned().chain(to_insert).collect::<Box<[_]>>() | ||
346 | } | ||
347 | InsertPosition::Before(anchor) | InsertPosition::After(anchor) => { | ||
348 | let take_anchor = if let InsertPosition::After(_) = position { 1 } else { 0 }; | ||
349 | let split_at = self.position_of_child(anchor) + take_anchor; | ||
350 | let (before, after) = old_children.split_at(split_at); | ||
351 | before | ||
352 | .iter() | ||
353 | .cloned() | ||
354 | .chain(to_insert) | ||
355 | .chain(after.iter().cloned()) | ||
356 | .collect::<Box<[_]>>() | ||
357 | } | ||
358 | }; | ||
359 | |||
360 | self.with_children(new_children) | ||
361 | } | ||
362 | |||
363 | /// Replaces all nodes in `to_delete` with nodes from `to_insert` | ||
364 | /// | ||
365 | /// This is a type-unsafe low-level editing API, if you need to use it, | ||
366 | /// prefer to create a type-safe abstraction on top of it instead. | ||
367 | pub fn replace_children<'a>( | ||
368 | &self, | ||
369 | to_delete: RangeInclusive<SyntaxElement<'_>>, | ||
370 | to_insert: impl Iterator<Item = SyntaxElement<'a>>, | ||
371 | ) -> TreeArc<SyntaxNode> { | ||
372 | let start = self.position_of_child(*to_delete.start()); | ||
373 | let end = self.position_of_child(*to_delete.end()); | ||
374 | let old_children = self.0.green().children(); | ||
375 | |||
376 | let new_children = old_children[..start] | ||
377 | .iter() | ||
378 | .cloned() | ||
379 | .chain(to_insert.map(to_green_element)) | ||
380 | .chain(old_children[end + 1..].iter().cloned()) | ||
381 | .collect::<Box<[_]>>(); | ||
382 | self.with_children(new_children) | ||
383 | } | ||
384 | |||
385 | fn with_children(&self, new_children: Box<[rowan::GreenElement]>) -> TreeArc<SyntaxNode> { | ||
386 | let len = new_children.iter().map(|it| it.text_len()).sum::<TextUnit>(); | ||
387 | let new_node = GreenNode::new(rowan::SyntaxKind(self.kind() as u16), new_children); | ||
388 | let new_file_node = self.replace_with(new_node); | ||
389 | let file = SourceFile::new(new_file_node, Vec::new()); | ||
390 | |||
391 | // FIXME: use a more elegant way to re-fetch the node (#1185), make | ||
392 | // `range` private afterwards | ||
393 | let mut ptr = SyntaxNodePtr::new(self); | ||
394 | ptr.range = TextRange::offset_len(ptr.range().start(), len); | ||
395 | return ptr.to_node(&file).to_owned(); | ||
396 | } | ||
397 | |||
398 | fn position_of_child(&self, child: SyntaxElement) -> usize { | ||
399 | self.children_with_tokens() | ||
400 | .position(|it| it == child) | ||
401 | .expect("elemetn is not a child of current element") | ||
402 | } | ||
403 | } | ||
404 | |||
405 | fn to_green_element(element: SyntaxElement) -> rowan::GreenElement { | ||
406 | match element { | ||
407 | SyntaxElement::Node(node) => node.0.green().clone().into(), | ||
408 | SyntaxElement::Token(tok) => { | ||
409 | GreenToken::new(rowan::SyntaxKind(tok.kind() as u16), tok.text().clone()).into() | ||
410 | } | ||
411 | } | ||
312 | } | 412 | } |
313 | 413 | ||
314 | #[derive(Clone, Copy, PartialEq, Eq, Hash)] | 414 | #[derive(Clone, Copy, PartialEq, Eq, Hash)] |
@@ -451,6 +551,13 @@ impl<'a> SyntaxElement<'a> { | |||
451 | } | 551 | } |
452 | .ancestors() | 552 | .ancestors() |
453 | } | 553 | } |
554 | |||
555 | fn text_len(&self) -> TextUnit { | ||
556 | match self { | ||
557 | SyntaxElement::Node(node) => node.0.green().text_len(), | ||
558 | SyntaxElement::Token(token) => TextUnit::of_str(token.0.text()), | ||
559 | } | ||
560 | } | ||
454 | } | 561 | } |
455 | 562 | ||
456 | impl<'a> From<rowan::SyntaxElement<'a>> for SyntaxElement<'a> { | 563 | impl<'a> From<rowan::SyntaxElement<'a>> for SyntaxElement<'a> { |