diff options
Diffstat (limited to 'crates/ide_assists/src/assist_context.rs')
-rw-r--r-- | crates/ide_assists/src/assist_context.rs | 44 |
1 files changed, 41 insertions, 3 deletions
diff --git a/crates/ide_assists/src/assist_context.rs b/crates/ide_assists/src/assist_context.rs index bba6c08e0..1482d37f8 100644 --- a/crates/ide_assists/src/assist_context.rs +++ b/crates/ide_assists/src/assist_context.rs | |||
@@ -14,8 +14,8 @@ use ide_db::{ | |||
14 | }; | 14 | }; |
15 | use syntax::{ | 15 | use syntax::{ |
16 | algo::{self, find_node_at_offset, SyntaxRewriter}, | 16 | algo::{self, find_node_at_offset, SyntaxRewriter}, |
17 | AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, TextSize, | 17 | AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr, |
18 | TokenAtOffset, | 18 | SyntaxToken, TextRange, TextSize, TokenAtOffset, |
19 | }; | 19 | }; |
20 | use text_edit::{TextEdit, TextEditBuilder}; | 20 | use text_edit::{TextEdit, TextEditBuilder}; |
21 | 21 | ||
@@ -180,11 +180,19 @@ pub(crate) struct AssistBuilder { | |||
180 | edit: TextEditBuilder, | 180 | edit: TextEditBuilder, |
181 | file_id: FileId, | 181 | file_id: FileId, |
182 | source_change: SourceChange, | 182 | source_change: SourceChange, |
183 | |||
184 | /// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin. | ||
185 | mutated_tree: Option<(SyntaxNode, SyntaxNode)>, | ||
183 | } | 186 | } |
184 | 187 | ||
185 | impl AssistBuilder { | 188 | impl AssistBuilder { |
186 | pub(crate) fn new(file_id: FileId) -> AssistBuilder { | 189 | pub(crate) fn new(file_id: FileId) -> AssistBuilder { |
187 | AssistBuilder { edit: TextEdit::builder(), file_id, source_change: SourceChange::default() } | 190 | AssistBuilder { |
191 | edit: TextEdit::builder(), | ||
192 | file_id, | ||
193 | source_change: SourceChange::default(), | ||
194 | mutated_tree: None, | ||
195 | } | ||
188 | } | 196 | } |
189 | 197 | ||
190 | pub(crate) fn edit_file(&mut self, file_id: FileId) { | 198 | pub(crate) fn edit_file(&mut self, file_id: FileId) { |
@@ -193,12 +201,42 @@ impl AssistBuilder { | |||
193 | } | 201 | } |
194 | 202 | ||
195 | fn commit(&mut self) { | 203 | fn commit(&mut self) { |
204 | if let Some((old, new)) = self.mutated_tree.take() { | ||
205 | algo::diff(&old, &new).into_text_edit(&mut self.edit) | ||
206 | } | ||
207 | |||
196 | let edit = mem::take(&mut self.edit).finish(); | 208 | let edit = mem::take(&mut self.edit).finish(); |
197 | if !edit.is_empty() { | 209 | if !edit.is_empty() { |
198 | self.source_change.insert_source_edit(self.file_id, edit); | 210 | self.source_change.insert_source_edit(self.file_id, edit); |
199 | } | 211 | } |
200 | } | 212 | } |
201 | 213 | ||
214 | pub(crate) fn make_ast_mut<N: AstNode>(&mut self, node: N) -> N { | ||
215 | N::cast(self.make_mut(node.syntax().clone())).unwrap() | ||
216 | } | ||
217 | /// Returns a copy of the `node`, suitable for mutation. | ||
218 | /// | ||
219 | /// Syntax trees in rust-analyzer are typically immutable, and mutating | ||
220 | /// operations panic at runtime. However, it is possible to make a copy of | ||
221 | /// the tree and mutate the copy freely. Mutation is based on interior | ||
222 | /// mutability, and different nodes in the same tree see the same mutations. | ||
223 | /// | ||
224 | /// The typical pattern for an assist is to find specific nodes in the read | ||
225 | /// phase, and then get their mutable couterparts using `make_mut` in the | ||
226 | /// mutable state. | ||
227 | pub(crate) fn make_mut(&mut self, node: SyntaxNode) -> SyntaxNode { | ||
228 | let root = &self | ||
229 | .mutated_tree | ||
230 | .get_or_insert_with(|| { | ||
231 | let immutable = node.ancestors().last().unwrap(); | ||
232 | let mutable = immutable.clone_for_update(); | ||
233 | (immutable, mutable) | ||
234 | }) | ||
235 | .1; | ||
236 | let ptr = SyntaxNodePtr::new(&&node); | ||
237 | ptr.to_node(root) | ||
238 | } | ||
239 | |||
202 | /// Remove specified `range` of text. | 240 | /// Remove specified `range` of text. |
203 | pub(crate) fn delete(&mut self, range: TextRange) { | 241 | pub(crate) fn delete(&mut self, range: TextRange) { |
204 | self.edit.delete(range) | 242 | self.edit.delete(range) |