1use crate::syntax::SyntaxKind;
2use crate::{
3 chain_trivia_pieces, AstNode, Language, SyntaxElement, SyntaxNode, SyntaxSlot, SyntaxToken,
4};
5use biome_text_edit::{TextEdit, TextEditBuilder};
6use biome_text_size::TextRange;
7use std::{
8 cmp,
9 collections::BinaryHeap,
10 iter::{empty, once},
11};
12use tracing::debug;
13
14pub trait BatchMutationExt<L>: AstNode<Language = L>
15where
16 L: Language,
17{
18 #[must_use = "This method consumes the node and return the BatchMutation api that returns the new SyntaxNode on commit"]
20 fn begin(self) -> BatchMutation<L>;
21}
22
23impl<L, T> BatchMutationExt<L> for T
24where
25 L: Language,
26 T: AstNode<Language = L>,
27{
28 #[must_use = "This method consumes the node and return the BatchMutation api that returns the new SyntaxNode on commit"]
29 fn begin(self) -> BatchMutation<L> {
30 BatchMutation::new(self.into_syntax())
31 }
32}
33
34#[derive(Debug, Clone)]
40struct CommitChange<L: Language> {
41 parent_depth: usize,
42 parent: Option<SyntaxNode<L>>,
43 parent_range: Option<(u32, u32)>,
44 new_node_slot: usize,
45 new_node: Option<SyntaxElement<L>>,
46 is_from_action: bool,
47}
48
49impl<L: Language> CommitChange<L> {
50 fn key(&self) -> (usize, u32, usize) {
68 (
69 self.parent_depth,
70 self.parent_range.map(|(start, _)| start).unwrap_or(0),
71 self.new_node_slot,
72 )
73 }
74}
75
76impl<L: Language> PartialEq for CommitChange<L> {
77 fn eq(&self, other: &Self) -> bool {
78 self.key() == other.key()
79 }
80}
81impl<L: Language> Eq for CommitChange<L> {}
82
83impl<L: Language> PartialOrd for CommitChange<L> {
84 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
85 Some(self.cmp(other))
86 }
87}
88impl<L: Language> Ord for CommitChange<L> {
89 fn cmp(&self, other: &Self) -> cmp::Ordering {
90 self.key().cmp(&other.key())
91 }
92}
93
94#[derive(Debug, Clone)]
95pub struct BatchMutation<L>
96where
97 L: Language,
98{
99 root: SyntaxNode<L>,
100 changes: BinaryHeap<CommitChange<L>>,
101}
102
103impl<L> BatchMutation<L>
104where
105 L: Language,
106{
107 pub fn new(root: SyntaxNode<L>) -> Self {
108 Self {
109 root,
110 changes: BinaryHeap::new(),
111 }
112 }
113
114 pub fn replace_node<T>(&mut self, prev_node: T, next_node: T)
119 where
120 T: AstNode<Language = L>,
121 {
122 self.replace_element(
123 prev_node.into_syntax().into(),
124 next_node.into_syntax().into(),
125 )
126 }
127
128 pub fn replace_token(&mut self, prev_token: SyntaxToken<L>, next_token: SyntaxToken<L>) {
133 self.replace_element(prev_token.into(), next_token.into())
134 }
135
136 pub fn replace_element(
141 &mut self,
142 prev_element: SyntaxElement<L>,
143 next_element: SyntaxElement<L>,
144 ) {
145 let (prev_leading_trivia, prev_trailing_trivia) = match &prev_element {
146 SyntaxElement::Node(node) => (
147 node.first_token().map(|token| token.leading_trivia()),
148 node.last_token().map(|token| token.trailing_trivia()),
149 ),
150 SyntaxElement::Token(token) => {
151 (Some(token.leading_trivia()), Some(token.trailing_trivia()))
152 }
153 };
154
155 let next_element = match next_element {
156 SyntaxElement::Node(mut node) => {
157 if let Some(token) = node.first_token() {
158 let new_token = match prev_leading_trivia {
159 Some(prev_leading_trivia) => {
160 token.with_leading_trivia_pieces(prev_leading_trivia.pieces())
161 }
162 None => token.with_leading_trivia_pieces(empty()),
163 };
164
165 node = node.replace_child(token.into(), new_token.into()).unwrap();
166 }
167
168 if let Some(token) = node.last_token() {
169 let new_token = match prev_trailing_trivia {
170 Some(prev_trailing_trivia) => {
171 token.with_trailing_trivia_pieces(prev_trailing_trivia.pieces())
172 }
173 None => token.with_trailing_trivia_pieces([]),
174 };
175
176 node = node.replace_child(token.into(), new_token.into()).unwrap();
177 }
178
179 SyntaxElement::Node(node)
180 }
181 SyntaxElement::Token(token) => {
182 let new_token = match prev_leading_trivia {
183 Some(prev_leading_trivia) => {
184 token.with_leading_trivia_pieces(prev_leading_trivia.pieces())
185 }
186 None => token.with_leading_trivia_pieces([]),
187 };
188
189 let new_token = match prev_trailing_trivia {
190 Some(prev_trailing_trivia) => {
191 new_token.with_trailing_trivia_pieces(prev_trailing_trivia.pieces())
192 }
193 None => new_token.with_trailing_trivia_pieces([]),
194 };
195 SyntaxElement::Token(new_token)
196 }
197 };
198
199 self.push_change(prev_element, Some(next_element))
200 }
201
202 pub fn replace_node_discard_trivia<T>(&mut self, prev_node: T, next_node: T)
206 where
207 T: AstNode<Language = L>,
208 {
209 self.replace_element_discard_trivia(
210 prev_node.into_syntax().into(),
211 next_node.into_syntax().into(),
212 )
213 }
214
215 pub fn replace_token_discard_trivia(
219 &mut self,
220 prev_token: SyntaxToken<L>,
221 next_token: SyntaxToken<L>,
222 ) {
223 self.replace_element_discard_trivia(prev_token.into(), next_token.into())
224 }
225
226 pub fn replace_token_transfer_trivia(
233 &mut self,
234 prev_token: SyntaxToken<L>,
235 next_token: SyntaxToken<L>,
236 ) {
237 let leading_trivia = chain_trivia_pieces(
238 prev_token.leading_trivia().pieces(),
239 next_token.leading_trivia().pieces(),
240 );
241
242 let trailing_trivia = chain_trivia_pieces(
243 prev_token.trailing_trivia().pieces(),
244 next_token.trailing_trivia().pieces(),
245 );
246 let new_token = next_token
247 .with_leading_trivia_pieces(leading_trivia)
248 .with_trailing_trivia_pieces(trailing_trivia);
249
250 self.replace_token_discard_trivia(prev_token, new_token)
251 }
252
253 pub fn replace_element_discard_trivia(
257 &mut self,
258 prev_element: SyntaxElement<L>,
259 next_element: SyntaxElement<L>,
260 ) {
261 self.push_change(prev_element, Some(next_element))
262 }
263
264 pub fn remove_token(&mut self, prev_token: SyntaxToken<L>) {
268 self.remove_element(prev_token.into())
269 }
270
271 pub fn remove_node<T>(&mut self, prev_node: T)
275 where
276 T: AstNode<Language = L>,
277 {
278 self.remove_element(prev_node.into_syntax().into())
279 }
280
281 pub fn remove_element(&mut self, prev_element: SyntaxElement<L>) {
285 self.push_change(prev_element, None)
286 }
287
288 fn push_change(
289 &mut self,
290 prev_element: SyntaxElement<L>,
291 next_element: Option<SyntaxElement<L>>,
292 ) {
293 let new_node_slot = prev_element.index();
294 let parent = prev_element.parent();
295 let parent_range: Option<(u32, u32)> = parent.as_ref().map(|p| {
296 let range = p.text_range();
297 (range.start().into(), range.end().into())
298 });
299 let parent_depth = parent.as_ref().map(|p| p.ancestors().count()).unwrap_or(0);
300
301 debug!("pushing change...");
302 self.changes.push(CommitChange {
303 parent_depth,
304 parent,
305 parent_range,
306 new_node_slot,
307 new_node: next_element,
308 is_from_action: true,
309 });
310 }
311
312 pub fn as_text_range_and_edit(self) -> Option<(TextRange, TextEdit)> {
319 self.commit_with_text_range_and_edit(true).1
320 }
321
322 pub fn commit(self) -> SyntaxNode<L> {
327 self.commit_with_text_range_and_edit(false).0
328 }
329
330 pub fn commit_with_text_range_and_edit(
359 self,
360 with_text_range_and_edit: bool,
361 ) -> (SyntaxNode<L>, Option<(TextRange, TextEdit)>) {
362 let BatchMutation { root, mut changes } = self;
363
364 let mut text_mutation_list: Vec<(TextRange, Option<String>)> =
366 Vec::with_capacity(changes.len());
370
371 while let Some(CommitChange {
373 new_node: curr_new_node,
374 new_node_slot: curr_new_node_slot,
375 parent: curr_parent,
376 parent_depth: curr_parent_depth,
377 is_from_action: curr_is_from_action,
378 ..
379 }) = changes.pop()
380 {
381 if let Some(curr_parent) = curr_parent {
382 let curr_grand_parent = curr_parent.parent();
385 let curr_grand_parent_range = curr_grand_parent.as_ref().map(|g| {
386 let range = g.text_range();
387 (range.start().into(), range.end().into())
388 });
389 let curr_parent_slot = curr_parent.index();
390
391 let mut modifications =
394 vec![(curr_new_node_slot, curr_new_node, curr_is_from_action)];
395
396 while changes
397 .peek()
398 .and_then(|c| c.parent.as_ref())
399 .map_or(false, |p| *p == curr_parent)
400 {
401 let CommitChange {
403 new_node: next_new_node,
404 new_node_slot: next_new_node_slot,
405 is_from_action: next_is_from_action,
406 ..
407 } = changes.pop().expect("changes.pop");
408
409 if let Some(&(prev_new_node_slot, ..)) = modifications.last() {
412 if prev_new_node_slot == next_new_node_slot {
413 modifications.pop();
414 }
415 }
416
417 modifications.push((next_new_node_slot, next_new_node, next_is_from_action));
419 }
420
421 if with_text_range_and_edit {
424 for (new_node_slot, new_node, is_from_action) in &modifications {
425 if !is_from_action {
426 continue;
427 }
428 let deleted_text_range = match curr_parent.slots().nth(*new_node_slot) {
429 Some(SyntaxSlot::Node(node)) => node.text_range(),
430 Some(SyntaxSlot::Token(token)) => token.text_range(),
431 Some(SyntaxSlot::Empty { index }) => {
432 TextRange::new(index.into(), index.into())
433 }
434 None => continue,
435 };
436 let optional_inserted_text = new_node.as_ref().map(|n| n.to_string());
437
438 match text_mutation_list
440 .binary_search_by(|(range, _)| range.ordering(deleted_text_range))
441 {
442 Ok(pos) => {
444 text_mutation_list[pos] =
445 (deleted_text_range, optional_inserted_text)
446 }
447 Err(pos) => text_mutation_list
449 .insert(pos, (deleted_text_range, optional_inserted_text)),
450 }
451 }
452 }
453
454 let mut current_parent = curr_parent.detach();
457 let is_list = current_parent.kind().is_list();
458 for (new_node_slot, new_node, ..) in modifications {
459 current_parent = if is_list && new_node.is_none() {
460 current_parent.splice_slots(new_node_slot..=new_node_slot, empty())
461 } else {
462 current_parent.splice_slots(new_node_slot..=new_node_slot, once(new_node))
463 }
464 }
465
466 changes.push(CommitChange {
467 parent_depth: curr_parent_depth - 1,
468 parent: curr_grand_parent,
469 parent_range: curr_grand_parent_range,
470 new_node_slot: curr_parent_slot,
471 new_node: Some(SyntaxElement::Node(current_parent)),
472 is_from_action: false,
473 });
474 }
475 else {
477 let optional_text_range_and_edit = if with_text_range_and_edit {
478 let mut document_root = root;
482 while let Some(parent) = document_root.parent() {
483 document_root = parent;
484 }
485
486 if curr_is_from_action {
487 text_mutation_list = vec![(
488 document_root.text_range(),
489 Some(
490 curr_new_node
491 .as_ref()
492 .map_or(String::new(), |n| n.to_string()),
493 ),
494 )];
495 }
496
497 let root_string = document_root.to_string();
499 let mut text_range = TextRange::default();
500 let mut text_edit_builder = TextEditBuilder::default();
501
502 let mut pointer: usize = 0;
503 for (deleted_text_range, optional_inserted_text) in text_mutation_list {
504 if let (Ok(range_start), Ok(range_end)) = (
505 usize::try_from(u32::from(deleted_text_range.start())),
506 usize::try_from(u32::from(deleted_text_range.end())),
507 ) {
508 text_range = text_range.cover(deleted_text_range);
509 if range_start > pointer {
510 text_edit_builder.equal(&root_string[pointer..range_start]);
511 }
512
513 let old = &root_string[range_start..range_end];
514 let new = &optional_inserted_text.map_or(String::new(), |t| t);
515
516 text_edit_builder.with_unicode_words_diff(old, new);
517
518 pointer = range_end;
519 }
520 }
521 let end_pos = root_string.len();
522 if end_pos > pointer {
523 text_edit_builder.equal(&root_string[pointer..end_pos]);
524 }
525
526 let text_edit = text_edit_builder.finish();
527
528 Some((text_range, text_edit))
529 } else {
530 None
531 };
532
533 return (
534 curr_new_node
541 .expect("expected root to exist")
542 .into_node()
543 .expect("expected root to be a node and not a token"),
544 optional_text_range_and_edit,
545 );
546 }
547 }
548
549 (root, None)
550 }
551
552 pub fn root(&self) -> &SyntaxNode<L> {
553 &self.root
554 }
555}
556
557#[cfg(test)]
558pub mod test {
559 use crate::{
560 raw_language::{LiteralExpression, RawLanguageKind, RawLanguageRoot, RawSyntaxTreeBuilder},
561 AstNode, BatchMutationExt, SyntaxNodeCast,
562 };
563
564 fn tree_one(a: &str) -> (RawLanguageRoot, String) {
570 let mut builder = RawSyntaxTreeBuilder::new();
571 builder
572 .start_node(RawLanguageKind::ROOT)
573 .start_node(RawLanguageKind::LITERAL_EXPRESSION)
574 .token(RawLanguageKind::STRING_TOKEN, a)
575 .finish_node()
576 .finish_node();
577 let root = builder.finish().cast::<RawLanguageRoot>().unwrap();
578 let s = format!("{:#?}", root.syntax());
579 (root, s)
580 }
581
582 fn tree_two(a: &str, b: &str) -> (RawLanguageRoot, String) {
590 let mut builder = RawSyntaxTreeBuilder::new();
591 builder
592 .start_node(RawLanguageKind::ROOT)
593 .start_node(RawLanguageKind::LITERAL_EXPRESSION)
594 .token(RawLanguageKind::STRING_TOKEN, a)
595 .finish_node()
596 .start_node(RawLanguageKind::LITERAL_EXPRESSION)
597 .token(RawLanguageKind::STRING_TOKEN, b)
598 .finish_node()
599 .finish_node();
600 let root = builder.finish().cast::<RawLanguageRoot>().unwrap();
601 let s = format!("{:#?}", root.syntax());
602 (root, s)
603 }
604
605 fn find(root: &RawLanguageRoot, name: &str) -> LiteralExpression {
606 root.syntax()
607 .descendants()
608 .find(|x| x.kind() == RawLanguageKind::LITERAL_EXPRESSION && x.text_trimmed() == name)
609 .unwrap()
610 .cast::<LiteralExpression>()
611 .unwrap()
612 }
613
614 fn clone_detach(root: &RawLanguageRoot, name: &str) -> LiteralExpression {
615 root.syntax()
616 .descendants()
617 .find(|x| x.kind() == RawLanguageKind::LITERAL_EXPRESSION && x.text_trimmed() == name)
618 .unwrap()
619 .detach()
620 .cast::<LiteralExpression>()
621 .unwrap()
622 }
623
624 #[test]
625 pub fn ok_batch_mutation_no_changes() {
626 let (before, before_debug) = tree_one("a");
627
628 let batch = before.begin();
629 let after = batch.commit();
630
631 assert_eq!(before_debug, format!("{after:#?}"));
632 }
633
634 #[test]
635 pub fn ok_batch_mutation_one_change() {
636 let (before, _) = tree_one("a");
637 let (expected, expected_debug) = tree_one("b");
638
639 let a = find(&before, "a");
640 let b = clone_detach(&expected, "b");
641
642 let mut batch = before.begin();
643 batch.replace_node(a, b);
644 let root = batch.commit();
645
646 assert_eq!(expected_debug, format!("{root:#?}"));
647 }
648
649 #[test]
650 pub fn ok_batch_mutation_multiple_changes_different_branches() {
651 let (before, _) = tree_two("a", "b");
652 let (expected, expected_debug) = tree_two("c", "d");
653
654 let a = find(&before, "a");
655 let b = find(&before, "b");
656 let c = clone_detach(&expected, "c");
657 let d = clone_detach(&expected, "d");
658
659 let mut batch = before.begin();
660 batch.replace_node(a, c);
661 batch.replace_node(b, d);
662 let after = batch.commit();
663
664 assert_eq!(expected_debug, format!("{after:#?}"));
665 }
666}