diff options
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 8 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/add_missing_impl_members.rs | 15 | ||||
-rw-r--r-- | crates/ra_assists/src/ast_editor.rs | 69 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/edit.rs | 97 |
4 files changed, 109 insertions, 80 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index c45262efa..cbe12e908 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -2,7 +2,7 @@ use hir::db::HirDatabase; | |||
2 | use ra_db::FileRange; | 2 | use ra_db::FileRange; |
3 | use ra_fmt::{leading_indent, reindent}; | 3 | use ra_fmt::{leading_indent, reindent}; |
4 | use ra_syntax::{ | 4 | use ra_syntax::{ |
5 | algo::{find_covering_element, find_node_at_offset}, | 5 | algo::{self, find_covering_element, find_node_at_offset}, |
6 | AstNode, SourceFile, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextUnit, | 6 | AstNode, SourceFile, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextUnit, |
7 | TokenAtOffset, | 7 | TokenAtOffset, |
8 | }; | 8 | }; |
@@ -177,6 +177,12 @@ impl AssistBuilder { | |||
177 | &mut self.edit | 177 | &mut self.edit |
178 | } | 178 | } |
179 | 179 | ||
180 | pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { | ||
181 | for (from, to) in algo::diff(old.syntax(), new.syntax()) { | ||
182 | self.edit.replace(from.text_range(), to.to_string()) | ||
183 | } | ||
184 | } | ||
185 | |||
180 | fn build(self) -> AssistAction { | 186 | fn build(self) -> AssistAction { |
181 | AssistAction { | 187 | AssistAction { |
182 | edit: self.edit.finish(), | 188 | edit: self.edit.finish(), |
diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs index 3fce4a5b7..c2e3eb06b 100644 --- a/crates/ra_assists/src/assists/add_missing_impl_members.rs +++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | SmolStr, | 4 | SmolStr, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::{ast_editor::AstEditor, Assist, AssistCtx, AssistId}; | 7 | use crate::{Assist, AssistCtx, AssistId}; |
8 | 8 | ||
9 | #[derive(PartialEq)] | 9 | #[derive(PartialEq)] |
10 | enum AddMissingImplMembersMode { | 10 | enum AddMissingImplMembersMode { |
@@ -79,14 +79,13 @@ fn add_missing_impl_members_inner( | |||
79 | ast::ImplItem::FnDef(def) => edit::strip_attrs_and_docs(add_body(def).into()), | 79 | ast::ImplItem::FnDef(def) => edit::strip_attrs_and_docs(add_body(def).into()), |
80 | _ => edit::strip_attrs_and_docs(it), | 80 | _ => edit::strip_attrs_and_docs(it), |
81 | }); | 81 | }); |
82 | let mut ast_editor = AstEditor::new(impl_item_list); | 82 | let new_impl_item_list = impl_item_list.append_items(items); |
83 | 83 | let cursor_position = { | |
84 | ast_editor.append_items(items); | 84 | let first_new_item = new_impl_item_list.impl_items().nth(n_existing_items).unwrap(); |
85 | 85 | first_new_item.syntax().text_range().start() | |
86 | let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap(); | 86 | }; |
87 | let cursor_position = first_new_item.syntax().text_range().start(); | ||
88 | ast_editor.into_text_edit(edit.text_edit_builder()); | ||
89 | 87 | ||
88 | edit.replace_ast(impl_item_list, new_impl_item_list); | ||
90 | edit.set_cursor(cursor_position); | 89 | edit.set_cursor(cursor_position); |
91 | }); | 90 | }); |
92 | 91 | ||
diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs index 60b8923e1..262e2fcf4 100644 --- a/crates/ra_assists/src/ast_editor.rs +++ b/crates/ra_assists/src/ast_editor.rs | |||
@@ -7,9 +7,7 @@ use ra_fmt::leading_indent; | |||
7 | use ra_syntax::{ | 7 | use ra_syntax::{ |
8 | algo, | 8 | algo, |
9 | ast::{self, make::tokens, TypeBoundsOwner}, | 9 | ast::{self, make::tokens, TypeBoundsOwner}, |
10 | AstNode, Direction, InsertPosition, SyntaxElement, | 10 | AstNode, Direction, InsertPosition, SyntaxElement, T, |
11 | SyntaxKind::*, | ||
12 | T, | ||
13 | }; | 11 | }; |
14 | use ra_text_edit::TextEditBuilder; | 12 | use ra_text_edit::TextEditBuilder; |
15 | 13 | ||
@@ -67,38 +65,6 @@ impl<N: AstNode> AstEditor<N> { | |||
67 | let new_syntax = algo::replace_children(self.ast().syntax(), to_delete, &mut to_insert); | 65 | let new_syntax = algo::replace_children(self.ast().syntax(), to_delete, &mut to_insert); |
68 | N::cast(new_syntax).unwrap() | 66 | N::cast(new_syntax).unwrap() |
69 | } | 67 | } |
70 | |||
71 | fn do_make_multiline(&mut self) { | ||
72 | let l_curly = | ||
73 | match self.ast().syntax().children_with_tokens().find(|it| it.kind() == T!['{']) { | ||
74 | Some(it) => it, | ||
75 | None => return, | ||
76 | }; | ||
77 | let sibling = match l_curly.next_sibling_or_token() { | ||
78 | Some(it) => it, | ||
79 | None => return, | ||
80 | }; | ||
81 | let existing_ws = match sibling.as_token() { | ||
82 | None => None, | ||
83 | Some(tok) if tok.kind() != WHITESPACE => None, | ||
84 | Some(ws) => { | ||
85 | if ws.text().contains('\n') { | ||
86 | return; | ||
87 | } | ||
88 | Some(ws.clone()) | ||
89 | } | ||
90 | }; | ||
91 | |||
92 | let indent = leading_indent(self.ast().syntax()).unwrap_or("".into()); | ||
93 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
94 | let to_insert = iter::once(ws.ws().into()); | ||
95 | self.ast = match existing_ws { | ||
96 | None => self.insert_children(InsertPosition::After(l_curly), to_insert), | ||
97 | Some(ws) => { | ||
98 | self.replace_children(RangeInclusive::new(ws.clone().into(), ws.into()), to_insert) | ||
99 | } | ||
100 | }; | ||
101 | } | ||
102 | } | 68 | } |
103 | 69 | ||
104 | impl AstEditor<ast::RecordFieldList> { | 70 | impl AstEditor<ast::RecordFieldList> { |
@@ -179,39 +145,6 @@ impl AstEditor<ast::RecordFieldList> { | |||
179 | } | 145 | } |
180 | } | 146 | } |
181 | 147 | ||
182 | impl AstEditor<ast::ItemList> { | ||
183 | pub fn append_items(&mut self, items: impl Iterator<Item = ast::ImplItem>) { | ||
184 | if !self.ast().syntax().text().contains_char('\n') { | ||
185 | self.do_make_multiline(); | ||
186 | } | ||
187 | items.for_each(|it| self.append_item(it)); | ||
188 | } | ||
189 | |||
190 | pub fn append_item(&mut self, item: ast::ImplItem) { | ||
191 | let (indent, position) = match self.ast().impl_items().last() { | ||
192 | Some(it) => ( | ||
193 | leading_indent(it.syntax()).unwrap_or_default().to_string(), | ||
194 | InsertPosition::After(it.syntax().clone().into()), | ||
195 | ), | ||
196 | None => match self.l_curly() { | ||
197 | Some(it) => ( | ||
198 | " ".to_string() + &leading_indent(self.ast().syntax()).unwrap_or_default(), | ||
199 | InsertPosition::After(it), | ||
200 | ), | ||
201 | None => return, | ||
202 | }, | ||
203 | }; | ||
204 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
205 | let to_insert: ArrayVec<[SyntaxElement; 2]> = | ||
206 | [ws.ws().into(), item.syntax().clone().into()].into(); | ||
207 | self.ast = self.insert_children(position, to_insert.into_iter()); | ||
208 | } | ||
209 | |||
210 | fn l_curly(&self) -> Option<SyntaxElement> { | ||
211 | self.ast().syntax().children_with_tokens().find(|it| it.kind() == T!['{']) | ||
212 | } | ||
213 | } | ||
214 | |||
215 | impl AstEditor<ast::TypeParam> { | 148 | impl AstEditor<ast::TypeParam> { |
216 | pub fn remove_bounds(&mut self) -> &mut Self { | 149 | pub fn remove_bounds(&mut self) -> &mut Self { |
217 | let colon = match self.ast.colon_token() { | 150 | let colon = match self.ast.colon_token() { |
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 7013cc9b5..2af6f573e 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -7,10 +7,14 @@ use arrayvec::ArrayVec; | |||
7 | 7 | ||
8 | use crate::{ | 8 | use crate::{ |
9 | algo, | 9 | algo, |
10 | ast::{self, make, AstNode}, | 10 | ast::{ |
11 | InsertPosition, SyntaxElement, | 11 | self, |
12 | make::{self, tokens}, | ||
13 | AstNode, | ||
14 | }, | ||
15 | AstToken, InsertPosition, SmolStr, SyntaxElement, | ||
12 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, | 16 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, |
13 | SyntaxNode, | 17 | SyntaxNode, T, |
14 | }; | 18 | }; |
15 | 19 | ||
16 | impl ast::FnDef { | 20 | impl ast::FnDef { |
@@ -33,6 +37,74 @@ impl ast::FnDef { | |||
33 | } | 37 | } |
34 | } | 38 | } |
35 | 39 | ||
40 | impl ast::ItemList { | ||
41 | #[must_use] | ||
42 | pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList { | ||
43 | let mut res = self.clone(); | ||
44 | if !self.syntax().text().contains_char('\n') { | ||
45 | res = res.make_multiline(); | ||
46 | } | ||
47 | items.for_each(|it| res = res.append_item(it)); | ||
48 | res | ||
49 | } | ||
50 | |||
51 | #[must_use] | ||
52 | pub fn append_item(&self, item: ast::ImplItem) -> ast::ItemList { | ||
53 | let (indent, position) = match self.impl_items().last() { | ||
54 | Some(it) => ( | ||
55 | leading_indent(it.syntax()).unwrap_or_default().to_string(), | ||
56 | InsertPosition::After(it.syntax().clone().into()), | ||
57 | ), | ||
58 | None => match self.l_curly() { | ||
59 | Some(it) => ( | ||
60 | " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(), | ||
61 | InsertPosition::After(it), | ||
62 | ), | ||
63 | None => return self.clone(), | ||
64 | }, | ||
65 | }; | ||
66 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
67 | let to_insert: ArrayVec<[SyntaxElement; 2]> = | ||
68 | [ws.ws().into(), item.syntax().clone().into()].into(); | ||
69 | insert_children(self, position, to_insert.into_iter()) | ||
70 | } | ||
71 | |||
72 | fn l_curly(&self) -> Option<SyntaxElement> { | ||
73 | self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) | ||
74 | } | ||
75 | |||
76 | fn make_multiline(&self) -> ast::ItemList { | ||
77 | let l_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) { | ||
78 | Some(it) => it, | ||
79 | None => return self.clone(), | ||
80 | }; | ||
81 | let sibling = match l_curly.next_sibling_or_token() { | ||
82 | Some(it) => it, | ||
83 | None => return self.clone(), | ||
84 | }; | ||
85 | let existing_ws = match sibling.as_token() { | ||
86 | None => None, | ||
87 | Some(tok) if tok.kind() != WHITESPACE => None, | ||
88 | Some(ws) => { | ||
89 | if ws.text().contains('\n') { | ||
90 | return self.clone(); | ||
91 | } | ||
92 | Some(ws.clone()) | ||
93 | } | ||
94 | }; | ||
95 | |||
96 | let indent = leading_indent(self.syntax()).unwrap_or("".into()); | ||
97 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
98 | let to_insert = iter::once(ws.ws().into()); | ||
99 | match existing_ws { | ||
100 | None => insert_children(self, InsertPosition::After(l_curly), to_insert), | ||
101 | Some(ws) => { | ||
102 | replace_children(self, RangeInclusive::new(ws.clone().into(), ws.into()), to_insert) | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | |||
36 | pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: N) -> N { | 108 | pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: N) -> N { |
37 | N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() | 109 | N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() |
38 | } | 110 | } |
@@ -50,6 +122,25 @@ fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode { | |||
50 | node | 122 | node |
51 | } | 123 | } |
52 | 124 | ||
125 | // Note this is copy-pasted from fmt. It seems like fmt should be a separate | ||
126 | // crate, but basic tree building should be this crate. However, tree building | ||
127 | // might want to call into fmt... | ||
128 | fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> { | ||
129 | let prev_tokens = std::iter::successors(node.first_token(), |token| token.prev_token()); | ||
130 | for token in prev_tokens { | ||
131 | if let Some(ws) = ast::Whitespace::cast(token.clone()) { | ||
132 | let ws_text = ws.text(); | ||
133 | if let Some(pos) = ws_text.rfind('\n') { | ||
134 | return Some(ws_text[pos + 1..].into()); | ||
135 | } | ||
136 | } | ||
137 | if token.text().contains('\n') { | ||
138 | break; | ||
139 | } | ||
140 | } | ||
141 | None | ||
142 | } | ||
143 | |||
53 | #[must_use] | 144 | #[must_use] |
54 | fn insert_children<N: AstNode>( | 145 | fn insert_children<N: AstNode>( |
55 | parent: &N, | 146 | parent: &N, |