aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2021-03-23 14:31:19 +0000
committerAleksey Kladov <[email protected]>2021-03-23 14:31:19 +0000
commitb83c7eedccea4c9cb35b1d1cc58231f07a5e3ba2 (patch)
treeb6a237b53f65aaf96fc65707580ef41ab9a73d2d /crates/ide_assists
parent258afb8fb8331e43a75e4f19df255d85d2430be7 (diff)
Tweak assits API to fit mutable syntax trees
changelog: skip
Diffstat (limited to 'crates/ide_assists')
-rw-r--r--crates/ide_assists/src/assist_context.rs44
-rw-r--r--crates/ide_assists/src/handlers/merge_imports.rs24
-rw-r--r--crates/ide_assists/src/handlers/move_bounds.rs8
3 files changed, 53 insertions, 23 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)
diff --git a/crates/ide_assists/src/handlers/merge_imports.rs b/crates/ide_assists/src/handlers/merge_imports.rs
index cfc472a32..8e0794218 100644
--- a/crates/ide_assists/src/handlers/merge_imports.rs
+++ b/crates/ide_assists/src/handlers/merge_imports.rs
@@ -21,12 +21,6 @@ use crate::{
21// ``` 21// ```
22pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 22pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
23 let tree: ast::UseTree = ctx.find_node_at_offset()?; 23 let tree: ast::UseTree = ctx.find_node_at_offset()?;
24 let original_parent = tree.syntax().ancestors().last()?;
25
26 let tree = tree.clone_for_update();
27 let new_parent = tree.syntax().ancestors().last()?;
28
29 let mut offset = ctx.offset();
30 24
31 let mut imports = None; 25 let mut imports = None;
32 let mut uses = None; 26 let mut uses = None;
@@ -53,22 +47,20 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
53 target, 47 target,
54 |builder| { 48 |builder| {
55 if let Some((to_replace, replacement, to_remove)) = imports { 49 if let Some((to_replace, replacement, to_remove)) = imports {
56 if to_remove.syntax().text_range().end() < offset { 50 let to_replace = builder.make_ast_mut(to_replace);
57 offset -= to_remove.syntax().text_range().len(); 51 let to_remove = builder.make_ast_mut(to_remove);
58 } 52
59 ted::replace(to_replace.syntax().clone(), replacement.syntax().clone()); 53 ted::replace(to_replace.syntax(), replacement.syntax());
60 to_remove.remove(); 54 to_remove.remove();
61 } 55 }
62 56
63 if let Some((to_replace, replacement, to_remove)) = uses { 57 if let Some((to_replace, replacement, to_remove)) = uses {
64 if to_remove.syntax().text_range().end() < offset { 58 let to_replace = builder.make_ast_mut(to_replace);
65 offset -= to_remove.syntax().text_range().len(); 59 let to_remove = builder.make_ast_mut(to_remove);
66 } 60
67 ted::replace(to_replace.syntax().clone(), replacement.syntax().clone()); 61 ted::replace(to_replace.syntax(), replacement.syntax());
68 to_remove.remove() 62 to_remove.remove()
69 } 63 }
70
71 builder.replace(original_parent.text_range(), new_parent.to_string())
72 }, 64 },
73 ) 65 )
74} 66}
diff --git a/crates/ide_assists/src/handlers/move_bounds.rs b/crates/ide_assists/src/handlers/move_bounds.rs
index b5dec8014..011a28d44 100644
--- a/crates/ide_assists/src/handlers/move_bounds.rs
+++ b/crates/ide_assists/src/handlers/move_bounds.rs
@@ -21,7 +21,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
21// } 21// }
22// ``` 22// ```
23pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 23pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let type_param_list = ctx.find_node_at_offset::<ast::GenericParamList>()?.clone_for_update(); 24 let type_param_list = ctx.find_node_at_offset::<ast::GenericParamList>()?;
25 25
26 let mut type_params = type_param_list.type_params(); 26 let mut type_params = type_param_list.type_params();
27 if type_params.all(|p| p.type_bound_list().is_none()) { 27 if type_params.all(|p| p.type_bound_list().is_none()) {
@@ -29,7 +29,6 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext
29 } 29 }
30 30
31 let parent = type_param_list.syntax().parent()?; 31 let parent = type_param_list.syntax().parent()?;
32 let original_parent_range = parent.text_range();
33 32
34 let target = type_param_list.syntax().text_range(); 33 let target = type_param_list.syntax().text_range();
35 acc.add( 34 acc.add(
@@ -37,6 +36,9 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext
37 "Move to where clause", 36 "Move to where clause",
38 target, 37 target,
39 |edit| { 38 |edit| {
39 let type_param_list = edit.make_ast_mut(type_param_list);
40 let parent = edit.make_mut(parent);
41
40 let where_clause: ast::WhereClause = match_ast! { 42 let where_clause: ast::WhereClause = match_ast! {
41 match parent { 43 match parent {
42 ast::Fn(it) => it.get_or_create_where_clause(), 44 ast::Fn(it) => it.get_or_create_where_clause(),
@@ -56,8 +58,6 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext
56 tbl.remove() 58 tbl.remove()
57 } 59 }
58 } 60 }
59
60 edit.replace(original_parent_range, parent.to_string())
61 }, 61 },
62 ) 62 )
63} 63}