aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/assist_context.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/assist_context.rs')
-rw-r--r--crates/ide_assists/src/assist_context.rs44
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};
15use syntax::{ 15use 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};
20use text_edit::{TextEdit, TextEditBuilder}; 20use 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
185impl AssistBuilder { 188impl 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)