diff options
author | Aleksey Kladov <[email protected]> | 2019-09-30 07:27:26 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-09-30 07:27:26 +0100 |
commit | e010b144d5abcbd0947d0490123ef693a6a17c78 (patch) | |
tree | fed2cbe36e741a30a94486e47534b35690111a2b | |
parent | 0840ec038b2822a424acf238d8db5af569f99a21 (diff) |
move field list to ast/edit.rs
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 4 | ||||
-rw-r--r-- | crates/ra_assists/src/ast_editor.rs | 101 | ||||
-rw-r--r-- | crates/ra_ide_api/src/diagnostics.rs | 10 | ||||
-rw-r--r-- | crates/ra_syntax/src/algo.rs | 17 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/edit.rs | 82 |
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 @@ | |||
1 | use std::{iter, ops::RangeInclusive}; | 1 | use std::{iter, ops::RangeInclusive}; |
2 | 2 | ||
3 | use arrayvec::ArrayVec; | ||
4 | use rustc_hash::FxHashMap; | ||
5 | |||
6 | use ra_fmt::leading_indent; | ||
7 | use ra_syntax::{ | 3 | use 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 | }; |
12 | use ra_text_edit::TextEditBuilder; | 8 | use ra_text_edit::TextEditBuilder; |
9 | use rustc_hash::FxHashMap; | ||
13 | 10 | ||
14 | pub struct AstEditor<N: AstNode> { | 11 | pub 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 | ||
70 | impl 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 | |||
148 | impl AstEditor<ast::TypeParam> { | 55 | impl 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 | ||
3 | use hir::diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}; | 3 | use hir::diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}; |
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use ra_assists::ast_editor::AstEditor; | ||
6 | use ra_db::SourceDatabase; | 5 | use ra_db::SourceDatabase; |
7 | use ra_prof::profile; | 6 | use ra_prof::profile; |
8 | use ra_syntax::{ | 7 | use 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; | |||
3 | use std::ops::RangeInclusive; | 3 | use std::ops::RangeInclusive; |
4 | 4 | ||
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | use ra_text_edit::TextEditBuilder; | ||
6 | use rustc_hash::FxHashMap; | 7 | use rustc_hash::FxHashMap; |
7 | 8 | ||
8 | use crate::{ | 9 | use crate::{ |
@@ -63,6 +64,18 @@ pub enum InsertPosition<T> { | |||
63 | After(T), | 64 | After(T), |
64 | } | 65 | } |
65 | 66 | ||
67 | pub struct TreeDiff { | ||
68 | replacements: FxHashMap<SyntaxElement, SyntaxElement>, | ||
69 | } | ||
70 | |||
71 | impl 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. |
73 | pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> FxHashMap<SyntaxElement, SyntaxElement> { | 86 | pub 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 | ||
108 | impl 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 | |||
108 | pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: N) -> N { | 188 | pub 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 | } |