aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-04-21 20:13:52 +0100
committerAleksey Kladov <[email protected]>2019-04-21 20:13:52 +0100
commitf313ac45af0b9c81cf411a8a4a6873e742bb8562 (patch)
tree61f3e898b7e86c64b113e690ee55a94882c25911 /crates/ra_assists
parent7cc845e88d870173e1baa39ce4d3885a5b1f7043 (diff)
use structured editing API for fill struct assist
Diffstat (limited to 'crates/ra_assists')
-rw-r--r--crates/ra_assists/src/assist_ctx.rs4
-rw-r--r--crates/ra_assists/src/ast_editor.rs140
-rw-r--r--crates/ra_assists/src/fill_struct_fields.rs113
3 files changed, 138 insertions, 119 deletions
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
index 7347c1738..5bf1fc0ed 100644
--- a/crates/ra_assists/src/ast_editor.rs
+++ b/crates/ra_assists/src/ast_editor.rs
@@ -1,6 +1,9 @@
1use std::iter;
2
1use arrayvec::ArrayVec; 3use arrayvec::ArrayVec;
2use ra_text_edit::{TextEdit, TextEditBuilder}; 4use ra_text_edit::TextEditBuilder;
3use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction}; 5use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction};
6use ra_fmt::leading_indent;
4 7
5pub struct AstEditor<N: AstNode> { 8pub struct AstEditor<N: AstNode> {
6 original_ast: TreeArc<N>, 9 original_ast: TreeArc<N>,
@@ -12,18 +15,25 @@ impl<N: AstNode> AstEditor<N> {
12 AstEditor { original_ast: node.to_owned(), ast: node.to_owned() } 15 AstEditor { original_ast: node.to_owned(), ast: node.to_owned() }
13 } 16 }
14 17
15 pub fn into_text_edit(self) -> TextEdit { 18 pub fn into_text_edit(self, builder: &mut TextEditBuilder) {
16 // FIXME: compute a more fine-grained diff here. 19 // FIXME: compute a more fine-grained diff here.
17 // If *you* know a nice algorithm to compute diff between two syntax 20 // If *you* know a nice algorithm to compute diff between two syntax
18 // tree, tell me about it! 21 // tree, tell me about it!
19 let mut builder = TextEditBuilder::default();
20 builder.replace(self.original_ast.syntax().range(), self.ast().syntax().text().to_string()); 22 builder.replace(self.original_ast.syntax().range(), self.ast().syntax().text().to_string());
21 builder.finish()
22 } 23 }
23 24
24 pub fn ast(&self) -> &N { 25 pub fn ast(&self) -> &N {
25 &*self.ast 26 &*self.ast
26 } 27 }
28
29 fn insert_children<'a>(
30 &self,
31 position: InsertPosition<SyntaxElement<'_>>,
32 to_insert: impl Iterator<Item = SyntaxElement<'a>>,
33 ) -> TreeArc<N> {
34 let new_syntax = self.ast().syntax().insert_children(position, to_insert);
35 N::cast(&new_syntax).unwrap().to_owned()
36 }
27} 37}
28 38
29impl AstEditor<ast::NamedFieldList> { 39impl AstEditor<ast::NamedFieldList> {
@@ -31,41 +41,61 @@ impl AstEditor<ast::NamedFieldList> {
31 self.insert_field(InsertPosition::Last, field) 41 self.insert_field(InsertPosition::Last, field)
32 } 42 }
33 43
44 pub fn make_multiline(&mut self) {
45 let l_curly = match self.l_curly() {
46 Some(it) => it,
47 None => return,
48 };
49 let sibling = match l_curly.next_sibling_or_token() {
50 Some(it) => it,
51 None => return,
52 };
53 if sibling.as_token().map(|it| it.text().contains('\n')) == Some(true) {
54 return;
55 }
56
57 let ws = tokens::WsBuilder::new(&format!(
58 "\n{}",
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 }
63
34 pub fn insert_field( 64 pub fn insert_field(
35 &mut self, 65 &mut self,
36 position: InsertPosition<&'_ ast::NamedField>, 66 position: InsertPosition<&'_ ast::NamedField>,
37 field: &ast::NamedField, 67 field: &ast::NamedField,
38 ) { 68 ) {
39 let mut to_insert: ArrayVec<[SyntaxElement; 2]> = 69 let is_multiline = self.ast().syntax().text().contains('\n');
40 [field.syntax().into(), tokens::comma().into()].into(); 70 let ws;
41 let position = match position { 71 let space = if is_multiline {
42 InsertPosition::First => { 72 ws = tokens::WsBuilder::new(&format!(
43 let anchor = match self 73 "\n{} ",
44 .ast() 74 leading_indent(self.ast().syntax()).unwrap_or("")
45 .syntax() 75 ));
46 .children_with_tokens() 76 ws.ws()
47 .find(|it| it.kind() == L_CURLY) 77 } else {
48 { 78 tokens::single_space()
79 };
80
81 let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
82 to_insert.push(space.into());
83 to_insert.push(field.syntax().into());
84 to_insert.push(tokens::comma().into());
85
86 macro_rules! after_l_curly {
87 () => {{
88 let anchor = match self.l_curly() {
49 Some(it) => it, 89 Some(it) => it,
50 None => return, 90 None => return,
51 }; 91 };
52 InsertPosition::After(anchor) 92 InsertPosition::After(anchor)
53 } 93 }};
54 InsertPosition::Last => { 94 }
55 let anchor = match self 95
56 .ast() 96 macro_rules! after_field {
57 .syntax() 97 ($anchor:expr) => {
58 .children_with_tokens() 98 if let Some(comma) = $anchor
59 .find(|it| it.kind() == R_CURLY)
60 {
61 Some(it) => it,
62 None => return,
63 };
64 InsertPosition::Before(anchor)
65 }
66 InsertPosition::Before(anchor) => InsertPosition::Before(anchor.syntax().into()),
67 InsertPosition::After(anchor) => {
68 if let Some(comma) = anchor
69 .syntax() 99 .syntax()
70 .siblings_with_tokens(Direction::Next) 100 .siblings_with_tokens(Direction::Next)
71 .find(|it| it.kind() == COMMA) 101 .find(|it| it.kind() == COMMA)
@@ -73,21 +103,26 @@ impl AstEditor<ast::NamedFieldList> {
73 InsertPosition::After(comma) 103 InsertPosition::After(comma)
74 } else { 104 } else {
75 to_insert.insert(0, tokens::comma().into()); 105 to_insert.insert(0, tokens::comma().into());
76 InsertPosition::After(anchor.syntax().into()) 106 InsertPosition::After($anchor.syntax().into())
77 } 107 }
78 } 108 };
109 };
110
111 let position = match position {
112 InsertPosition::First => after_l_curly!(),
113 InsertPosition::Last => match self.ast().fields().last() {
114 Some(it) => after_field!(it),
115 None => after_l_curly!(),
116 },
117 InsertPosition::Before(anchor) => InsertPosition::Before(anchor.syntax().into()),
118 InsertPosition::After(anchor) => after_field!(anchor),
79 }; 119 };
80 self.ast = insert_children_into_ast(self.ast(), position, to_insert.iter().cloned()); 120 self.ast = self.insert_children(position, to_insert.iter().cloned());
81 } 121 }
82}
83 122
84fn insert_children_into_ast<'a, N: AstNode>( 123 fn l_curly(&self) -> Option<SyntaxElement> {
85 node: &N, 124 self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY)
86 position: InsertPosition<SyntaxElement<'_>>, 125 }
87 to_insert: impl Iterator<Item = SyntaxElement<'a>>,
88) -> TreeArc<N> {
89 let new_syntax = node.syntax().insert_children(position, to_insert);
90 N::cast(&new_syntax).unwrap().to_owned()
91} 126}
92 127
93pub struct AstBuilder<N: AstNode> { 128pub struct AstBuilder<N: AstNode> {
@@ -111,7 +146,7 @@ mod tokens {
111 use ra_syntax::{AstNode, SourceFile, TreeArc, SyntaxToken, SyntaxKind::*}; 146 use ra_syntax::{AstNode, SourceFile, TreeArc, SyntaxToken, SyntaxKind::*};
112 147
113 lazy_static! { 148 lazy_static! {
114 static ref SOURCE_FILE: TreeArc<SourceFile> = SourceFile::parse(","); 149 static ref SOURCE_FILE: TreeArc<SourceFile> = SourceFile::parse(",\n; ;");
115 } 150 }
116 151
117 pub(crate) fn comma() -> SyntaxToken<'static> { 152 pub(crate) fn comma() -> SyntaxToken<'static> {
@@ -122,6 +157,27 @@ mod tokens {
122 .find(|it| it.kind() == COMMA) 157 .find(|it| it.kind() == COMMA)
123 .unwrap() 158 .unwrap()
124 } 159 }
160
161 pub(crate) fn single_space() -> SyntaxToken<'static> {
162 SOURCE_FILE
163 .syntax()
164 .descendants_with_tokens()
165 .filter_map(|it| it.as_token())
166 .find(|it| it.kind() == WHITESPACE && it.text().as_str() == " ")
167 .unwrap()
168 }
169
170 pub(crate) struct WsBuilder(TreeArc<SourceFile>);
171
172 impl WsBuilder {
173 pub(crate) fn new(text: &str) -> WsBuilder {
174 WsBuilder(SourceFile::parse(text))
175 }
176 pub(crate) fn ws(&self) -> SyntaxToken<'_> {
177 self.0.syntax().first_child_or_token().unwrap().as_token().unwrap()
178 }
179 }
180
125} 181}
126 182
127#[cfg(test)] 183#[cfg(test)]
diff --git a/crates/ra_assists/src/fill_struct_fields.rs b/crates/ra_assists/src/fill_struct_fields.rs
index 663b4f669..ca128168a 100644
--- a/crates/ra_assists/src/fill_struct_fields.rs
+++ b/crates/ra_assists/src/fill_struct_fields.rs
@@ -1,94 +1,53 @@
1use std::fmt::Write;
2
3use hir::{AdtDef, db::HirDatabase}; 1use hir::{AdtDef, db::HirDatabase};
4 2
5use ra_syntax::ast::{self, AstNode}; 3use ra_syntax::ast::{self, AstNode};
6 4
7use crate::{AssistCtx, Assist, AssistId}; 5use crate::{AssistCtx, Assist, AssistId, ast_editor::{AstEditor, AstBuilder}};
8 6
9pub(crate) fn fill_struct_fields(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 7pub(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
26struct FillStructFields<'a, 'b: 'a, DB> {
27 ctx: &'a mut AssistCtx<'b, DB>,
28 named_field_list: &'a ast::NamedFieldList,
29 struct_fields: Vec<(String, String)>,
30 struct_lit: &'a ast::StructLit,
31}
32 10
33impl<DB> FillStructFields<'_, '_, DB> 11 // Collect all fields from struct definition
34where 12 let mut fields = {
35 DB: HirDatabase, 13 let analyzer =
36{ 14 hir::SourceAnalyzer::new(ctx.db, ctx.frange.file_id, struct_lit.syntax(), None);
37 fn add_action(&mut self) -> Option<()> { 15 let struct_lit_ty = analyzer.type_of(ctx.db, struct_lit.into())?;
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 =
41 AstBuilder::<ast::NamedField>::from_text(&format!("{}: ()", field.name(db)));
42 ast_editor.append_field(&field);
88 } 43 }
89 buf.push_str("}"); 44
90 Some(buf) 45 edit.target(struct_lit.syntax().range());
91 } 46 edit.set_cursor(struct_lit.syntax().range().start());
47
48 ast_editor.into_text_edit(edit.text_edit_builder());
49 });
50 ctx.build()
92} 51}
93 52
94#[cfg(test)] 53#[cfg(test)]
@@ -225,11 +184,11 @@ mod tests {
225 184
226 fn main() { 185 fn main() {
227 let s = <|>S { 186 let s = <|>S {
187 c: (1, 2),
188 e: "foo",
228 a: (), 189 a: (),
229 b: (), 190 b: (),
230 c: (1, 2),
231 d: (), 191 d: (),
232 e: "foo",
233 } 192 }
234 } 193 }
235 "#, 194 "#,