aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-09-30 07:27:26 +0100
committerAleksey Kladov <[email protected]>2019-09-30 07:27:26 +0100
commite010b144d5abcbd0947d0490123ef693a6a17c78 (patch)
treefed2cbe36e741a30a94486e47534b35690111a2b
parent0840ec038b2822a424acf238d8db5af569f99a21 (diff)
move field list to ast/edit.rs
-rw-r--r--crates/ra_assists/src/assist_ctx.rs4
-rw-r--r--crates/ra_assists/src/ast_editor.rs101
-rw-r--r--crates/ra_ide_api/src/diagnostics.rs10
-rw-r--r--crates/ra_syntax/src/algo.rs17
-rw-r--r--crates/ra_syntax/src/ast/edit.rs82
5 files changed, 106 insertions, 108 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index cbe12e908..5f564be0b 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -178,9 +178,7 @@ impl AssistBuilder {
178 } 178 }
179 179
180 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { 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()) { 181 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
182 self.edit.replace(from.text_range(), to.to_string())
183 }
184 } 182 }
185 183
186 fn build(self) -> AssistAction { 184 fn build(self) -> AssistAction {
diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs
index 262e2fcf4..54849b7b0 100644
--- a/crates/ra_assists/src/ast_editor.rs
+++ b/crates/ra_assists/src/ast_editor.rs
@@ -1,15 +1,12 @@
1use std::{iter, ops::RangeInclusive}; 1use std::{iter, ops::RangeInclusive};
2 2
3use arrayvec::ArrayVec;
4use rustc_hash::FxHashMap;
5
6use ra_fmt::leading_indent;
7use ra_syntax::{ 3use ra_syntax::{
8 algo, 4 algo,
9 ast::{self, make::tokens, TypeBoundsOwner}, 5 ast::{self, TypeBoundsOwner},
10 AstNode, Direction, InsertPosition, SyntaxElement, T, 6 AstNode, SyntaxElement,
11}; 7};
12use ra_text_edit::TextEditBuilder; 8use ra_text_edit::TextEditBuilder;
9use rustc_hash::FxHashMap;
13 10
14pub struct AstEditor<N: AstNode> { 11pub struct AstEditor<N: AstNode> {
15 original_ast: N, 12 original_ast: N,
@@ -25,9 +22,7 @@ impl<N: AstNode> AstEditor<N> {
25 } 22 }
26 23
27 pub fn into_text_edit(self, builder: &mut TextEditBuilder) { 24 pub fn into_text_edit(self, builder: &mut TextEditBuilder) {
28 for (from, to) in algo::diff(&self.original_ast.syntax(), self.ast().syntax()) { 25 algo::diff(&self.original_ast.syntax(), self.ast().syntax()).into_text_edit(builder)
29 builder.replace(from.text_range(), to.to_string())
30 }
31 } 26 }
32 27
33 pub fn ast(&self) -> &N { 28 pub fn ast(&self) -> &N {
@@ -47,16 +42,6 @@ impl<N: AstNode> AstEditor<N> {
47 } 42 }
48 43
49 #[must_use] 44 #[must_use]
50 fn insert_children(
51 &self,
52 position: InsertPosition<SyntaxElement>,
53 mut to_insert: impl Iterator<Item = SyntaxElement>,
54 ) -> N {
55 let new_syntax = algo::insert_children(self.ast().syntax(), position, &mut to_insert);
56 N::cast(new_syntax).unwrap()
57 }
58
59 #[must_use]
60 fn replace_children( 45 fn replace_children(
61 &self, 46 &self,
62 to_delete: RangeInclusive<SyntaxElement>, 47 to_delete: RangeInclusive<SyntaxElement>,
@@ -67,84 +52,6 @@ impl<N: AstNode> AstEditor<N> {
67 } 52 }
68} 53}
69 54
70impl AstEditor<ast::RecordFieldList> {
71 pub fn append_field(&mut self, field: &ast::RecordField) {
72 self.insert_field(InsertPosition::Last, field)
73 }
74
75 pub fn insert_field(
76 &mut self,
77 position: InsertPosition<&'_ ast::RecordField>,
78 field: &ast::RecordField,
79 ) {
80 let is_multiline = self.ast().syntax().text().contains_char('\n');
81 let ws;
82 let space = if is_multiline {
83 ws = tokens::WsBuilder::new(&format!(
84 "\n{} ",
85 leading_indent(self.ast().syntax()).unwrap_or("".into())
86 ));
87 ws.ws()
88 } else {
89 tokens::single_space()
90 };
91
92 let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
93 to_insert.push(space.into());
94 to_insert.push(field.syntax().clone().into());
95 to_insert.push(tokens::comma().into());
96
97 macro_rules! after_l_curly {
98 () => {{
99 let anchor = match self.l_curly() {
100 Some(it) => it,
101 None => return,
102 };
103 InsertPosition::After(anchor)
104 }};
105 }
106
107 macro_rules! after_field {
108 ($anchor:expr) => {
109 if let Some(comma) = $anchor
110 .syntax()
111 .siblings_with_tokens(Direction::Next)
112 .find(|it| it.kind() == T![,])
113 {
114 InsertPosition::After(comma)
115 } else {
116 to_insert.insert(0, tokens::comma().into());
117 InsertPosition::After($anchor.syntax().clone().into())
118 }
119 };
120 };
121
122 let position = match position {
123 InsertPosition::First => after_l_curly!(),
124 InsertPosition::Last => {
125 if !is_multiline {
126 // don't insert comma before curly
127 to_insert.pop();
128 }
129 match self.ast().fields().last() {
130 Some(it) => after_field!(it),
131 None => after_l_curly!(),
132 }
133 }
134 InsertPosition::Before(anchor) => {
135 InsertPosition::Before(anchor.syntax().clone().into())
136 }
137 InsertPosition::After(anchor) => after_field!(anchor),
138 };
139
140 self.ast = self.insert_children(position, to_insert.iter().cloned());
141 }
142
143 fn l_curly(&self) -> Option<SyntaxElement> {
144 self.ast().syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
145 }
146}
147
148impl AstEditor<ast::TypeParam> { 55impl AstEditor<ast::TypeParam> {
149 pub fn remove_bounds(&mut self) -> &mut Self { 56 pub fn remove_bounds(&mut self) -> &mut Self {
150 let colon = match self.ast.colon_token() { 57 let colon = match self.ast.colon_token() {
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs
index 144bc0a70..4fa07e3dc 100644
--- a/crates/ra_ide_api/src/diagnostics.rs
+++ b/crates/ra_ide_api/src/diagnostics.rs
@@ -2,10 +2,10 @@ use std::cell::RefCell;
2 2
3use hir::diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}; 3use hir::diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_assists::ast_editor::AstEditor;
6use ra_db::SourceDatabase; 5use ra_db::SourceDatabase;
7use ra_prof::profile; 6use ra_prof::profile;
8use ra_syntax::{ 7use ra_syntax::{
8 algo,
9 ast::{self, make, AstNode}, 9 ast::{self, make, AstNode},
10 Location, SyntaxNode, TextRange, T, 10 Location, SyntaxNode, TextRange, T,
11}; 11};
@@ -56,15 +56,15 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
56 }) 56 })
57 }) 57 })
58 .on::<hir::diagnostics::MissingFields, _>(|d| { 58 .on::<hir::diagnostics::MissingFields, _>(|d| {
59 let node = d.ast(db); 59 let mut field_list = d.ast(db);
60 let mut ast_editor = AstEditor::new(node);
61 for f in d.missed_fields.iter() { 60 for f in d.missed_fields.iter() {
62 let field = make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); 61 let field = make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
63 ast_editor.append_field(&field); 62 field_list = field_list.append_field(&field);
64 } 63 }
65 64
66 let mut builder = TextEditBuilder::default(); 65 let mut builder = TextEditBuilder::default();
67 ast_editor.into_text_edit(&mut builder); 66 algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder);
67
68 let fix = 68 let fix =
69 SourceChange::source_file_edit_from("fill struct fields", file_id, builder.finish()); 69 SourceChange::source_file_edit_from("fill struct fields", file_id, builder.finish());
70 res.borrow_mut().push(Diagnostic { 70 res.borrow_mut().push(Diagnostic {
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs
index 46680a08f..f33d2ad4e 100644
--- a/crates/ra_syntax/src/algo.rs
+++ b/crates/ra_syntax/src/algo.rs
@@ -3,6 +3,7 @@ pub mod visit;
3use std::ops::RangeInclusive; 3use std::ops::RangeInclusive;
4 4
5use itertools::Itertools; 5use itertools::Itertools;
6use ra_text_edit::TextEditBuilder;
6use rustc_hash::FxHashMap; 7use rustc_hash::FxHashMap;
7 8
8use crate::{ 9use crate::{
@@ -63,6 +64,18 @@ pub enum InsertPosition<T> {
63 After(T), 64 After(T),
64} 65}
65 66
67pub struct TreeDiff {
68 replacements: FxHashMap<SyntaxElement, SyntaxElement>,
69}
70
71impl TreeDiff {
72 pub fn into_text_edit(&self, builder: &mut TextEditBuilder) {
73 for (from, to) in self.replacements.iter() {
74 builder.replace(from.text_range(), to.to_string())
75 }
76 }
77}
78
66/// Finds minimal the diff, which, applied to `from`, will result in `to`. 79/// Finds minimal the diff, which, applied to `from`, will result in `to`.
67/// 80///
68/// Specifically, returns a map whose keys are descendants of `from` and values 81/// Specifically, returns a map whose keys are descendants of `from` and values
@@ -70,12 +83,12 @@ pub enum InsertPosition<T> {
70/// 83///
71/// A trivial solution is a singletom map `{ from: to }`, but this function 84/// A trivial solution is a singletom map `{ from: to }`, but this function
72/// tries to find a more fine-grained diff. 85/// tries to find a more fine-grained diff.
73pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> FxHashMap<SyntaxElement, SyntaxElement> { 86pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
74 let mut buf = FxHashMap::default(); 87 let mut buf = FxHashMap::default();
75 // FIXME: this is both horrible inefficient and gives larger than 88 // FIXME: this is both horrible inefficient and gives larger than
76 // necessary diff. I bet there's a cool algorithm to diff trees properly. 89 // necessary diff. I bet there's a cool algorithm to diff trees properly.
77 go(&mut buf, from.clone().into(), to.clone().into()); 90 go(&mut buf, from.clone().into(), to.clone().into());
78 return buf; 91 return TreeDiff { replacements: buf };
79 92
80 fn go( 93 fn go(
81 buf: &mut FxHashMap<SyntaxElement, SyntaxElement>, 94 buf: &mut FxHashMap<SyntaxElement, SyntaxElement>,
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
index 2af6f573e..6e64c0675 100644
--- a/crates/ra_syntax/src/ast/edit.rs
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -12,7 +12,7 @@ use crate::{
12 make::{self, tokens}, 12 make::{self, tokens},
13 AstNode, 13 AstNode,
14 }, 14 },
15 AstToken, InsertPosition, SmolStr, SyntaxElement, 15 AstToken, Direction, InsertPosition, SmolStr, SyntaxElement,
16 SyntaxKind::{ATTR, COMMENT, WHITESPACE}, 16 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
17 SyntaxNode, T, 17 SyntaxNode, T,
18}; 18};
@@ -105,6 +105,86 @@ impl ast::ItemList {
105 } 105 }
106} 106}
107 107
108impl ast::RecordFieldList {
109 #[must_use]
110 pub fn append_field(&self, field: &ast::RecordField) -> ast::RecordFieldList {
111 self.insert_field(InsertPosition::Last, field)
112 }
113
114 #[must_use]
115 pub fn insert_field(
116 &self,
117 position: InsertPosition<&'_ ast::RecordField>,
118 field: &ast::RecordField,
119 ) -> ast::RecordFieldList {
120 let is_multiline = self.syntax().text().contains_char('\n');
121 let ws;
122 let space = if is_multiline {
123 ws = tokens::WsBuilder::new(&format!(
124 "\n{} ",
125 leading_indent(self.syntax()).unwrap_or("".into())
126 ));
127 ws.ws()
128 } else {
129 tokens::single_space()
130 };
131
132 let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
133 to_insert.push(space.into());
134 to_insert.push(field.syntax().clone().into());
135 to_insert.push(tokens::comma().into());
136
137 macro_rules! after_l_curly {
138 () => {{
139 let anchor = match self.l_curly() {
140 Some(it) => it,
141 None => return self.clone(),
142 };
143 InsertPosition::After(anchor)
144 }};
145 }
146
147 macro_rules! after_field {
148 ($anchor:expr) => {
149 if let Some(comma) = $anchor
150 .syntax()
151 .siblings_with_tokens(Direction::Next)
152 .find(|it| it.kind() == T![,])
153 {
154 InsertPosition::After(comma)
155 } else {
156 to_insert.insert(0, tokens::comma().into());
157 InsertPosition::After($anchor.syntax().clone().into())
158 }
159 };
160 };
161
162 let position = match position {
163 InsertPosition::First => after_l_curly!(),
164 InsertPosition::Last => {
165 if !is_multiline {
166 // don't insert comma before curly
167 to_insert.pop();
168 }
169 match self.fields().last() {
170 Some(it) => after_field!(it),
171 None => after_l_curly!(),
172 }
173 }
174 InsertPosition::Before(anchor) => {
175 InsertPosition::Before(anchor.syntax().clone().into())
176 }
177 InsertPosition::After(anchor) => after_field!(anchor),
178 };
179
180 insert_children(self, position, to_insert.iter().cloned())
181 }
182
183 fn l_curly(&self) -> Option<SyntaxElement> {
184 self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
185 }
186}
187
108pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: N) -> N { 188pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: N) -> N {
109 N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() 189 N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap()
110} 190}