1use std::ops::Range;
2use std::sync::Arc;
3
4use crate::model::piece_tree::{LeafData, PieceTreeNode};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct PieceTreeDiff {
9 pub equal: bool,
11 pub byte_ranges: Vec<Range<usize>>,
13 pub nodes_visited: usize,
17}
18
19pub fn diff_piece_trees(before: &Arc<PieceTreeNode>, after: &Arc<PieceTreeNode>) -> PieceTreeDiff {
25 if Arc::ptr_eq(before, after) {
27 return PieceTreeDiff {
28 equal: true,
29 byte_ranges: Vec::new(),
30 nodes_visited: 1,
31 };
32 }
33
34 let mut before_spans = Vec::new();
37 let mut after_spans = Vec::new();
38 let mut nodes_visited: usize = 0;
39 let mut before_doc_offset: usize = 0;
40 let mut after_doc_offset: usize = 0;
41 diff_collect_leaves(
42 before,
43 after,
44 &mut before_spans,
45 &mut after_spans,
46 &mut nodes_visited,
47 &mut before_doc_offset,
48 &mut after_doc_offset,
49 );
50
51 let before_spans = normalize_spans(before_spans);
52 let after_spans = normalize_spans(after_spans);
53
54 if span_slices_equal(&before_spans, &after_spans) {
56 return PieceTreeDiff {
57 equal: true,
58 byte_ranges: Vec::new(),
59 nodes_visited,
60 };
61 }
62
63 let prefix = common_prefix_bytes(&before_spans, &after_spans);
65 let suffix = common_suffix_bytes(&before_spans, &after_spans, prefix);
67
68 let ranges = collect_diff_ranges(&before_spans, &after_spans, prefix, suffix);
69
70 PieceTreeDiff {
71 equal: false,
72 byte_ranges: ranges,
73 nodes_visited,
74 }
75}
76
77fn node_bytes(node: &PieceTreeNode) -> usize {
79 match node {
80 PieceTreeNode::Internal {
81 left_bytes, right, ..
82 } => left_bytes + node_bytes(right),
83 PieceTreeNode::Leaf { bytes, .. } => *bytes,
84 }
85}
86
87fn diff_collect_leaves(
91 before: &Arc<PieceTreeNode>,
92 after: &Arc<PieceTreeNode>,
93 before_out: &mut Vec<Span>,
94 after_out: &mut Vec<Span>,
95 nodes_visited: &mut usize,
96 before_doc_offset: &mut usize,
97 after_doc_offset: &mut usize,
98) {
99 *nodes_visited += 2; if Arc::ptr_eq(before, after) {
103 let bytes = node_bytes(before);
104 *before_doc_offset += bytes;
105 *after_doc_offset += bytes;
106 return;
107 }
108
109 match (before.as_ref(), after.as_ref()) {
110 (
112 PieceTreeNode::Internal {
113 left: b_left,
114 right: b_right,
115 ..
116 },
117 PieceTreeNode::Internal {
118 left: a_left,
119 right: a_right,
120 ..
121 },
122 ) => {
123 diff_collect_leaves(
124 b_left,
125 a_left,
126 before_out,
127 after_out,
128 nodes_visited,
129 before_doc_offset,
130 after_doc_offset,
131 );
132 diff_collect_leaves(
133 b_right,
134 a_right,
135 before_out,
136 after_out,
137 nodes_visited,
138 before_doc_offset,
139 after_doc_offset,
140 );
141 }
142 _ => {
144 collect_leaves_with_offsets(before, before_out, nodes_visited, before_doc_offset);
145 collect_leaves_with_offsets(after, after_out, nodes_visited, after_doc_offset);
146 }
147 }
148}
149
150fn collect_leaves_with_offsets(
151 node: &Arc<PieceTreeNode>,
152 out: &mut Vec<Span>,
153 nodes_visited: &mut usize,
154 doc_offset: &mut usize,
155) {
156 *nodes_visited += 1;
157 match node.as_ref() {
158 PieceTreeNode::Internal { left, right, .. } => {
159 collect_leaves_with_offsets(left, out, nodes_visited, doc_offset);
160 collect_leaves_with_offsets(right, out, nodes_visited, doc_offset);
161 }
162 PieceTreeNode::Leaf {
163 location,
164 offset,
165 bytes,
166 line_feed_cnt,
167 } => {
168 let leaf = LeafData::new(*location, *offset, *bytes, *line_feed_cnt);
169 out.push(Span {
170 leaf,
171 doc_offset: *doc_offset,
172 });
173 *doc_offset += bytes;
174 }
175 }
176}
177
178#[derive(Clone)]
179struct Span {
180 leaf: LeafData,
181 doc_offset: usize,
182}
183
184fn spans_equal(a: &Span, b: &Span) -> bool {
185 a.leaf.location == b.leaf.location
186 && a.leaf.offset == b.leaf.offset
187 && a.leaf.bytes == b.leaf.bytes
188}
189
190fn span_slices_equal(a: &[Span], b: &[Span]) -> bool {
191 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| spans_equal(x, y))
192}
193
194fn normalize_spans(spans: Vec<Span>) -> Vec<Span> {
195 if spans.is_empty() {
196 return spans;
197 }
198
199 let mut it = spans.into_iter();
200 let mut current = it.next().unwrap();
201 let mut normalized = Vec::new();
202
203 for span in it {
204 let contiguous = current.leaf.location == span.leaf.location
205 && current.leaf.offset + current.leaf.bytes == span.leaf.offset
206 && current.doc_offset + current.leaf.bytes == span.doc_offset;
207 if contiguous {
208 current.leaf.bytes += span.leaf.bytes;
209 current.leaf.line_feed_cnt = match (current.leaf.line_feed_cnt, span.leaf.line_feed_cnt)
210 {
211 (Some(a), Some(b)) => Some(a + b),
212 _ => None,
213 };
214 } else {
215 normalized.push(current);
216 current = span;
217 }
218 }
219
220 normalized.push(current);
221 normalized
222}
223
224fn common_prefix_bytes(before: &[Span], after: &[Span]) -> usize {
225 let mut b_idx = 0;
226 let mut a_idx = 0;
227 let mut b_off = 0;
228 let mut a_off = 0;
229 let mut consumed = 0;
230
231 while b_idx < before.len() && a_idx < after.len() {
232 let b_span = &before[b_idx];
233 let a_span = &after[a_idx];
234 let b = &b_span.leaf;
235 let a = &a_span.leaf;
236
237 let b_pos = b.offset + b_off;
238 let a_pos = a.offset + a_off;
239
240 if b.location == a.location
243 && b_pos == a_pos
244 && (b_span.doc_offset + b_off) == (a_span.doc_offset + a_off)
245 {
246 let b_rem = b.bytes - b_off;
247 let a_rem = a.bytes - a_off;
248 let take = b_rem.min(a_rem);
249
250 consumed += take;
251 b_off += take;
252 a_off += take;
253
254 if b_off == b.bytes {
255 b_idx += 1;
256 b_off = 0;
257 }
258 if a_off == a.bytes {
259 a_idx += 1;
260 a_off = 0;
261 }
262 } else {
263 break;
264 }
265 }
266
267 consumed
268}
269
270fn common_suffix_bytes(before: &[Span], after: &[Span], prefix_bytes: usize) -> usize {
271 let total_before = before
272 .last()
273 .map(|s| s.doc_offset + s.leaf.bytes)
274 .unwrap_or(0);
275 let total_after = after
276 .last()
277 .map(|s| s.doc_offset + s.leaf.bytes)
278 .unwrap_or(0);
279
280 let mut b_idx: isize = before.len() as isize - 1;
281 let mut a_idx: isize = after.len() as isize - 1;
282 let mut b_off = 0;
283 let mut a_off = 0;
284 let mut consumed = 0;
285
286 while b_idx >= 0
287 && a_idx >= 0
288 && (total_before - consumed) > prefix_bytes
289 && (total_after - consumed) > prefix_bytes
290 {
291 let b_span = &before[b_idx as usize];
292 let a_span = &after[a_idx as usize];
293 let b_leaf = &b_span.leaf;
294 let a_leaf = &a_span.leaf;
295
296 let b_pos = b_leaf.offset + b_leaf.bytes - b_off;
297 let a_pos = a_leaf.offset + a_leaf.bytes - a_off;
298
299 if b_leaf.location == a_leaf.location && b_pos == a_pos {
303 let b_rem = b_leaf.bytes - b_off;
304 let a_rem = a_leaf.bytes - a_off;
305 let take = b_rem.min(a_rem);
306
307 consumed += take;
308 b_off += take;
309 a_off += take;
310
311 if b_off == b_leaf.bytes {
312 b_idx -= 1;
313 b_off = 0;
314 }
315 if a_off == a_leaf.bytes {
316 a_idx -= 1;
317 a_off = 0;
318 }
319 } else {
320 break;
321 }
322 }
323
324 consumed.min(total_after.saturating_sub(prefix_bytes))
325}
326
327fn collect_diff_ranges(
328 before: &[Span],
329 after: &[Span],
330 prefix: usize,
331 suffix: usize,
332) -> Vec<Range<usize>> {
333 let mut ranges = Vec::new();
334 let mut b_idx = 0;
335 let mut a_idx = 0;
336 let mut b_off = 0;
337 let mut a_off = 0;
338 let mut matched_prefix = 0;
339
340 while matched_prefix < prefix && b_idx < before.len() && a_idx < after.len() {
342 let b = &before[b_idx].leaf;
343 let a = &after[a_idx].leaf;
344 let b_rem = b.bytes - b_off;
345 let a_rem = a.bytes - a_off;
346 let take = b_rem.min(a_rem).min(prefix - matched_prefix);
347 matched_prefix += take;
348 b_off += take;
349 a_off += take;
350 if b_off == b.bytes {
351 b_idx += 1;
352 b_off = 0;
353 }
354 if a_off == a.bytes {
355 a_idx += 1;
356 a_off = 0;
357 }
358 }
359
360 let doc_end = after
362 .last()
363 .map(|s| s.doc_offset + s.leaf.bytes)
364 .unwrap_or(0);
365 let compare_limit = doc_end.saturating_sub(suffix);
366
367 let doc_start = after.first().map(|s| s.doc_offset).unwrap_or(0);
369
370 let mut current_start: Option<usize> = None;
371 let mut current_end: usize = 0;
372
373 while a_idx < after.len() {
374 let a = &after[a_idx];
375 let pos = a.doc_offset + a_off;
376 if pos >= compare_limit {
377 break;
378 }
379
380 if let Some(start) = current_start {
384 if pos > current_end {
385 ranges.push(start..current_end);
386 current_start = None;
387 }
388 }
389
390 let matches = if b_idx < before.len() {
391 let b = &before[b_idx].leaf;
392 let b_pos = b.offset + b_off;
393 let a_pos = a.leaf.offset + a_off;
394 b.location == a.leaf.location && b_pos == a_pos
397 } else {
398 false
399 };
400
401 if matches {
402 if let Some(start) = current_start.take() {
403 ranges.push(start..current_end);
404 }
405
406 let b = &before[b_idx].leaf;
407 let b_rem = b.bytes - b_off;
408 let a_rem = a.leaf.bytes - a_off;
409 let take = b_rem.min(a_rem).min(compare_limit.saturating_sub(pos));
410
411 b_off += take;
412 a_off += take;
413
414 if b_off == b.bytes {
415 b_idx += 1;
416 b_off = 0;
417 }
418 if a_off == a.leaf.bytes {
419 a_idx += 1;
420 a_off = 0;
421 }
422 } else {
423 if current_start.is_none() {
424 current_start = Some(pos);
425 }
426 let take = (a.leaf.bytes - a_off).min(compare_limit.saturating_sub(pos));
427 current_end = pos + take;
428 a_off += take;
429 if a_off == a.leaf.bytes {
430 a_idx += 1;
431 a_off = 0;
432 }
433 }
434 }
435
436 if let Some(start) = current_start {
437 ranges.push(start..current_end);
438 }
439
440 while a_idx < after.len() {
442 let start = after[a_idx].doc_offset + a_off;
443 if start >= compare_limit {
444 break;
445 }
446 let end = (after[a_idx].doc_offset + after[a_idx].leaf.bytes).min(compare_limit);
447 ranges.push(start..end);
448 a_idx += 1;
449 a_off = 0;
450 }
451
452 if ranges.is_empty() {
453 ranges.push((doc_start + prefix)..compare_limit);
456 }
457
458 ranges
459}
460
461#[cfg(test)]
462#[allow(clippy::single_range_in_vec_init)]
463mod tests {
464 use super::*;
465 use crate::model::piece_tree::BufferLocation;
466
467 fn sum_bytes(leaves: &[LeafData]) -> usize {
468 leaves.iter().map(|leaf| leaf.bytes).sum()
469 }
470
471 fn leaf(loc: BufferLocation, offset: usize, bytes: usize, lfs: Option<usize>) -> LeafData {
472 LeafData::new(loc, offset, bytes, lfs)
473 }
474
475 fn build(leaves: &[LeafData]) -> Arc<PieceTreeNode> {
477 if leaves.is_empty() {
478 return Arc::new(PieceTreeNode::Leaf {
479 location: BufferLocation::Stored(0),
480 offset: 0,
481 bytes: 0,
482 line_feed_cnt: Some(0),
483 });
484 }
485 if leaves.len() == 1 {
486 let l = leaves[0];
487 return Arc::new(PieceTreeNode::Leaf {
488 location: l.location,
489 offset: l.offset,
490 bytes: l.bytes,
491 line_feed_cnt: l.line_feed_cnt,
492 });
493 }
494
495 let mid = leaves.len() / 2;
496 let left = build(&leaves[..mid]);
497 let right = build(&leaves[mid..]);
498
499 Arc::new(PieceTreeNode::Internal {
500 left_bytes: sum_bytes(&leaves[..mid]),
501 lf_left: leaves[..mid]
502 .iter()
503 .map(|l| l.line_feed_cnt)
504 .try_fold(0usize, |acc, v| v.map(|b| acc + b)),
505 left,
506 right,
507 })
508 }
509
510 #[test]
511 fn detects_identical_trees() {
512 let leaves = vec![leaf(BufferLocation::Stored(0), 0, 10, Some(0))];
513 let before = build(&leaves);
514 let after = build(&leaves);
515
516 let diff = diff_piece_trees(&before, &after);
517 assert!(diff.equal);
518 assert!(diff.byte_ranges.is_empty());
519 }
520
521 #[test]
522 fn detects_single_line_change() {
523 let before = build(&[leaf(BufferLocation::Stored(0), 0, 5, Some(0))]);
524 let after = build(&[leaf(BufferLocation::Added(1), 0, 5, Some(0))]);
525
526 let diff = diff_piece_trees(&before, &after);
527 assert!(!diff.equal);
528 assert_eq!(diff.byte_ranges, vec![0..5]);
529 }
530
531 #[test]
532 fn tracks_newlines_in_changed_span() {
533 let before = build(&[leaf(BufferLocation::Stored(0), 0, 6, Some(0))]);
534 let after = build(&[leaf(BufferLocation::Added(1), 0, 6, Some(1))]);
535
536 let diff = diff_piece_trees(&before, &after);
537 assert!(!diff.equal);
538 assert_eq!(diff.byte_ranges, vec![0..6]);
539 }
540
541 #[test]
542 fn handles_deletion_by_marking_anchor() {
543 let before = build(&[
544 leaf(BufferLocation::Stored(0), 0, 6, Some(1)),
545 leaf(BufferLocation::Stored(0), 6, 4, Some(0)),
546 ]);
547 let after = build(&[leaf(BufferLocation::Stored(0), 0, 6, Some(1))]);
548
549 let diff = diff_piece_trees(&before, &after);
550 assert!(!diff.equal);
551 assert_eq!(diff.byte_ranges, vec![6..6]);
552 }
553
554 #[test]
557 fn diff_after_path_copy_insert_at_eof() {
558 use crate::model::piece_tree::{PieceTree, StringBuffer};
559
560 let chunk_size = 1000;
561 let total = 10_000;
562 let content: Vec<u8> = (0..total)
563 .map(|i| if i % 100 == 99 { b'\n' } else { b'A' })
564 .collect();
565 let buf = StringBuffer::new_loaded(0, content, false);
566
567 let mut saved_tree = PieceTree::new(BufferLocation::Stored(0), 0, total, None);
568 saved_tree.split_leaves_to_chunk_size(chunk_size);
569 let lf_updates: Vec<(usize, usize)> = (0..10).map(|i| (i, 10)).collect();
570 saved_tree.update_leaf_line_feeds(&lf_updates);
571 let saved_root = saved_tree.root();
572
573 let mut after_tree = saved_tree;
574 let insert_offset = total - 100;
575 let insert_buf = StringBuffer::new_loaded(1, b"HELLO".to_vec(), false);
576 after_tree.insert(
577 insert_offset,
578 BufferLocation::Added(1),
579 0,
580 5,
581 Some(0),
582 &[buf, insert_buf],
583 );
584 let after_root = after_tree.root();
585
586 let diff = diff_piece_trees(&saved_root, &after_root);
587 assert!(!diff.equal);
588
589 assert!(
590 diff.byte_ranges[0].start >= total - 200,
591 "byte_ranges should be document-absolute (near EOF): got {:?}, expected near {}",
592 diff.byte_ranges,
593 insert_offset,
594 );
595 }
596
597 #[test]
600 fn diff_after_rebalance_matches_path_copy_diff() {
601 use crate::model::piece_tree::{PieceTree, StringBuffer};
602
603 let chunk_size = 1000;
604 let total = 10_000;
605 let content: Vec<u8> = (0..total)
606 .map(|i| if i % 100 == 99 { b'\n' } else { b'A' })
607 .collect();
608 let buf = StringBuffer::new_loaded(0, content, false);
609
610 let mut saved_tree = PieceTree::new(BufferLocation::Stored(0), 0, total, None);
611 saved_tree.split_leaves_to_chunk_size(chunk_size);
612 let lf_updates: Vec<(usize, usize)> = (0..10).map(|i| (i, 10)).collect();
613 saved_tree.update_leaf_line_feeds(&lf_updates);
614 let saved_root = saved_tree.root();
615
616 let mut after_tree = saved_tree;
617 let insert_buf = StringBuffer::new_loaded(1, b"HELLO".to_vec(), false);
618 after_tree.insert(
619 total - 100,
620 BufferLocation::Added(1),
621 0,
622 5,
623 Some(0),
624 &[buf.clone(), insert_buf.clone()],
625 );
626
627 let diff_shared = diff_piece_trees(&saved_root, &after_tree.root());
628
629 after_tree.rebalance();
630 let diff_rebalanced = diff_piece_trees(&saved_root, &after_tree.root());
631
632 assert!(!diff_shared.equal);
633 assert!(!diff_rebalanced.equal);
634 assert_eq!(
635 diff_shared.byte_ranges, diff_rebalanced.byte_ranges,
636 "byte_ranges should be identical whether or not Arc sharing exists"
637 );
638 }
639
640 #[test]
641 fn tolerates_split_leaves_with_same_content_prefix() {
642 let before = build(&[leaf(BufferLocation::Stored(0), 0, 100, Some(1))]);
643 let after = build(&[
644 leaf(BufferLocation::Stored(0), 0, 50, Some(0)),
645 leaf(BufferLocation::Added(1), 0, 10, Some(0)),
646 leaf(BufferLocation::Stored(0), 50, 50, Some(1)),
647 ]);
648
649 let diff = diff_piece_trees(&before, &after);
650 assert!(!diff.equal);
651 assert_eq!(diff.byte_ranges, vec![50..60]);
652 }
653
654 #[test]
655 fn diff_with_disjoint_changes() {
656 let leaf1_before = leaf(BufferLocation::Stored(0), 0, 10, Some(0));
657 let leaf2 = leaf(BufferLocation::Stored(0), 10, 10, Some(1));
658 let leaf3_before = leaf(BufferLocation::Stored(0), 20, 10, Some(0));
659
660 let leaf1_after = leaf(BufferLocation::Added(1), 0, 10, Some(0));
661 let leaf3_after = leaf(BufferLocation::Added(1), 10, 10, Some(0));
662
663 let leaf2_arc = Arc::new(PieceTreeNode::Leaf {
664 location: leaf2.location,
665 offset: leaf2.offset,
666 bytes: leaf2.bytes,
667 line_feed_cnt: leaf2.line_feed_cnt,
668 });
669
670 let before = Arc::new(PieceTreeNode::Internal {
671 left_bytes: 10,
672 lf_left: Some(0),
673 left: Arc::new(PieceTreeNode::Leaf {
674 location: leaf1_before.location,
675 offset: leaf1_before.offset,
676 bytes: leaf1_before.bytes,
677 line_feed_cnt: leaf1_before.line_feed_cnt,
678 }),
679 right: Arc::new(PieceTreeNode::Internal {
680 left_bytes: 10,
681 lf_left: Some(1),
682 left: Arc::clone(&leaf2_arc),
683 right: Arc::new(PieceTreeNode::Leaf {
684 location: leaf3_before.location,
685 offset: leaf3_before.offset,
686 bytes: leaf3_before.bytes,
687 line_feed_cnt: leaf3_before.line_feed_cnt,
688 }),
689 }),
690 });
691
692 let after = Arc::new(PieceTreeNode::Internal {
693 left_bytes: 10,
694 lf_left: Some(0),
695 left: Arc::new(PieceTreeNode::Leaf {
696 location: leaf1_after.location,
697 offset: leaf1_after.offset,
698 bytes: leaf1_after.bytes,
699 line_feed_cnt: leaf1_after.line_feed_cnt,
700 }),
701 right: Arc::new(PieceTreeNode::Internal {
702 left_bytes: 10,
703 lf_left: Some(1),
704 left: Arc::clone(&leaf2_arc), right: Arc::new(PieceTreeNode::Leaf {
706 location: leaf3_after.location,
707 offset: leaf3_after.offset,
708 bytes: leaf3_after.bytes,
709 line_feed_cnt: leaf3_after.line_feed_cnt,
710 }),
711 }),
712 });
713
714 let diff = diff_piece_trees(&before, &after);
715
716 assert_eq!(diff.byte_ranges, vec![0..10, 20..30]);
717 }
718}