Skip to content

Commit 5bd2f42

Browse files
committed
internal: Extend SourceChangeBuilder to make make working with SyntaxEditors easier
1 parent b9be0f2 commit 5bd2f42

File tree

1 file changed

+95
-1
lines changed

1 file changed

+95
-1
lines changed

src/tools/rust-analyzer/crates/ide-db/src/source_change.rs

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ use crate::{assists::Command, SnippetCap};
99
use base_db::AnchoredPathBuf;
1010
use itertools::Itertools;
1111
use nohash_hasher::IntMap;
12+
use rustc_hash::FxHashMap;
1213
use span::FileId;
1314
use stdx::never;
1415
use syntax::{
15-
algo, AstNode, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize,
16+
algo,
17+
syntax_editor::{SyntaxAnnotation, SyntaxEditor},
18+
AstNode, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize,
1619
};
1720
use text_edit::{TextEdit, TextEditBuilder};
1821

@@ -197,6 +200,11 @@ pub struct SourceChangeBuilder {
197200
pub source_change: SourceChange,
198201
pub command: Option<Command>,
199202

203+
/// Keeps track of all edits performed on each file
204+
pub file_editors: FxHashMap<FileId, SyntaxEditor>,
205+
/// Keeps track of which annotations correspond to which snippets
206+
pub snippet_annotations: Vec<(AnnotationSnippet, SyntaxAnnotation)>,
207+
200208
/// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin.
201209
pub mutated_tree: Option<TreeMutator>,
202210
/// Keeps track of where to place snippets
@@ -238,6 +246,8 @@ impl SourceChangeBuilder {
238246
file_id: file_id.into(),
239247
source_change: SourceChange::default(),
240248
command: None,
249+
file_editors: FxHashMap::default(),
250+
snippet_annotations: vec![],
241251
mutated_tree: None,
242252
snippet_builder: None,
243253
}
@@ -248,7 +258,75 @@ impl SourceChangeBuilder {
248258
self.file_id = file_id.into();
249259
}
250260

261+
pub fn make_editor(&self, node: &SyntaxNode) -> SyntaxEditor {
262+
SyntaxEditor::new(node.ancestors().last().unwrap_or_else(|| node.clone()))
263+
}
264+
265+
pub fn add_file_edits(&mut self, file_id: impl Into<FileId>, edit: SyntaxEditor) {
266+
match self.file_editors.entry(file_id.into()) {
267+
Entry::Occupied(mut entry) => entry.get_mut().merge(edit),
268+
Entry::Vacant(entry) => {
269+
entry.insert(edit);
270+
}
271+
}
272+
}
273+
274+
pub fn make_placeholder_snippet(&mut self, _cap: SnippetCap) -> SyntaxAnnotation {
275+
self.add_snippet_annotation(AnnotationSnippet::Over)
276+
}
277+
278+
pub fn make_tabstop_before(&mut self, _cap: SnippetCap) -> SyntaxAnnotation {
279+
self.add_snippet_annotation(AnnotationSnippet::Before)
280+
}
281+
282+
pub fn make_tabstop_after(&mut self, _cap: SnippetCap) -> SyntaxAnnotation {
283+
self.add_snippet_annotation(AnnotationSnippet::After)
284+
}
285+
251286
fn commit(&mut self) {
287+
// Apply syntax editor edits
288+
for (file_id, editor) in mem::take(&mut self.file_editors) {
289+
let edit_result = editor.finish();
290+
let mut snippet_edit = vec![];
291+
292+
// Find snippet edits
293+
for (kind, annotation) in &self.snippet_annotations {
294+
let elements = edit_result.find_annotation(*annotation);
295+
296+
let snippet = match (kind, elements) {
297+
(AnnotationSnippet::Before, [element]) => {
298+
Snippet::Tabstop(element.text_range().start())
299+
}
300+
(AnnotationSnippet::After, [element]) => {
301+
Snippet::Tabstop(element.text_range().end())
302+
}
303+
(AnnotationSnippet::Over, [element]) => {
304+
Snippet::Placeholder(element.text_range())
305+
}
306+
(AnnotationSnippet::Over, elements) if !elements.is_empty() => {
307+
Snippet::PlaceholderGroup(
308+
elements.iter().map(|it| it.text_range()).collect(),
309+
)
310+
}
311+
_ => continue,
312+
};
313+
314+
snippet_edit.push(snippet);
315+
}
316+
317+
let mut edit = TextEdit::builder();
318+
algo::diff(edit_result.old_root(), edit_result.new_root()).into_text_edit(&mut edit);
319+
let edit = edit.finish();
320+
321+
let snippet_edit =
322+
if !snippet_edit.is_empty() { Some(SnippetEdit::new(snippet_edit)) } else { None };
323+
324+
if !edit.is_empty() || snippet_edit.is_some() {
325+
self.source_change.insert_source_and_snippet_edit(file_id, edit, snippet_edit);
326+
}
327+
}
328+
329+
// Apply mutable edits
252330
let snippet_edit = self.snippet_builder.take().map(|builder| {
253331
SnippetEdit::new(
254332
builder.places.into_iter().flat_map(PlaceSnippet::finalize_position).collect(),
@@ -369,6 +447,13 @@ impl SourceChangeBuilder {
369447
self.source_change.is_snippet = true;
370448
}
371449

450+
fn add_snippet_annotation(&mut self, kind: AnnotationSnippet) -> SyntaxAnnotation {
451+
let annotation = SyntaxAnnotation::new();
452+
self.snippet_annotations.push((kind, annotation));
453+
self.source_change.is_snippet = true;
454+
annotation
455+
}
456+
372457
pub fn finish(mut self) -> SourceChange {
373458
self.commit();
374459

@@ -416,6 +501,15 @@ pub enum Snippet {
416501
PlaceholderGroup(Vec<TextRange>),
417502
}
418503

504+
pub enum AnnotationSnippet {
505+
/// Place a tabstop before an element
506+
Before,
507+
/// Place a tabstop before an element
508+
After,
509+
/// Place a placeholder snippet in place of the element(s)
510+
Over,
511+
}
512+
419513
enum PlaceSnippet {
420514
/// Place a tabstop before an element
421515
Before(SyntaxElement),

0 commit comments

Comments
 (0)