aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/assist_ctx.rs8
-rw-r--r--crates/ra_assists/src/assists/add_missing_impl_members.rs15
-rw-r--r--crates/ra_assists/src/ast_editor.rs69
-rw-r--r--crates/ra_syntax/src/ast/edit.rs97
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;
2use ra_db::FileRange; 2use ra_db::FileRange;
3use ra_fmt::{leading_indent, reindent}; 3use ra_fmt::{leading_indent, reindent};
4use ra_syntax::{ 4use 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
7use crate::{ast_editor::AstEditor, Assist, AssistCtx, AssistId}; 7use crate::{Assist, AssistCtx, AssistId};
8 8
9#[derive(PartialEq)] 9#[derive(PartialEq)]
10enum AddMissingImplMembersMode { 10enum 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;
7use ra_syntax::{ 7use 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};
14use ra_text_edit::TextEditBuilder; 12use 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
104impl AstEditor<ast::RecordFieldList> { 70impl AstEditor<ast::RecordFieldList> {
@@ -179,39 +145,6 @@ impl AstEditor<ast::RecordFieldList> {
179 } 145 }
180} 146}
181 147
182impl 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
215impl AstEditor<ast::TypeParam> { 148impl 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
8use crate::{ 8use 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
16impl ast::FnDef { 20impl ast::FnDef {
@@ -33,6 +37,74 @@ impl ast::FnDef {
33 } 37 }
34} 38}
35 39
40impl 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
36pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: N) -> N { 108pub 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...
128fn 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]
54fn insert_children<N: AstNode>( 145fn insert_children<N: AstNode>(
55 parent: &N, 146 parent: &N,