1use std::cell::RefCell;
2use std::cmp::{max, Ordering};
3use std::collections::HashMap;
4use std::mem;
5use std::rc::{Rc, Weak};
6
7pub type MarkerId = u64;
9
10#[derive(Debug, Clone, PartialEq)]
15pub struct Interval {
16 pub start: u64,
17 pub end: u64,
18}
19
20#[derive(Debug, Clone, PartialEq)]
22pub enum MarkerType {
23 Position,
25 LineAnchor {
27 estimated_line: usize,
28 confidence: AnchorConfidence,
29 },
30}
31
32#[derive(Debug, Clone, PartialEq)]
34pub enum AnchorConfidence {
35 Exact,
37 Estimated,
39 Relative(MarkerId),
41}
42
43#[derive(Debug, Clone, PartialEq)]
44pub struct Marker {
45 pub id: MarkerId,
46 pub interval: Interval,
47 pub marker_type: MarkerType,
48 pub right_gravity: bool,
59}
60
61type NodePtr = Option<Rc<RefCell<Node>>>;
63type WeakNodePtr = Weak<RefCell<Node>>;
65
66#[derive(Debug)]
68struct Node {
69 pub marker: Marker,
70
71 pub height: i32,
73 pub max_end: u64,
75 pub lazy_delta: i64,
77
78 pub parent: WeakNodePtr,
79 pub left: NodePtr,
80 pub right: NodePtr,
81}
82
83#[derive(Debug, Default)]
85pub struct IntervalTree {
86 root: NodePtr,
87 next_id: u64,
88 marker_map: HashMap<MarkerId, Rc<RefCell<Node>>>,
90}
91
92impl Node {
97 fn new(marker: Marker, parent: WeakNodePtr) -> Rc<RefCell<Self>> {
98 let max_end_val = marker.interval.end;
100
101 Rc::new(RefCell::new(Node {
102 marker,
103 height: 1,
104 max_end: max_end_val,
105 lazy_delta: 0,
106 parent,
107 left: None,
108 right: None,
109 }))
110 }
111
112 fn height(node: &NodePtr) -> i32 {
114 node.as_ref().map_or(0, |n| n.borrow().height)
115 }
116
117 fn balance_factor(node: &Rc<RefCell<Self>>) -> i32 {
119 let n = node.borrow();
120 Self::height(&n.left) - Self::height(&n.right)
121 }
122
123 fn push_delta(node_rc: &Rc<RefCell<Self>>) {
125 let mut node = node_rc.borrow_mut();
126 if node.lazy_delta == 0 {
127 return;
128 }
129
130 let delta = node.lazy_delta;
131
132 node.marker.interval.start = (node.marker.interval.start as i64 + delta) as u64;
134 node.marker.interval.end = (node.marker.interval.end as i64 + delta) as u64;
135
136 if let Some(ref left) = node.left {
138 left.borrow_mut().lazy_delta += delta;
139 }
140 if let Some(ref right) = node.right {
141 right.borrow_mut().lazy_delta += delta;
142 }
143
144 node.lazy_delta = 0;
145
146 let max_l = node.left.as_ref().map_or(0, |l| l.borrow().max_end);
148 let max_r = node.right.as_ref().map_or(0, |r| r.borrow().max_end);
149 node.max_end = max(node.marker.interval.end, max(max_l, max_r));
150 }
151
152 fn update_stats(node: &Rc<RefCell<Self>>) {
154 let mut n = node.borrow_mut();
155 let height_l = Self::height(&n.left);
156 let height_r = Self::height(&n.right);
157
158 n.height = 1 + max(height_l, height_r);
159
160 let max_l = n.left.as_ref().map_or(0, |l| l.borrow().max_end);
161 let max_r = n.right.as_ref().map_or(0, |r| r.borrow().max_end);
162 n.max_end = max(n.marker.interval.end, max(max_l, max_r));
163 }
164}
165
166impl IntervalTree {
171 pub fn new() -> Self {
172 Self::default()
173 }
174
175 pub fn insert(&mut self, start: u64, end: u64) -> MarkerId {
177 self.insert_with_type(start, end, MarkerType::Position)
178 }
179
180 pub fn insert_left_gravity(&mut self, start: u64, end: u64) -> MarkerId {
184 self.insert_full(start, end, MarkerType::Position, false)
185 }
186
187 fn insert_with_id(
190 &mut self,
191 id: MarkerId,
192 start: u64,
193 end: u64,
194 marker_type: MarkerType,
195 right_gravity: bool,
196 ) {
197 debug_assert!(
198 id < self.next_id,
199 "insert_with_id: id {} must be < next_id {}",
200 id,
201 self.next_id
202 );
203 debug_assert!(
204 !self.marker_map.contains_key(&id),
205 "insert_with_id: id {} already in use",
206 id
207 );
208 let marker = Marker {
209 id,
210 interval: Interval { start, end },
211 marker_type,
212 right_gravity,
213 };
214
215 let new_node = Node::new(marker.clone(), Weak::new());
216 self.root = Self::insert_recursive(self.root.take(), new_node.clone());
217 self.marker_map.insert(id, new_node);
218 }
219
220 pub fn insert_with_type(&mut self, start: u64, end: u64, marker_type: MarkerType) -> MarkerId {
222 self.insert_full(start, end, marker_type, true)
223 }
224
225 fn insert_full(
227 &mut self,
228 start: u64,
229 end: u64,
230 marker_type: MarkerType,
231 right_gravity: bool,
232 ) -> MarkerId {
233 let id = self.next_id;
234 self.next_id += 1;
235 let marker = Marker {
236 id,
237 interval: Interval { start, end },
238 marker_type,
239 right_gravity,
240 };
241
242 let new_node = Node::new(marker.clone(), Weak::new());
243 self.root = Self::insert_recursive(self.root.take(), new_node.clone());
244
245 self.marker_map.insert(id, new_node);
246 id
247 }
248
249 pub fn insert_line_anchor(
251 &mut self,
252 start: u64,
253 end: u64,
254 estimated_line: usize,
255 confidence: AnchorConfidence,
256 ) -> MarkerId {
257 self.insert_with_type(
258 start,
259 end,
260 MarkerType::LineAnchor {
261 estimated_line,
262 confidence,
263 },
264 )
265 }
266
267 pub fn get_position(&self, id: MarkerId) -> Option<(u64, u64)> {
269 let node_rc = self.marker_map.get(&id)?;
270 let mut node_opt = Some(Rc::clone(node_rc));
271 let mut current_delta: i64 = 0;
272
273 while let Some(current_rc) = node_opt {
275 let current = current_rc.borrow();
276
277 current_delta += current.lazy_delta;
279
280 node_opt = current.parent.upgrade();
282 }
283
284 let raw_marker = node_rc.borrow().marker.interval.clone();
285
286 let start = (raw_marker.start as i64 + current_delta) as u64;
287 let end = (raw_marker.end as i64 + current_delta) as u64;
288
289 Some((start, end))
290 }
291
292 pub fn delete(&mut self, id: MarkerId) -> bool {
302 let node_rc = match self.marker_map.get(&id) {
303 Some(n) => Rc::clone(n),
304 None => return false,
305 };
306
307 Self::push_to_node(&node_rc);
311
312 let two_children = {
316 let n = node_rc.borrow();
317 n.left.is_some() && n.right.is_some()
318 };
319
320 let remove_rc = if two_children {
321 let right = node_rc.borrow().right.as_ref().unwrap().clone();
322 let succ = Self::min_node(&right);
323 let succ_id = succ.borrow().marker.id;
324
325 mem::swap(
326 &mut node_rc.borrow_mut().marker,
327 &mut succ.borrow_mut().marker,
328 );
329 self.marker_map.insert(succ_id, Rc::clone(&node_rc));
331 succ
332 } else {
333 Rc::clone(&node_rc)
334 };
335
336 let child = {
339 let mut rb = remove_rc.borrow_mut();
340 rb.left.take().or_else(|| rb.right.take())
341 };
342 let parent = remove_rc.borrow().parent.upgrade();
343 if let Some(ref ch) = child {
344 ch.borrow_mut().parent = remove_rc.borrow().parent.clone();
345 }
346 match parent {
347 None => self.root = child,
348 Some(ref p) => {
349 let is_left = p
350 .borrow()
351 .left
352 .as_ref()
353 .is_some_and(|l| Rc::ptr_eq(l, &remove_rc));
354 if is_left {
355 p.borrow_mut().left = child;
356 } else {
357 p.borrow_mut().right = child;
358 }
359 }
360 }
361
362 self.marker_map.remove(&id);
363
364 self.rebalance_upward(parent);
366 true
367 }
368
369 fn push_to_node(node_rc: &Rc<RefCell<Node>>) {
373 let mut path: Vec<Rc<RefCell<Node>>> = Vec::new();
374 let mut cur = Some(Rc::clone(node_rc));
375 while let Some(c) = cur {
376 let parent = c.borrow().parent.upgrade();
377 path.push(c);
378 cur = parent;
379 }
380 for n in path.into_iter().rev() {
381 Node::push_delta(&n);
382 }
383 }
384
385 fn rebalance_upward(&mut self, start: NodePtr) {
388 let mut cur = start;
389 while let Some(n) = cur {
390 let parent = n.borrow().parent.upgrade();
391 Node::update_stats(&n);
392 let new_sub = Self::balance(Rc::clone(&n));
393 match &parent {
394 None => {
395 if let Some(ref ns) = new_sub {
396 ns.borrow_mut().parent = Weak::new();
397 }
398 self.root = new_sub;
399 }
400 Some(p) => {
401 let is_left = p.borrow().left.as_ref().is_some_and(|l| Rc::ptr_eq(l, &n));
402 if let Some(ref ns) = new_sub {
403 ns.borrow_mut().parent = Rc::downgrade(p);
404 }
405 if is_left {
406 p.borrow_mut().left = new_sub;
407 } else {
408 p.borrow_mut().right = new_sub;
409 }
410 }
411 }
412 cur = parent;
413 }
414 }
415
416 pub fn set_position(&mut self, id: MarkerId, new_start: u64, new_end: u64) -> bool {
421 let (marker_type, right_gravity) = match self.get_marker(id) {
423 Some(m) => (m.marker_type, m.right_gravity),
424 None => return false,
425 };
426
427 if !self.delete(id) {
429 return false;
430 }
431
432 self.insert_with_id(id, new_start, new_end, marker_type, right_gravity);
434 true
435 }
436
437 pub fn adjust_for_edit(&mut self, pos: u64, delta: i64) {
440 if delta > 0 {
456 let mut at_pos: Vec<(MarkerId, u64, bool, MarkerType)> = Vec::new();
462 Self::collect_starts_at(&self.root, 0, pos, &mut at_pos);
463
464 let has_mover = at_pos.iter().any(|(_, _, rg, _)| *rg);
465 let stayers: Vec<(MarkerId, u64, bool, MarkerType)> =
466 at_pos.into_iter().filter(|(_, _, rg, _)| !*rg).collect();
467
468 if has_mover && !stayers.is_empty() {
469 for (id, _, _, _) in &stayers {
470 self.delete(*id);
471 }
472 Self::adjust_recursive(&mut self.root, pos, delta);
473 for (id, end, _rg, mtype) in stayers {
474 let new_end = if end > pos {
478 (end as i64 + delta) as u64
479 } else {
480 end
481 };
482 self.insert_with_id(id, pos, new_end, mtype, false);
483 }
484 return;
485 }
486 }
487
488 Self::adjust_recursive(&mut self.root, pos, delta);
489 }
490
491 fn collect_starts_at(
496 node: &NodePtr,
497 acc_delta: i64,
498 pos: u64,
499 out: &mut Vec<(MarkerId, u64, bool, MarkerType)>,
500 ) {
501 let Some(n) = node else { return };
502 let nb = n.borrow();
503 let d = acc_delta + nb.lazy_delta;
504 let start = (nb.marker.interval.start as i64 + d) as u64;
505 match pos.cmp(&start) {
506 Ordering::Less => Self::collect_starts_at(&nb.left, d, pos, out),
507 Ordering::Greater => Self::collect_starts_at(&nb.right, d, pos, out),
508 Ordering::Equal => {
509 let end = (nb.marker.interval.end as i64 + d) as u64;
510 out.push((
511 nb.marker.id,
512 end,
513 nb.marker.right_gravity,
514 nb.marker.marker_type.clone(),
515 ));
516 Self::collect_starts_at(&nb.left, d, pos, out);
519 Self::collect_starts_at(&nb.right, d, pos, out);
520 }
521 }
522 }
523
524 pub fn query(&self, query_start: u64, query_end: u64) -> Vec<Marker> {
527 let mut results = Vec::new();
528 Self::query_recursive(&self.root, query_start, query_end, &mut results);
529 results
530 }
531
532 pub fn get_marker(&self, id: MarkerId) -> Option<Marker> {
534 let node_rc = self.marker_map.get(&id)?;
535 Some(node_rc.borrow().marker.clone())
536 }
537
538 pub fn update_line_anchor(
540 &mut self,
541 id: MarkerId,
542 estimated_line: usize,
543 confidence: AnchorConfidence,
544 ) -> bool {
545 if let Some(node_rc) = self.marker_map.get(&id) {
546 let mut node = node_rc.borrow_mut();
547 node.marker.marker_type = MarkerType::LineAnchor {
548 estimated_line,
549 confidence,
550 };
551 true
552 } else {
553 false
554 }
555 }
556
557 pub fn query_line_anchors(&self, query_start: u64, query_end: u64) -> Vec<Marker> {
559 self.query(query_start, query_end)
560 .into_iter()
561 .filter(|m| matches!(m.marker_type, MarkerType::LineAnchor { .. }))
562 .collect()
563 }
564}
565
566impl IntervalTree {
571 fn insert_recursive(root: NodePtr, new_node: Rc<RefCell<Node>>) -> NodePtr {
573 let root = match root {
575 Some(r) => r,
576 None => return Some(new_node),
577 };
578
579 Node::push_delta(&root);
580
581 let (start, id) = (
582 new_node.borrow().marker.interval.start,
583 new_node.borrow().marker.id,
584 );
585
586 let mut root_mut = root.borrow_mut();
587 let (root_start, root_id) = (root_mut.marker.interval.start, root_mut.marker.id);
588
589 if start < root_start || (start == root_start && id < root_id) {
590 root_mut.left = Self::insert_recursive(root_mut.left.take(), Rc::clone(&new_node));
591 root_mut.left.as_ref().unwrap().borrow_mut().parent = Rc::downgrade(&root);
592 } else {
593 root_mut.right = Self::insert_recursive(root_mut.right.take(), Rc::clone(&new_node));
594 root_mut.right.as_ref().unwrap().borrow_mut().parent = Rc::downgrade(&root);
595 }
596
597 drop(root_mut);
598 Node::update_stats(&root);
599 Self::balance(root)
600 }
601
602 fn min_node(node_rc: &Rc<RefCell<Node>>) -> Rc<RefCell<Node>> {
604 let mut current = Rc::clone(node_rc);
605 loop {
606 Node::push_delta(¤t);
607
608 let next_left_opt = current.borrow().left.clone();
611
612 if let Some(next) = next_left_opt {
613 current = next;
614 } else {
615 break current;
616 }
617 }
618 }
619
620 fn adjust_recursive(node_opt: &mut NodePtr, pos: u64, delta: i64) {
622 let node_rc = match node_opt {
623 Some(n) => n,
624 None => return,
625 };
626
627 Node::push_delta(node_rc);
628
629 let mut node = node_rc.borrow_mut();
630 let start = node.marker.interval.start;
631 let left_gravity = !node.marker.right_gravity;
636
637 if pos <= start {
638 let stay_put = left_gravity && delta > 0 && pos == start;
644
645 if !stay_put {
647 if delta < 0 {
648 node.marker.interval.start = (start as i64 + delta).max(pos as i64) as u64;
649 } else {
650 node.marker.interval.start = (start as i64 + delta) as u64;
651 }
652 }
653
654 if delta < 0 || pos == start {
663 Self::adjust_recursive(&mut node.right, pos, delta);
664 } else if let Some(ref right) = node.right {
665 right.borrow_mut().lazy_delta += delta;
666 }
667
668 Self::adjust_recursive(&mut node.left, pos, delta);
670 } else {
671 Self::adjust_recursive(&mut node.right, pos, delta);
676 }
677
678 let end = node.marker.interval.end;
682 let shift_end = if left_gravity && delta > 0 {
683 end > pos
684 } else {
685 end >= pos
686 };
687 if shift_end {
688 node.marker.interval.end =
689 (end as i64 + delta).max(node.marker.interval.start as i64) as u64;
690 }
691
692 drop(node);
693 Node::update_stats(node_rc);
694 }
695
696 fn query_recursive(
698 node_opt: &NodePtr,
699 query_start: u64,
700 query_end: u64,
701 results: &mut Vec<Marker>,
702 ) {
703 let node_rc = match node_opt {
704 Some(n) => n,
705 None => return,
706 };
707
708 Node::push_delta(node_rc);
709 let node = node_rc.borrow();
710
711 let i = &node.marker.interval;
712 if i.start <= query_end && i.end >= query_start {
713 results.push(node.marker.clone());
714 }
715
716 if node.left.is_some() && node.left.as_ref().unwrap().borrow().max_end >= query_start {
717 Self::query_recursive(&node.left, query_start, query_end, results);
718 }
719
720 if node.right.is_some() && node.marker.interval.start <= query_end {
721 Self::query_recursive(&node.right, query_start, query_end, results);
722 }
723 }
724
725 fn balance(node: Rc<RefCell<Node>>) -> NodePtr {
728 let bf = Node::balance_factor(&node);
729
730 if bf > 1 {
731 let left_rc = node.borrow().left.as_ref().unwrap().clone();
732 if Node::balance_factor(&left_rc) < 0 {
733 let left_child = node.borrow_mut().left.take().unwrap();
735 node.borrow_mut().left = Self::rotate_left(left_child);
736 }
737 Self::rotate_right(node)
738 } else if bf < -1 {
739 let right_rc = node.borrow().right.as_ref().unwrap().clone();
740 if Node::balance_factor(&right_rc) > 0 {
741 let right_child = node.borrow_mut().right.take().unwrap();
743 node.borrow_mut().right = Self::rotate_right(right_child);
744 }
745 Self::rotate_left(node)
746 } else {
747 Some(node)
748 }
749 }
750
751 fn rotate_left(node_rc: Rc<RefCell<Node>>) -> NodePtr {
752 Node::push_delta(&node_rc);
753 let x_rc = node_rc.borrow_mut().right.take().unwrap();
754 Node::push_delta(&x_rc);
755
756 let mut y = node_rc.borrow_mut();
757 let mut x = x_rc.borrow_mut();
758
759 y.right = x.left.take();
760 if let Some(ref r) = y.right {
761 r.borrow_mut().parent = Rc::downgrade(&node_rc);
762 }
763 x.left = Some(Rc::clone(&node_rc));
764 x.parent = y.parent.clone();
765 y.parent = Rc::downgrade(&x_rc);
766
767 drop(x);
768 drop(y);
769
770 Node::update_stats(&node_rc);
771 Node::update_stats(&x_rc);
772 Some(x_rc)
773 }
774
775 fn rotate_right(node_rc: Rc<RefCell<Node>>) -> NodePtr {
776 Node::push_delta(&node_rc);
777 let x_rc = node_rc.borrow_mut().left.take().unwrap();
778 Node::push_delta(&x_rc);
779
780 let mut y = node_rc.borrow_mut();
781 let mut x = x_rc.borrow_mut();
782
783 y.left = x.right.take();
784 if let Some(ref l) = y.left {
785 l.borrow_mut().parent = Rc::downgrade(&node_rc);
786 }
787 x.right = Some(Rc::clone(&node_rc));
788 x.parent = y.parent.clone();
789 y.parent = Rc::downgrade(&x_rc);
790
791 drop(x);
792 drop(y);
793
794 Node::update_stats(&node_rc);
795 Node::update_stats(&x_rc);
796 Some(x_rc)
797 }
798}
799
800#[cfg(test)]
801impl IntervalTree {
802 fn debug_dump(&self) -> Vec<(MarkerId, u64, u64, bool)> {
803 let mut out = Vec::new();
804 Self::debug_collect(&self.root, 0, &mut out);
805 out
806 }
807 fn debug_collect(node: &NodePtr, ad: i64, out: &mut Vec<(MarkerId, u64, u64, bool)>) {
808 if let Some(n) = node {
809 let nb = n.borrow();
810 let d = ad + nb.lazy_delta;
811 Self::debug_collect(&nb.left, d, out);
812 out.push((
813 nb.marker.id,
814 (nb.marker.interval.start as i64 + d) as u64,
815 (nb.marker.interval.end as i64 + d) as u64,
816 nb.marker.right_gravity,
817 ));
818 Self::debug_collect(&nb.right, d, out);
819 }
820 }
821}
822
823#[cfg(test)]
824mod tests {
825 use super::*;
826
827 fn insert_marker(tree: &mut IntervalTree, start: u64, end: u64) -> MarkerId {
829 tree.insert(start, end)
830 }
831
832 fn get_pos(tree: &IntervalTree, id: MarkerId) -> (u64, u64) {
834 tree.get_position(id)
835 .unwrap_or_else(|| panic!("Marker ID {} not found.", id))
836 }
837
838 #[test]
841 fn test_left_gravity_marker_stays_on_insert_at_position() {
842 let mut tree = IntervalTree::new();
846 let m = tree.insert_left_gravity(3, 3);
847
848 tree.adjust_for_edit(3, 4);
850
851 assert_eq!(
852 get_pos(&tree, m),
853 (3, 3),
854 "left-gravity marker must stay put when text is inserted at its position"
855 );
856 }
857
858 #[test]
859 fn test_right_gravity_marker_moves_on_insert_at_position() {
860 let mut tree = IntervalTree::new();
863 let m = tree.insert(3, 3);
864
865 tree.adjust_for_edit(3, 4);
866
867 assert_eq!(get_pos(&tree, m), (7, 7));
868 }
869
870 #[test]
871 fn test_left_gravity_marker_still_shifts_on_insert_before() {
872 let mut tree = IntervalTree::new();
875 let m = tree.insert_left_gravity(5, 5);
876
877 tree.adjust_for_edit(2, 3);
878
879 assert_eq!(get_pos(&tree, m), (8, 8));
880 }
881
882 #[test]
883 fn test_search_match_does_not_grow_on_adjacent_insert() {
884 let mut tree = IntervalTree::new();
889 let start = tree.insert(0, 0);
890 let end = tree.insert_left_gravity(3, 3);
891
892 tree.adjust_for_edit(3, 1);
894
895 assert_eq!(get_pos(&tree, start), (0, 0));
896 assert_eq!(
897 get_pos(&tree, end),
898 (3, 3),
899 "highlight end must not extend over text typed after the match"
900 );
901 }
902
903 #[test]
904 fn test_adjacent_matches_keep_independent_gravity_at_shared_boundary() {
905 let mut tree = IntervalTree::new();
910 let m1_start = tree.insert(0, 0);
911 let m1_end = tree.insert_left_gravity(2, 2);
912 let m2_start = tree.insert(2, 2);
913 let m2_end = tree.insert_left_gravity(4, 4);
914
915 tree.adjust_for_edit(2, 3);
917
918 assert_eq!(get_pos(&tree, m1_start), (0, 0));
919 assert_eq!(get_pos(&tree, m1_end), (2, 2), "first match must not grow");
920 assert_eq!(get_pos(&tree, m2_start), (5, 5), "second match shifts");
921 assert_eq!(get_pos(&tree, m2_end), (7, 7));
922 }
923
924 #[test]
925 fn test_gravity_reversal_preserves_bst_for_later_edits() {
926 let mut tree = IntervalTree::new();
934
935 let right = tree.insert(10, 10); let left = tree.insert_left_gravity(11, 11); tree.adjust_for_edit(10, -1);
942 assert_eq!(get_pos(&tree, right), (10, 10));
943 assert_eq!(get_pos(&tree, left), (10, 10));
944
945 tree.adjust_for_edit(10, 5);
948 assert_eq!(get_pos(&tree, left), (10, 10), "left-gravity marker stays");
949 assert_eq!(
950 get_pos(&tree, right),
951 (15, 15),
952 "right-gravity marker moves"
953 );
954
955 tree.adjust_for_edit(12, -1);
959 assert_eq!(
960 get_pos(&tree, right),
961 (14, 14),
962 "later deletion must still route to the displaced marker"
963 );
964 assert_eq!(get_pos(&tree, left), (10, 10));
965 }
966
967 #[test]
968 fn test_gravity_reversal_after_clamp_with_unordered_ids() {
969 let mut tree = IntervalTree::new();
975 let a = tree.insert(100, 100); let b = tree.insert_left_gravity(50, 50); tree.adjust_for_edit(50, -60);
980 assert_eq!(get_pos(&tree, a), (50, 50));
981 assert_eq!(get_pos(&tree, b), (50, 50));
982
983 tree.adjust_for_edit(50, 5);
985 assert_eq!(get_pos(&tree, b), (50, 50), "left-gravity stayer");
986 assert_eq!(get_pos(&tree, a), (55, 55), "right-gravity mover");
987
988 tree.adjust_for_edit(52, -1);
990 assert_eq!(get_pos(&tree, a), (54, 54));
991 assert_eq!(get_pos(&tree, b), (50, 50));
992
993 assert_eq!(
996 tree.debug_dump().len(),
997 2,
998 "tree leaked a duplicate node: {:?}",
999 tree.debug_dump()
1000 );
1001 }
1002
1003 #[test]
1004 fn test_initial_insert_and_delete() {
1005 let mut tree = IntervalTree::new();
1006 let id1 = insert_marker(&mut tree, 10, 20);
1007 let id2 = insert_marker(&mut tree, 30, 40);
1008
1009 assert_eq!(get_pos(&tree, id1), (10, 20));
1010 assert_eq!(get_pos(&tree, id2), (30, 40));
1011
1012 assert!(tree.delete(id1));
1013 assert_eq!(tree.get_position(id1), None);
1014 assert_eq!(get_pos(&tree, id2), (30, 40));
1015 }
1016
1017 #[test]
1018 fn test_basic_edit_adjustment() {
1019 let mut tree = IntervalTree::new();
1020 let id1 = insert_marker(&mut tree, 10, 20); let id2 = insert_marker(&mut tree, 30, 40); tree.adjust_for_edit(30, 5);
1025
1026 assert_eq!(
1028 get_pos(&tree, id1),
1029 (10, 20),
1030 "Marker before edit should not move."
1031 );
1032
1033 assert_eq!(
1035 get_pos(&tree, id2),
1036 (35, 45),
1037 "Marker at/after edit should move."
1038 );
1039
1040 tree.adjust_for_edit(5, -10); assert_eq!(
1045 get_pos(&tree, id1),
1046 (5, 10),
1047 "Marker moved back by deletion."
1048 );
1049
1050 assert_eq!(
1052 get_pos(&tree, id2),
1053 (25, 35),
1054 "Marker moved back by deletion."
1055 );
1056 }
1057
1058 #[test]
1059 fn test_problematic_lazy_delta_scenario() {
1060 let mut tree = IntervalTree::new();
1064
1065 let id_p = insert_marker(&mut tree, 200, 250); let id_r = insert_marker(&mut tree, 300, 350); let id_l = insert_marker(&mut tree, 100, 150); assert_eq!(
1074 get_pos(&tree, id_l),
1075 (100, 150),
1076 "L initial position incorrect."
1077 );
1078 assert_eq!(
1079 get_pos(&tree, id_p),
1080 (200, 250),
1081 "P initial position incorrect."
1082 );
1083 assert_eq!(
1084 get_pos(&tree, id_r),
1085 (300, 350),
1086 "R initial position incorrect."
1087 );
1088
1089 tree.adjust_for_edit(150, 50);
1094
1095 assert_eq!(
1099 get_pos(&tree, id_l),
1100 (100, 200),
1101 "L(100) should expand to (100, 200)."
1102 );
1103
1104 assert_eq!(
1106 get_pos(&tree, id_p),
1107 (250, 300),
1108 "P(200) did not shift correctly. Should be 250."
1109 );
1110
1111 assert_eq!(
1113 get_pos(&tree, id_r),
1114 (350, 400),
1115 "R(300) did not shift correctly. Should be 350."
1116 );
1117 }
1118
1119 #[test]
1120 fn test_interval_spanning_edit() {
1121 let mut tree = IntervalTree::new();
1122 let id_s = insert_marker(&mut tree, 50, 200);
1124
1125 tree.adjust_for_edit(100, 10);
1127
1128 assert_eq!(
1131 get_pos(&tree, id_s),
1132 (50, 210),
1133 "Spanning marker end did not move correctly."
1134 );
1135 }
1136
1137 #[test]
1138 fn test_deletion_engulfing_marker_start() {
1139 let mut tree = IntervalTree::new();
1140 let id1 = insert_marker(&mut tree, 8, 20);
1141
1142 tree.adjust_for_edit(5, -10);
1148
1149 assert_eq!(
1150 get_pos(&tree, id1),
1151 (5, 10),
1152 "Marker should be clamped and shrunk."
1153 );
1154 }
1155
1156 #[test]
1157 fn test_zero_length_marker() {
1158 let mut tree = IntervalTree::new();
1159 let id1 = insert_marker(&mut tree, 10, 10);
1160
1161 tree.adjust_for_edit(10, 5);
1163 assert_eq!(
1164 get_pos(&tree, id1),
1165 (15, 15),
1166 "Insertion at zero-length marker."
1167 );
1168
1169 tree.adjust_for_edit(5, 5);
1171 assert_eq!(
1172 get_pos(&tree, id1),
1173 (20, 20),
1174 "Insertion before zero-length marker."
1175 );
1176
1177 tree.adjust_for_edit(25, -5);
1179 assert_eq!(
1180 get_pos(&tree, id1),
1181 (20, 20),
1182 "Deletion after zero-length marker."
1183 );
1184
1185 tree.adjust_for_edit(15, -10);
1187 assert_eq!(
1191 get_pos(&tree, id1),
1192 (15, 15),
1193 "Deletion containing zero-length marker."
1194 );
1195 }
1196
1197 #[test]
1198 fn test_edit_at_pos_zero() {
1199 let mut tree = IntervalTree::new();
1200 let id1 = insert_marker(&mut tree, 10, 20);
1201
1202 tree.adjust_for_edit(0, 5);
1204 assert_eq!(get_pos(&tree, id1), (15, 25), "Insertion at pos 0.");
1205
1206 tree.adjust_for_edit(0, -5);
1208 assert_eq!(get_pos(&tree, id1), (10, 20), "Deletion at pos 0.");
1209
1210 tree.adjust_for_edit(0, -15);
1212 assert_eq!(get_pos(&tree, id1), (0, 5), "Engulfing deletion at pos 0.");
1216 }
1217
1218 #[test]
1219 fn test_deletion_preserves_marker_ordering() {
1220 let mut tree = IntervalTree::new();
1223
1224 let id0 = insert_marker(&mut tree, 0, 0);
1226 let id1 = insert_marker(&mut tree, 10, 10);
1227 let id2 = insert_marker(&mut tree, 20, 20);
1228 let id3 = insert_marker(&mut tree, 30, 30);
1229 let id4 = insert_marker(&mut tree, 40, 40);
1230
1231 assert_eq!(get_pos(&tree, id0), (0, 0));
1233 assert_eq!(get_pos(&tree, id1), (10, 10));
1234 assert_eq!(get_pos(&tree, id2), (20, 20));
1235 assert_eq!(get_pos(&tree, id3), (30, 30));
1236 assert_eq!(get_pos(&tree, id4), (40, 40));
1237
1238 tree.adjust_for_edit(5, -16);
1242
1243 let positions = vec![
1245 get_pos(&tree, id0).0,
1246 get_pos(&tree, id1).0,
1247 get_pos(&tree, id2).0,
1248 get_pos(&tree, id3).0,
1249 get_pos(&tree, id4).0,
1250 ];
1251
1252 for i in 0..positions.len() - 1 {
1254 assert!(
1255 positions[i] <= positions[i + 1],
1256 "Ordering violated at index {}: {:?}[{}]={} > {:?}[{}]={}",
1257 i,
1258 positions,
1259 i,
1260 positions[i],
1261 positions,
1262 i + 1,
1263 positions[i + 1]
1264 );
1265 }
1266
1267 assert_eq!(get_pos(&tree, id0), (0, 0), "Marker at 0 should stay at 0");
1269 assert_eq!(
1270 get_pos(&tree, id1),
1271 (5, 5),
1272 "Marker at 10 should clamp to 5"
1273 );
1274 assert_eq!(
1275 get_pos(&tree, id2),
1276 (5, 5),
1277 "Marker at 20 should clamp to 5"
1278 );
1279 assert_eq!(
1280 get_pos(&tree, id3),
1281 (14, 14),
1282 "Marker at 30 should shift to 14"
1283 );
1284 assert_eq!(
1285 get_pos(&tree, id4),
1286 (24, 24),
1287 "Marker at 40 should shift to 24"
1288 );
1289 }
1290
1291 mod property_tests {
1296 use super::*;
1297 use proptest::prelude::*;
1298
1299 #[derive(Debug, Clone)]
1300 enum Op {
1301 Insert { pos: u64, len: u64 },
1302 Delete { pos: u64, len: u64 },
1303 CreateMarker { pos: u64, right_gravity: bool },
1304 DeleteMarker { idx: usize },
1305 }
1306
1307 fn arb_op(max: u64) -> impl Strategy<Value = Op> {
1308 prop_oneof![
1309 (0..=max, 1..=50u64).prop_map(|(pos, len)| Op::Insert { pos, len }),
1310 (0..=max, 1..=30u64).prop_map(|(pos, len)| Op::Delete { pos, len }),
1311 (0..=max, any::<bool>())
1312 .prop_map(|(pos, right_gravity)| Op::CreateMarker { pos, right_gravity }),
1313 (0..200usize).prop_map(|idx| Op::DeleteMarker { idx }),
1314 ]
1315 }
1316
1317 proptest! {
1318 #[test]
1323 fn prop_tree_matches_shadow_with_unordered_ids(
1324 init in prop::collection::vec((0..1000u64, any::<bool>()), 0..15),
1325 ops in prop::collection::vec(arb_op(1000), 1..40),
1326 ) {
1327 let mut tree = IntervalTree::new();
1328 let mut shadow: Vec<(MarkerId, Option<u64>, bool)> = Vec::new();
1330
1331 for (pos, rg) in init {
1332 let id = if rg {
1333 tree.insert(pos, pos)
1334 } else {
1335 tree.insert_left_gravity(pos, pos)
1336 };
1337 shadow.push((id, Some(pos), rg));
1338 }
1339
1340 for op in ops {
1341 match op {
1342 Op::Insert { pos, len } => {
1343 tree.adjust_for_edit(pos, len as i64);
1344 for (_id, p, rg) in shadow.iter_mut() {
1345 if let Some(cur) = p {
1346 let shifts = if *rg { *cur >= pos } else { *cur > pos };
1347 if shifts {
1348 *cur += len;
1349 }
1350 }
1351 }
1352 }
1353 Op::Delete { pos, len } => {
1354 tree.adjust_for_edit(pos, -(len as i64));
1355 for (_id, p, _rg) in shadow.iter_mut() {
1356 if let Some(cur) = p {
1357 if *cur >= pos + len {
1358 *cur -= len;
1359 } else if *cur > pos {
1360 *cur = pos;
1361 }
1362 }
1363 }
1364 }
1365 Op::CreateMarker { pos, right_gravity } => {
1366 let id = if right_gravity {
1367 tree.insert(pos, pos)
1368 } else {
1369 tree.insert_left_gravity(pos, pos)
1370 };
1371 shadow.push((id, Some(pos), right_gravity));
1372 }
1373 Op::DeleteMarker { idx } => {
1374 if !shadow.is_empty() {
1375 let i = idx % shadow.len();
1376 if let (id, Some(_), _) = shadow[i] {
1377 tree.delete(id);
1378 shadow[i].1 = None;
1379 }
1380 }
1381 }
1382 }
1383
1384 for (id, p, _rg) in &shadow {
1386 if let Some(expected) = p {
1387 let actual = tree.get_position(*id).map(|x| x.0);
1388 prop_assert_eq!(
1389 actual,
1390 Some(*expected),
1391 "marker {} expected at {} but tree says {:?}",
1392 id,
1393 expected,
1394 actual
1395 );
1396 }
1397 }
1398
1399 let dump = tree.debug_dump();
1402 for w in dump.windows(2) {
1403 prop_assert!(
1404 w[0].1 <= w[1].1,
1405 "BST position order violated: id {}@{} before id {}@{}",
1406 w[0].0, w[0].1, w[1].0, w[1].1
1407 );
1408 }
1409 let live = shadow.iter().filter(|(_, p, _)| p.is_some()).count();
1410 prop_assert_eq!(
1411 dump.len(),
1412 live,
1413 "tree node count {} != live marker count {}",
1414 dump.len(),
1415 live
1416 );
1417 }
1418 }
1419 }
1420 }
1421}