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 line_ranges: Option<Vec<Range<usize>>>,
15 pub nodes_visited: usize,
19}
20
21pub fn diff_piece_trees(
30 before: &Arc<PieceTreeNode>,
31 after: &Arc<PieceTreeNode>,
32 line_counter: &dyn Fn(&LeafData, usize, usize) -> Option<usize>,
33) -> PieceTreeDiff {
34 if Arc::ptr_eq(before, after) {
36 return PieceTreeDiff {
37 equal: true,
38 byte_ranges: Vec::new(),
39 line_ranges: Some(Vec::new()),
40 nodes_visited: 1,
41 };
42 }
43
44 let mut before_spans = Vec::new();
47 let mut after_spans = Vec::new();
48 let mut nodes_visited: usize = 0;
49 let mut before_doc_offset: usize = 0;
50 let mut after_doc_offset: usize = 0;
51 let mut after_doc_lf: Option<usize> = Some(0);
52 diff_collect_leaves(
53 before,
54 after,
55 &mut before_spans,
56 &mut after_spans,
57 &mut nodes_visited,
58 &mut before_doc_offset,
59 &mut after_doc_offset,
60 &mut after_doc_lf,
61 );
62
63 let before_spans = normalize_spans(before_spans);
64 let after_spans = normalize_spans(after_spans);
65
66 if span_slices_equal(&before_spans, &after_spans) {
68 return PieceTreeDiff {
69 equal: true,
70 byte_ranges: Vec::new(),
71 line_ranges: Some(Vec::new()),
72 nodes_visited,
73 };
74 }
75
76 let prefix = common_prefix_bytes(&before_spans, &after_spans);
78 let suffix = common_suffix_bytes(&before_spans, &after_spans, prefix);
80
81 let ranges = collect_diff_ranges(&before_spans, &after_spans, prefix, suffix);
82
83 let line_ranges = line_ranges(&after_spans, &ranges, line_counter);
85
86 PieceTreeDiff {
87 equal: false,
88 byte_ranges: ranges,
89 line_ranges,
90 nodes_visited,
91 }
92}
93
94fn node_bytes(node: &PieceTreeNode) -> usize {
96 match node {
97 PieceTreeNode::Internal {
98 left_bytes, right, ..
99 } => left_bytes + node_bytes(right),
100 PieceTreeNode::Leaf { bytes, .. } => *bytes,
101 }
102}
103
104fn node_line_feeds(node: &PieceTreeNode) -> Option<usize> {
106 match node {
107 PieceTreeNode::Internal { lf_left, right, .. } => {
108 let left_lf = (*lf_left)?;
109 let right_lf = node_line_feeds(right)?;
110 Some(left_lf + right_lf)
111 }
112 PieceTreeNode::Leaf { line_feed_cnt, .. } => *line_feed_cnt,
113 }
114}
115
116fn diff_collect_leaves(
121 before: &Arc<PieceTreeNode>,
122 after: &Arc<PieceTreeNode>,
123 before_out: &mut Vec<Span>,
124 after_out: &mut Vec<Span>,
125 nodes_visited: &mut usize,
126 before_doc_offset: &mut usize,
127 after_doc_offset: &mut usize,
128 after_doc_lf: &mut Option<usize>,
129) {
130 *nodes_visited += 2; if Arc::ptr_eq(before, after) {
134 let bytes = node_bytes(before);
135 *before_doc_offset += bytes;
136 *after_doc_offset += bytes;
137 *after_doc_lf = match (*after_doc_lf, node_line_feeds(after)) {
138 (Some(a), Some(b)) => Some(a + b),
139 _ => None,
140 };
141 return;
142 }
143
144 match (before.as_ref(), after.as_ref()) {
145 (
147 PieceTreeNode::Internal {
148 left: b_left,
149 right: b_right,
150 ..
151 },
152 PieceTreeNode::Internal {
153 left: a_left,
154 right: a_right,
155 ..
156 },
157 ) => {
158 diff_collect_leaves(
159 b_left,
160 a_left,
161 before_out,
162 after_out,
163 nodes_visited,
164 before_doc_offset,
165 after_doc_offset,
166 after_doc_lf,
167 );
168 diff_collect_leaves(
169 b_right,
170 a_right,
171 before_out,
172 after_out,
173 nodes_visited,
174 before_doc_offset,
175 after_doc_offset,
176 after_doc_lf,
177 );
178 }
179 _ => {
181 let mut before_lf_dummy = Some(0); collect_leaves_with_offsets(
183 before,
184 before_out,
185 nodes_visited,
186 before_doc_offset,
187 &mut before_lf_dummy,
188 );
189 collect_leaves_with_offsets(
190 after,
191 after_out,
192 nodes_visited,
193 after_doc_offset,
194 after_doc_lf,
195 );
196 }
197 }
198}
199
200fn collect_leaves_with_offsets(
201 node: &Arc<PieceTreeNode>,
202 out: &mut Vec<Span>,
203 nodes_visited: &mut usize,
204 doc_offset: &mut usize,
205 doc_lf: &mut Option<usize>,
206) {
207 *nodes_visited += 1;
208 match node.as_ref() {
209 PieceTreeNode::Internal { left, right, .. } => {
210 collect_leaves_with_offsets(left, out, nodes_visited, doc_offset, doc_lf);
211 collect_leaves_with_offsets(right, out, nodes_visited, doc_offset, doc_lf);
212 }
213 PieceTreeNode::Leaf {
214 location,
215 offset,
216 bytes,
217 line_feed_cnt,
218 } => {
219 let leaf = LeafData::new(*location, *offset, *bytes, *line_feed_cnt);
220 out.push(Span {
221 leaf,
222 doc_offset: *doc_offset,
223 doc_lf_offset: doc_lf.unwrap_or(0),
224 });
225 *doc_offset += bytes;
226 *doc_lf = match (*doc_lf, line_feed_cnt) {
227 (Some(a), Some(b)) => Some(a + b),
228 _ => None,
229 };
230 }
231 }
232}
233
234#[derive(Clone)]
235struct Span {
236 leaf: LeafData,
237 doc_offset: usize,
238 doc_lf_offset: usize,
239}
240
241fn spans_equal(a: &Span, b: &Span) -> bool {
242 a.leaf.location == b.leaf.location
243 && a.leaf.offset == b.leaf.offset
244 && a.leaf.bytes == b.leaf.bytes
245}
246
247fn span_slices_equal(a: &[Span], b: &[Span]) -> bool {
248 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| spans_equal(x, y))
249}
250
251fn normalize_spans(spans: Vec<Span>) -> Vec<Span> {
252 if spans.is_empty() {
253 return spans;
254 }
255
256 let mut it = spans.into_iter();
257 let mut current = it.next().unwrap();
258 let mut normalized = Vec::new();
259
260 for span in it {
261 let contiguous = current.leaf.location == span.leaf.location
262 && current.leaf.offset + current.leaf.bytes == span.leaf.offset
263 && current.doc_offset + current.leaf.bytes == span.doc_offset;
264 if contiguous {
265 current.leaf.bytes += span.leaf.bytes;
266 current.leaf.line_feed_cnt = match (current.leaf.line_feed_cnt, span.leaf.line_feed_cnt)
267 {
268 (Some(a), Some(b)) => Some(a + b),
269 _ => None,
270 };
271 } else {
272 normalized.push(current);
273 current = span;
274 }
275 }
276
277 normalized.push(current);
278 normalized
279}
280
281fn common_prefix_bytes(before: &[Span], after: &[Span]) -> usize {
282 let mut b_idx = 0;
283 let mut a_idx = 0;
284 let mut b_off = 0;
285 let mut a_off = 0;
286 let mut consumed = 0;
287
288 while b_idx < before.len() && a_idx < after.len() {
289 let b_span = &before[b_idx];
290 let a_span = &after[a_idx];
291 let b = &b_span.leaf;
292 let a = &a_span.leaf;
293
294 let b_pos = b.offset + b_off;
295 let a_pos = a.offset + a_off;
296
297 if b.location == a.location
300 && b_pos == a_pos
301 && (b_span.doc_offset + b_off) == (a_span.doc_offset + a_off)
302 {
303 let b_rem = b.bytes - b_off;
304 let a_rem = a.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.bytes {
312 b_idx += 1;
313 b_off = 0;
314 }
315 if a_off == a.bytes {
316 a_idx += 1;
317 a_off = 0;
318 }
319 } else {
320 break;
321 }
322 }
323
324 consumed
325}
326
327fn common_suffix_bytes(before: &[Span], after: &[Span], prefix_bytes: usize) -> usize {
328 let total_before = before
329 .last()
330 .map(|s| s.doc_offset + s.leaf.bytes)
331 .unwrap_or(0);
332 let total_after = after
333 .last()
334 .map(|s| s.doc_offset + s.leaf.bytes)
335 .unwrap_or(0);
336
337 let mut b_idx: isize = before.len() as isize - 1;
338 let mut a_idx: isize = after.len() as isize - 1;
339 let mut b_off = 0;
340 let mut a_off = 0;
341 let mut consumed = 0;
342
343 while b_idx >= 0
344 && a_idx >= 0
345 && (total_before - consumed) > prefix_bytes
346 && (total_after - consumed) > prefix_bytes
347 {
348 let b_span = &before[b_idx as usize];
349 let a_span = &after[a_idx as usize];
350 let b_leaf = &b_span.leaf;
351 let a_leaf = &a_span.leaf;
352
353 let b_pos = b_leaf.offset + b_leaf.bytes - b_off;
354 let a_pos = a_leaf.offset + a_leaf.bytes - a_off;
355
356 if b_leaf.location == a_leaf.location && b_pos == a_pos {
360 let b_rem = b_leaf.bytes - b_off;
361 let a_rem = a_leaf.bytes - a_off;
362 let take = b_rem.min(a_rem);
363
364 consumed += take;
365 b_off += take;
366 a_off += take;
367
368 if b_off == b_leaf.bytes {
369 b_idx -= 1;
370 b_off = 0;
371 }
372 if a_off == a_leaf.bytes {
373 a_idx -= 1;
374 a_off = 0;
375 }
376 } else {
377 break;
378 }
379 }
380
381 consumed.min(total_after.saturating_sub(prefix_bytes))
382}
383
384fn collect_diff_ranges(
385 before: &[Span],
386 after: &[Span],
387 prefix: usize,
388 suffix: usize,
389) -> Vec<Range<usize>> {
390 let mut ranges = Vec::new();
391 let mut b_idx = 0;
392 let mut a_idx = 0;
393 let mut b_off = 0;
394 let mut a_off = 0;
395 let mut matched_prefix = 0;
396
397 while matched_prefix < prefix && b_idx < before.len() && a_idx < after.len() {
399 let b = &before[b_idx].leaf;
400 let a = &after[a_idx].leaf;
401 let b_rem = b.bytes - b_off;
402 let a_rem = a.bytes - a_off;
403 let take = b_rem.min(a_rem).min(prefix - matched_prefix);
404 matched_prefix += take;
405 b_off += take;
406 a_off += take;
407 if b_off == b.bytes {
408 b_idx += 1;
409 b_off = 0;
410 }
411 if a_off == a.bytes {
412 a_idx += 1;
413 a_off = 0;
414 }
415 }
416
417 let doc_end = after
419 .last()
420 .map(|s| s.doc_offset + s.leaf.bytes)
421 .unwrap_or(0);
422 let compare_limit = doc_end.saturating_sub(suffix);
423
424 let doc_start = after.first().map(|s| s.doc_offset).unwrap_or(0);
426
427 let mut current_start: Option<usize> = None;
428 let mut current_end: usize = 0;
429
430 while a_idx < after.len() {
431 let a = &after[a_idx];
432 let pos = a.doc_offset + a_off;
433 if pos >= compare_limit {
434 break;
435 }
436
437 if let Some(start) = current_start {
441 if pos > current_end {
442 ranges.push(start..current_end);
443 current_start = None;
444 }
445 }
446
447 let matches = if b_idx < before.len() {
448 let b = &before[b_idx].leaf;
449 let b_pos = b.offset + b_off;
450 let a_pos = a.leaf.offset + a_off;
451 b.location == a.leaf.location && b_pos == a_pos
454 } else {
455 false
456 };
457
458 if matches {
459 if let Some(start) = current_start.take() {
460 ranges.push(start..current_end);
461 }
462
463 let b = &before[b_idx].leaf;
464 let b_rem = b.bytes - b_off;
465 let a_rem = a.leaf.bytes - a_off;
466 let take = b_rem.min(a_rem).min(compare_limit.saturating_sub(pos));
467
468 b_off += take;
469 a_off += take;
470
471 if b_off == b.bytes {
472 b_idx += 1;
473 b_off = 0;
474 }
475 if a_off == a.leaf.bytes {
476 a_idx += 1;
477 a_off = 0;
478 }
479 } else {
480 if current_start.is_none() {
481 current_start = Some(pos);
482 }
483 let take = (a.leaf.bytes - a_off).min(compare_limit.saturating_sub(pos));
484 current_end = pos + take;
485 a_off += take;
486 if a_off == a.leaf.bytes {
487 a_idx += 1;
488 a_off = 0;
489 }
490 }
491 }
492
493 if let Some(start) = current_start {
494 ranges.push(start..current_end);
495 }
496
497 while a_idx < after.len() {
499 let start = after[a_idx].doc_offset + a_off;
500 if start >= compare_limit {
501 break;
502 }
503 let end = (after[a_idx].doc_offset + after[a_idx].leaf.bytes).min(compare_limit);
504 ranges.push(start..end);
505 a_idx += 1;
506 a_off = 0;
507 }
508
509 if ranges.is_empty() {
510 ranges.push((doc_start + prefix)..compare_limit);
513 }
514
515 ranges
516}
517
518fn count_lines_in_range(
519 spans: &[Span],
520 start: usize,
521 end: usize,
522 line_counter: &dyn Fn(&LeafData, usize, usize) -> Option<usize>,
523) -> Option<usize> {
524 if start >= end {
525 return Some(0);
526 }
527
528 let mut line_feeds = 0usize;
529 for span in spans {
530 let span_start = span.doc_offset;
531 let span_end = span_start + span.leaf.bytes;
532
533 if end <= span_start {
534 break;
535 }
536 if start >= span_end {
537 continue;
538 }
539
540 let overlap_start = start.max(span_start);
541 let overlap_end = end.min(span_end);
542 let local_start = overlap_start - span_start;
543 let len = overlap_end - overlap_start;
544
545 let chunk_lines = line_counter(&span.leaf, local_start, len)?;
546 line_feeds += chunk_lines;
547 }
548
549 Some(line_feeds)
550}
551
552fn line_ranges(
553 after_spans: &[Span],
554 byte_ranges: &[Range<usize>],
555 line_counter: &dyn Fn(&LeafData, usize, usize) -> Option<usize>,
556) -> Option<Vec<Range<usize>>> {
557 let mut accum = Vec::with_capacity(byte_ranges.len());
558 for range in byte_ranges {
559 let span = after_spans
562 .iter()
563 .find(|s| range.start >= s.doc_offset && range.start <= s.doc_offset + s.leaf.bytes)?;
564
565 let offset_into_span = (range.start - span.doc_offset).min(span.leaf.bytes);
566 let lf_at_span_start = span.doc_lf_offset;
567 let lf_to_range_start = line_counter(&span.leaf, 0, offset_into_span)?;
568 let start_line = lf_at_span_start + lf_to_range_start;
569
570 let lf_in_range = count_lines_in_range(after_spans, range.start, range.end, line_counter)?;
571
572 let end_line = if range.start == range.end {
573 start_line + 1
574 } else {
575 start_line + lf_in_range + 1
576 };
577 accum.push(start_line..end_line);
578 }
579
580 Some(accum)
581}
582
583#[cfg(test)]
584#[allow(clippy::single_range_in_vec_init)]
585mod tests {
586 use super::*;
587 use crate::model::piece_tree::BufferLocation;
588
589 fn sum_bytes(leaves: &[LeafData]) -> usize {
590 leaves.iter().map(|leaf| leaf.bytes).sum()
591 }
592
593 fn leaf(loc: BufferLocation, offset: usize, bytes: usize, lfs: Option<usize>) -> LeafData {
594 LeafData::new(loc, offset, bytes, lfs)
595 }
596
597 fn build(leaves: &[LeafData]) -> Arc<PieceTreeNode> {
599 if leaves.is_empty() {
600 return Arc::new(PieceTreeNode::Leaf {
601 location: BufferLocation::Stored(0),
602 offset: 0,
603 bytes: 0,
604 line_feed_cnt: Some(0),
605 });
606 }
607 if leaves.len() == 1 {
608 let l = leaves[0];
609 return Arc::new(PieceTreeNode::Leaf {
610 location: l.location,
611 offset: l.offset,
612 bytes: l.bytes,
613 line_feed_cnt: l.line_feed_cnt,
614 });
615 }
616
617 let mid = leaves.len() / 2;
618 let left = build(&leaves[..mid]);
619 let right = build(&leaves[mid..]);
620
621 Arc::new(PieceTreeNode::Internal {
622 left_bytes: sum_bytes(&leaves[..mid]),
623 lf_left: leaves[..mid]
624 .iter()
625 .map(|l| l.line_feed_cnt)
626 .try_fold(0usize, |acc, v| v.map(|b| acc + b)),
627 left,
628 right,
629 })
630 }
631
632 fn count_line_feeds(leaf: &LeafData, start: usize, len: usize) -> Option<usize> {
633 if len == 0 {
634 return Some(0);
635 }
636 if start == 0 && len == leaf.bytes {
638 return leaf.line_feed_cnt;
639 }
640 None
641 }
642
643 #[test]
644 fn detects_identical_trees() {
645 let leaves = vec![leaf(BufferLocation::Stored(0), 0, 10, Some(0))];
646 let before = build(&leaves);
647 let after = build(&leaves);
648
649 let diff = diff_piece_trees(&before, &after, &count_line_feeds);
650 assert!(diff.equal);
651 assert!(diff.byte_ranges.is_empty());
652 assert_eq!(diff.line_ranges, Some(Vec::new()));
653 }
654
655 #[test]
656 fn detects_single_line_change() {
657 let before = build(&[leaf(BufferLocation::Stored(0), 0, 5, Some(0))]);
658 let after = build(&[leaf(BufferLocation::Added(1), 0, 5, Some(0))]);
659
660 let diff = diff_piece_trees(&before, &after, &count_line_feeds);
661 assert!(!diff.equal);
662 assert_eq!(diff.byte_ranges, vec![0..5]);
663 assert_eq!(diff.line_ranges, Some(vec![0..1])); }
665
666 #[test]
667 fn tracks_newlines_in_changed_span() {
668 let before = build(&[leaf(BufferLocation::Stored(0), 0, 6, Some(0))]);
669 let after = build(&[leaf(BufferLocation::Added(1), 0, 6, Some(1))]); let diff = diff_piece_trees(&before, &after, &count_line_feeds);
672 assert!(!diff.equal);
673 assert_eq!(diff.byte_ranges, vec![0..6]);
674 assert_eq!(diff.line_ranges, Some(vec![0..2])); }
676
677 #[test]
678 fn handles_deletion_by_marking_anchor_line() {
679 let before = build(&[
680 leaf(BufferLocation::Stored(0), 0, 6, Some(1)), leaf(BufferLocation::Stored(0), 6, 4, Some(0)), ]);
683 let after = build(&[leaf(BufferLocation::Stored(0), 0, 6, Some(1))]);
684
685 let diff = diff_piece_trees(&before, &after, &count_line_feeds);
686 assert!(!diff.equal);
687 assert_eq!(diff.byte_ranges, vec![6..6]); assert_eq!(diff.line_ranges, Some(vec![1..2])); }
690
691 #[test]
694 fn diff_after_path_copy_insert_at_eof() {
695 use crate::model::piece_tree::{PieceTree, StringBuffer};
696
697 let chunk_size = 1000;
699 let total = 10_000;
700 let content: Vec<u8> = (0..total)
702 .map(|i| if i % 100 == 99 { b'\n' } else { b'A' })
703 .collect();
704 let buf = StringBuffer::new_loaded(0, content, false);
705
706 let mut saved_tree = PieceTree::new(BufferLocation::Stored(0), 0, total, None);
707 saved_tree.split_leaves_to_chunk_size(chunk_size);
708 let lf_updates: Vec<(usize, usize)> = (0..10).map(|i| (i, 10)).collect();
710 saved_tree.update_leaf_line_feeds(&lf_updates);
711 let saved_root = saved_tree.root();
712
713 let mut after_tree = saved_tree;
715 let insert_offset = total - 100; let insert_buf = StringBuffer::new_loaded(1, b"HELLO".to_vec(), false);
717 after_tree.insert(
718 insert_offset,
719 BufferLocation::Added(1),
720 0,
721 5,
722 Some(0),
723 &[buf, insert_buf],
724 );
725 let after_root = after_tree.root();
726
727 let diff = diff_piece_trees(&saved_root, &after_root, &count_line_feeds);
728 assert!(!diff.equal);
729
730 assert!(
731 diff.byte_ranges[0].start >= total - 200,
732 "byte_ranges should be document-absolute (near EOF): got {:?}, expected near {}",
733 diff.byte_ranges,
734 insert_offset,
735 );
736
737 if let Some(ref lr) = diff.line_ranges {
738 assert!(
739 lr[0].start >= 90,
740 "line_ranges should be document-absolute (>= 90), got {:?}",
741 lr
742 );
743 }
744 }
745
746 #[test]
749 fn diff_after_rebalance_matches_path_copy_diff() {
750 use crate::model::piece_tree::{PieceTree, StringBuffer};
751
752 let chunk_size = 1000;
753 let total = 10_000;
754 let content: Vec<u8> = (0..total)
755 .map(|i| if i % 100 == 99 { b'\n' } else { b'A' })
756 .collect();
757 let buf = StringBuffer::new_loaded(0, content, false);
758
759 let mut saved_tree = PieceTree::new(BufferLocation::Stored(0), 0, total, None);
760 saved_tree.split_leaves_to_chunk_size(chunk_size);
761 let lf_updates: Vec<(usize, usize)> = (0..10).map(|i| (i, 10)).collect();
762 saved_tree.update_leaf_line_feeds(&lf_updates);
763 let saved_root = saved_tree.root();
764
765 let mut after_tree = saved_tree;
767 let insert_buf = StringBuffer::new_loaded(1, b"HELLO".to_vec(), false);
768 after_tree.insert(
769 total - 100,
770 BufferLocation::Added(1),
771 0,
772 5,
773 Some(0),
774 &[buf.clone(), insert_buf.clone()],
775 );
776
777 let diff_shared = diff_piece_trees(&saved_root, &after_tree.root(), &count_line_feeds);
779
780 after_tree.rebalance();
782 let diff_rebalanced = diff_piece_trees(&saved_root, &after_tree.root(), &count_line_feeds);
783
784 assert!(!diff_shared.equal);
785 assert!(!diff_rebalanced.equal);
786 assert_eq!(
787 diff_shared.byte_ranges, diff_rebalanced.byte_ranges,
788 "byte_ranges should be identical whether or not Arc sharing exists"
789 );
790 assert_eq!(
791 diff_shared.line_ranges, diff_rebalanced.line_ranges,
792 "line_ranges should be identical whether or not Arc sharing exists"
793 );
794 }
795
796 #[test]
797 fn tolerates_split_leaves_with_same_content_prefix() {
798 let before = build(&[leaf(BufferLocation::Stored(0), 0, 100, Some(1))]);
799 let after = build(&[
800 leaf(BufferLocation::Stored(0), 0, 50, Some(0)),
801 leaf(BufferLocation::Added(1), 0, 10, Some(0)),
802 leaf(BufferLocation::Stored(0), 50, 50, Some(1)),
803 ]);
804
805 let diff = diff_piece_trees(&before, &after, &count_line_feeds);
806 assert!(!diff.equal);
807 assert_eq!(diff.byte_ranges, vec![50..60]);
809 }
810
811 #[test]
812 fn diff_with_disjoint_changes_reports_correct_line_numbers() {
813 let leaf1_before = leaf(BufferLocation::Stored(0), 0, 10, Some(0));
814 let leaf2 = leaf(BufferLocation::Stored(0), 10, 10, Some(1)); let leaf3_before = leaf(BufferLocation::Stored(0), 20, 10, Some(0));
816
817 let leaf1_after = leaf(BufferLocation::Added(1), 0, 10, Some(0));
818 let leaf3_after = leaf(BufferLocation::Added(1), 10, 10, Some(0));
819
820 let leaf2_arc = Arc::new(PieceTreeNode::Leaf {
822 location: leaf2.location,
823 offset: leaf2.offset,
824 bytes: leaf2.bytes,
825 line_feed_cnt: leaf2.line_feed_cnt,
826 });
827
828 let before = Arc::new(PieceTreeNode::Internal {
829 left_bytes: 10,
830 lf_left: Some(0),
831 left: Arc::new(PieceTreeNode::Leaf {
832 location: leaf1_before.location,
833 offset: leaf1_before.offset,
834 bytes: leaf1_before.bytes,
835 line_feed_cnt: leaf1_before.line_feed_cnt,
836 }),
837 right: Arc::new(PieceTreeNode::Internal {
838 left_bytes: 10,
839 lf_left: Some(1),
840 left: Arc::clone(&leaf2_arc),
841 right: Arc::new(PieceTreeNode::Leaf {
842 location: leaf3_before.location,
843 offset: leaf3_before.offset,
844 bytes: leaf3_before.bytes,
845 line_feed_cnt: leaf3_before.line_feed_cnt,
846 }),
847 }),
848 });
849
850 let after = Arc::new(PieceTreeNode::Internal {
851 left_bytes: 10,
852 lf_left: Some(0),
853 left: Arc::new(PieceTreeNode::Leaf {
854 location: leaf1_after.location,
855 offset: leaf1_after.offset,
856 bytes: leaf1_after.bytes,
857 line_feed_cnt: leaf1_after.line_feed_cnt,
858 }),
859 right: Arc::new(PieceTreeNode::Internal {
860 left_bytes: 10,
861 lf_left: Some(1),
862 left: Arc::clone(&leaf2_arc), right: Arc::new(PieceTreeNode::Leaf {
864 location: leaf3_after.location,
865 offset: leaf3_after.offset,
866 bytes: leaf3_after.bytes,
867 line_feed_cnt: leaf3_after.line_feed_cnt,
868 }),
869 }),
870 });
871
872 let diff = diff_piece_trees(&before, &after, &count_line_feeds);
873
874 assert_eq!(diff.byte_ranges, vec![0..10, 20..30]);
875 assert_eq!(
878 diff.line_ranges,
879 Some(vec![0..1, 1..2]),
880 "Change after a skipped subtree with line feeds should have correct line number"
881 );
882 }
883}