aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists')
-rw-r--r--crates/ra_assists/src/assist_ctx.rs6
-rw-r--r--crates/ra_assists/src/assists/add_missing_impl_members.rs36
-rw-r--r--crates/ra_assists/src/assists/move_bounds.rs12
-rw-r--r--crates/ra_assists/src/ast_editor.rs245
-rw-r--r--crates/ra_assists/src/lib.rs1
5 files changed, 26 insertions, 274 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index c45262efa..5f564be0b 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,10 @@ 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 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
182 }
183
180 fn build(self) -> AssistAction { 184 fn build(self) -> AssistAction {
181 AssistAction { 185 AssistAction {
182 edit: self.edit.finish(), 186 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 682455bce..6fd1c3753 100644
--- a/crates/ra_assists/src/assists/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs
@@ -1,10 +1,10 @@
1use hir::{db::HirDatabase, HasSource}; 1use hir::{db::HirDatabase, HasSource};
2use ra_syntax::{ 2use ra_syntax::{
3 ast::{self, make, AstNode, NameOwner}, 3 ast::{self, edit, make, AstNode, NameOwner},
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 {
@@ -75,30 +75,26 @@ fn add_missing_impl_members_inner(
75 75
76 ctx.add_action(AssistId(assist_id), label, |edit| { 76 ctx.add_action(AssistId(assist_id), label, |edit| {
77 let n_existing_items = impl_item_list.impl_items().count(); 77 let n_existing_items = impl_item_list.impl_items().count();
78 let items = missing_items.into_iter().map(|it| match it { 78 let items = missing_items
79 ast::ImplItem::FnDef(def) => strip_docstring(add_body(def).into()), 79 .into_iter()
80 _ => strip_docstring(it), 80 .map(|it| match it {
81 }); 81 ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)),
82 let mut ast_editor = AstEditor::new(impl_item_list); 82 _ => it,
83 83 })
84 ast_editor.append_items(items); 84 .map(|it| edit::strip_attrs_and_docs(&it));
85 85 let new_impl_item_list = impl_item_list.append_items(items);
86 let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap(); 86 let cursor_position = {
87 let cursor_position = first_new_item.syntax().text_range().start(); 87 let first_new_item = new_impl_item_list.impl_items().nth(n_existing_items).unwrap();
88 ast_editor.into_text_edit(edit.text_edit_builder()); 88 first_new_item.syntax().text_range().start()
89 89 };
90
91 edit.replace_ast(impl_item_list, new_impl_item_list);
90 edit.set_cursor(cursor_position); 92 edit.set_cursor(cursor_position);
91 }); 93 });
92 94
93 ctx.build() 95 ctx.build()
94} 96}
95 97
96fn strip_docstring(item: ast::ImplItem) -> ast::ImplItem {
97 let mut ast_editor = AstEditor::new(item);
98 ast_editor.strip_attrs_and_docs();
99 ast_editor.ast().to_owned()
100}
101
102fn add_body(fn_def: ast::FnDef) -> ast::FnDef { 98fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
103 if fn_def.body().is_none() { 99 if fn_def.body().is_none() {
104 fn_def.with_body(make::block_from_expr(make::expr_unimplemented())) 100 fn_def.with_body(make::block_from_expr(make::expr_unimplemented()))
diff --git a/crates/ra_assists/src/assists/move_bounds.rs b/crates/ra_assists/src/assists/move_bounds.rs
index fd4bdc55c..39ff51233 100644
--- a/crates/ra_assists/src/assists/move_bounds.rs
+++ b/crates/ra_assists/src/assists/move_bounds.rs
@@ -1,11 +1,11 @@
1use hir::db::HirDatabase; 1use hir::db::HirDatabase;
2use ra_syntax::{ 2use ra_syntax::{
3 ast::{self, make, AstNode, NameOwner, TypeBoundsOwner}, 3 ast::{self, edit, make, AstNode, NameOwner, TypeBoundsOwner},
4 SyntaxElement, 4 SyntaxElement,
5 SyntaxKind::*, 5 SyntaxKind::*,
6}; 6};
7 7
8use crate::{ast_editor::AstEditor, Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
9 9
10pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11 let type_param_list = ctx.node_at_offset::<ast::TypeParamList>()?; 11 let type_param_list = ctx.node_at_offset::<ast::TypeParamList>()?;
@@ -39,14 +39,12 @@ pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>)
39 .type_params() 39 .type_params()
40 .filter(|it| it.type_bound_list().is_some()) 40 .filter(|it| it.type_bound_list().is_some())
41 .map(|type_param| { 41 .map(|type_param| {
42 let without_bounds = 42 let without_bounds = type_param.remove_bounds();
43 AstEditor::new(type_param.clone()).remove_bounds().ast().clone();
44 (type_param, without_bounds) 43 (type_param, without_bounds)
45 }); 44 });
46 45
47 let mut ast_editor = AstEditor::new(type_param_list.clone()); 46 let new_type_param_list = edit::replace_descendants(&type_param_list, new_params);
48 ast_editor.replace_descendants(new_params); 47 edit.replace_ast(type_param_list.clone(), new_type_param_list);
49 ast_editor.into_text_edit(edit.text_edit_builder());
50 48
51 let where_clause = { 49 let where_clause = {
52 let predicates = type_param_list.type_params().filter_map(build_predicate); 50 let predicates = type_param_list.type_params().filter_map(build_predicate);
diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs
deleted file mode 100644
index 72c8c478a..000000000
--- a/crates/ra_assists/src/ast_editor.rs
+++ /dev/null
@@ -1,245 +0,0 @@
1use std::{iter, ops::RangeInclusive};
2
3use arrayvec::ArrayVec;
4use rustc_hash::FxHashMap;
5
6use ra_fmt::leading_indent;
7use ra_syntax::{
8 algo,
9 ast::{self, make::tokens, TypeBoundsOwner},
10 AstNode, Direction, InsertPosition, SyntaxElement,
11 SyntaxKind::*,
12 T,
13};
14use ra_text_edit::TextEditBuilder;
15
16pub struct AstEditor<N: AstNode> {
17 original_ast: N,
18 ast: N,
19}
20
21impl<N: AstNode> AstEditor<N> {
22 pub fn new(node: N) -> AstEditor<N>
23 where
24 N: Clone,
25 {
26 AstEditor { original_ast: node.clone(), ast: node }
27 }
28
29 pub fn into_text_edit(self, builder: &mut TextEditBuilder) {
30 for (from, to) in algo::diff(&self.original_ast.syntax(), self.ast().syntax()) {
31 builder.replace(from.text_range(), to.to_string())
32 }
33 }
34
35 pub fn ast(&self) -> &N {
36 &self.ast
37 }
38
39 pub fn replace_descendants<T: AstNode>(
40 &mut self,
41 replacement_map: impl Iterator<Item = (T, T)>,
42 ) -> &mut Self {
43 let map = replacement_map
44 .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into()))
45 .collect::<FxHashMap<_, _>>();
46 let new_syntax = algo::replace_descendants(self.ast.syntax(), &map);
47 self.ast = N::cast(new_syntax).unwrap();
48 self
49 }
50
51 #[must_use]
52 fn insert_children(
53 &self,
54 position: InsertPosition<SyntaxElement>,
55 mut to_insert: impl Iterator<Item = SyntaxElement>,
56 ) -> N {
57 let new_syntax = algo::insert_children(self.ast().syntax(), position, &mut to_insert);
58 N::cast(new_syntax).unwrap()
59 }
60
61 #[must_use]
62 fn replace_children(
63 &self,
64 to_delete: RangeInclusive<SyntaxElement>,
65 mut to_insert: impl Iterator<Item = SyntaxElement>,
66 ) -> N {
67 let new_syntax = algo::replace_children(self.ast().syntax(), to_delete, &mut to_insert);
68 N::cast(new_syntax).unwrap()
69 }
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}
103
104impl AstEditor<ast::RecordFieldList> {
105 pub fn append_field(&mut self, field: &ast::RecordField) {
106 self.insert_field(InsertPosition::Last, field)
107 }
108
109 pub fn insert_field(
110 &mut self,
111 position: InsertPosition<&'_ ast::RecordField>,
112 field: &ast::RecordField,
113 ) {
114 let is_multiline = self.ast().syntax().text().contains_char('\n');
115 let ws;
116 let space = if is_multiline {
117 ws = tokens::WsBuilder::new(&format!(
118 "\n{} ",
119 leading_indent(self.ast().syntax()).unwrap_or("".into())
120 ));
121 ws.ws()
122 } else {
123 tokens::single_space()
124 };
125
126 let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
127 to_insert.push(space.into());
128 to_insert.push(field.syntax().clone().into());
129 to_insert.push(tokens::comma().into());
130
131 macro_rules! after_l_curly {
132 () => {{
133 let anchor = match self.l_curly() {
134 Some(it) => it,
135 None => return,
136 };
137 InsertPosition::After(anchor)
138 }};
139 }
140
141 macro_rules! after_field {
142 ($anchor:expr) => {
143 if let Some(comma) = $anchor
144 .syntax()
145 .siblings_with_tokens(Direction::Next)
146 .find(|it| it.kind() == T![,])
147 {
148 InsertPosition::After(comma)
149 } else {
150 to_insert.insert(0, tokens::comma().into());
151 InsertPosition::After($anchor.syntax().clone().into())
152 }
153 };
154 };
155
156 let position = match position {
157 InsertPosition::First => after_l_curly!(),
158 InsertPosition::Last => {
159 if !is_multiline {
160 // don't insert comma before curly
161 to_insert.pop();
162 }
163 match self.ast().fields().last() {
164 Some(it) => after_field!(it),
165 None => after_l_curly!(),
166 }
167 }
168 InsertPosition::Before(anchor) => {
169 InsertPosition::Before(anchor.syntax().clone().into())
170 }
171 InsertPosition::After(anchor) => after_field!(anchor),
172 };
173
174 self.ast = self.insert_children(position, to_insert.iter().cloned());
175 }
176
177 fn l_curly(&self) -> Option<SyntaxElement> {
178 self.ast().syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
179 }
180}
181
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::ImplItem> {
216 pub fn strip_attrs_and_docs(&mut self) {
217 while let Some(start) = self
218 .ast()
219 .syntax()
220 .children_with_tokens()
221 .find(|it| it.kind() == ATTR || it.kind() == COMMENT)
222 {
223 let end = match &start.next_sibling_or_token() {
224 Some(el) if el.kind() == WHITESPACE => el.clone(),
225 Some(_) | None => start.clone(),
226 };
227 self.ast = self.replace_children(RangeInclusive::new(start, end), iter::empty());
228 }
229 }
230}
231
232impl AstEditor<ast::TypeParam> {
233 pub fn remove_bounds(&mut self) -> &mut Self {
234 let colon = match self.ast.colon_token() {
235 Some(it) => it,
236 None => return self,
237 };
238 let end = match self.ast.type_bound_list() {
239 Some(it) => it.syntax().clone().into(),
240 None => colon.clone().into(),
241 };
242 self.ast = self.replace_children(RangeInclusive::new(colon.into(), end), iter::empty());
243 self
244 }
245}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 3ca3320f7..91b2a1dce 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -7,7 +7,6 @@
7 7
8mod assist_ctx; 8mod assist_ctx;
9mod marks; 9mod marks;
10pub mod ast_editor;
11 10
12use hir::db::HirDatabase; 11use hir::db::HirDatabase;
13use itertools::Itertools; 12use itertools::Itertools;