Skip to main content

fresh/model/
marker_tree.rs

1use std::cell::{RefCell, RefMut};
2use std::cmp::{max, Ordering};
3use std::collections::HashMap;
4use std::mem;
5use std::rc::{Rc, Weak};
6
7/// Use a simple u64 for marker IDs
8pub type MarkerId = u64;
9
10// ---
11// 1. Core Data Structures and Pointers
12// ---
13
14#[derive(Debug, Clone, PartialEq)]
15pub struct Interval {
16    pub start: u64,
17    pub end: u64,
18}
19
20/// Type of marker - either a position marker or a line anchor
21#[derive(Debug, Clone, PartialEq)]
22pub enum MarkerType {
23    /// Regular position marker (for overlays, cursors, etc.)
24    Position,
25    /// Line anchor with estimated/exact line number
26    LineAnchor {
27        estimated_line: usize,
28        confidence: AnchorConfidence,
29    },
30}
31
32/// Confidence level for line anchor estimates
33#[derive(Debug, Clone, PartialEq)]
34pub enum AnchorConfidence {
35    /// Exact line number (scanned from known position)
36    Exact,
37    /// Estimated from average line length
38    Estimated,
39    /// Relative to another anchor
40    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    /// Insertion gravity at the marker's exact position.
49    /// - `true` (right gravity, default): text inserted exactly at the marker
50    ///   position pushes the marker forward (the marker moves after the text).
51    /// - `false` (left gravity): text inserted exactly at the marker position
52    ///   leaves it in place (the marker stays before the text).
53    ///
54    /// Only matters for insertions landing on the marker's own position;
55    /// insertions strictly before always shift it, insertions strictly after
56    /// never do. Used to keep search-match highlights from growing when text
57    /// is typed immediately after a match (issue #2053).
58    pub right_gravity: bool,
59}
60
61/// A Strong pointer to a tree node (child/sibling/map reference)
62type NodePtr = Option<Rc<RefCell<Node>>>;
63/// A Weak pointer to a tree node (parent reference, doesn't count for ownership)
64type WeakNodePtr = Weak<RefCell<Node>>;
65
66/// The internal tree node
67#[derive(Debug)]
68struct Node {
69    pub marker: Marker,
70
71    /// AVL: Height of this node's subtree
72    pub height: i32,
73    /// Augmentation: The max 'end' value in this node's subtree
74    pub max_end: u64,
75    /// VSCode-style: The delta to be applied to this node and its children
76    pub lazy_delta: i64,
77
78    pub parent: WeakNodePtr,
79    pub left: NodePtr,
80    pub right: NodePtr,
81}
82
83/// The main Interval Tree structure
84#[derive(Debug, Default)]
85pub struct IntervalTree {
86    root: NodePtr,
87    next_id: u64,
88    /// ID-to-Node map for O(1) lookups
89    marker_map: HashMap<MarkerId, Rc<RefCell<Node>>>,
90}
91
92// ---
93// 2. Node Helpers (Pushing Deltas, Stats, Heights)
94// ---
95
96impl Node {
97    fn new(marker: Marker, parent: WeakNodePtr) -> Rc<RefCell<Self>> {
98        // Fix E0382: Calculate max_end before moving ownership of `marker` into the struct.
99        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    /// Gets the height of a node (0 for None).
113    fn height(node: &NodePtr) -> i32 {
114        node.as_ref().map_or(0, |n| n.borrow().height)
115    }
116
117    /// Calculates the balance factor of a node (height(left) - height(right)).
118    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    /// Pushes this node's lazy_delta down to its immediate children.
124    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        // Apply delta to self (start and end)
133        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        // Apply delta to children (only update their lazy_delta fields)
137        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        // The max_end needs to be updated after the push
147        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    /// Updates a node's height and max_end based on its children.
153    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
166// ---
167// 3. Main Public API
168// ---
169
170impl IntervalTree {
171    pub fn new() -> Self {
172        Self::default()
173    }
174
175    /// Inserts a new marker interval. Performance: O(log n)
176    pub fn insert(&mut self, start: u64, end: u64) -> MarkerId {
177        self.insert_with_type(start, end, MarkerType::Position)
178    }
179
180    /// Inserts a new left-gravity marker interval: text inserted exactly at the
181    /// marker's position leaves it in place rather than pushing it forward.
182    /// Performance: O(log n)
183    pub fn insert_left_gravity(&mut self, start: u64, end: u64) -> MarkerId {
184        self.insert_full(start, end, MarkerType::Position, false)
185    }
186
187    /// Insert a marker with a specific ID and type (for set_position).
188    /// The caller must ensure the ID is not already in use.
189    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    /// Insert a marker with a specific type (right gravity).
221    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    /// Insert a marker with a specific type and gravity.
226    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    /// Insert a line anchor at a specific position
250    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    /// Finds the current true position of a marker by its ID. Performance: O(log n)
268    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        // Walk up the tree, collecting all deltas that haven't been applied yet.
274        while let Some(current_rc) = node_opt {
275            let current = current_rc.borrow();
276
277            // Add this node's delta (if any)
278            current_delta += current.lazy_delta;
279
280            // Move up to the parent
281            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    /// Deletes a marker by its ID. Performance: O(log n)
293    pub fn delete(&mut self, id: MarkerId) -> bool {
294        let (start, _) = match self.get_position(id) {
295            Some(pos) => pos,
296            None => return false,
297        };
298
299        if !self.marker_map.contains_key(&id) {
300            return false;
301        }
302
303        self.root = Self::delete_recursive(self.root.take(), start, id, &mut self.marker_map);
304
305        self.marker_map.remove(&id).is_some()
306    }
307
308    /// Move a marker to a new position, preserving its ID and type.
309    /// Implemented as delete + reinsert with the same ID.
310    /// Returns false if the marker doesn't exist.
311    /// Performance: O(log n)
312    pub fn set_position(&mut self, id: MarkerId, new_start: u64, new_end: u64) -> bool {
313        // Get the marker's type and gravity before deleting
314        let (marker_type, right_gravity) = match self.get_marker(id) {
315            Some(m) => (m.marker_type, m.right_gravity),
316            None => return false,
317        };
318
319        // Delete from tree
320        if !self.delete(id) {
321            return false;
322        }
323
324        // Reinsert with same ID
325        self.insert_with_id(id, new_start, new_end, marker_type, right_gravity);
326        true
327    }
328
329    /// Adjusts all markers for a text edit (insertion or deletion).
330    /// Performance: O(log n) due to lazy delta propagation.
331    pub fn adjust_for_edit(&mut self, pos: u64, delta: i64) {
332        Self::adjust_recursive(&mut self.root, pos, delta);
333    }
334
335    /// Finds all markers that overlap a given query range.
336    /// Performance: O(log n + k)
337    pub fn query(&self, query_start: u64, query_end: u64) -> Vec<Marker> {
338        let mut results = Vec::new();
339        Self::query_recursive(&self.root, query_start, query_end, &mut results);
340        results
341    }
342
343    /// Get the marker data for a given marker ID
344    pub fn get_marker(&self, id: MarkerId) -> Option<Marker> {
345        let node_rc = self.marker_map.get(&id)?;
346        Some(node_rc.borrow().marker.clone())
347    }
348
349    /// Update a line anchor's estimated line number and confidence
350    pub fn update_line_anchor(
351        &mut self,
352        id: MarkerId,
353        estimated_line: usize,
354        confidence: AnchorConfidence,
355    ) -> bool {
356        if let Some(node_rc) = self.marker_map.get(&id) {
357            let mut node = node_rc.borrow_mut();
358            node.marker.marker_type = MarkerType::LineAnchor {
359                estimated_line,
360                confidence,
361            };
362            true
363        } else {
364            false
365        }
366    }
367
368    /// Query only line anchors in a range
369    pub fn query_line_anchors(&self, query_start: u64, query_end: u64) -> Vec<Marker> {
370        self.query(query_start, query_end)
371            .into_iter()
372            .filter(|m| matches!(m.marker_type, MarkerType::LineAnchor { .. }))
373            .collect()
374    }
375}
376
377// ---
378// 4. Recursive Implementation Details (Insert, Delete, Adjust)
379// ---
380
381impl IntervalTree {
382    /// Recursive helper for insert
383    fn insert_recursive(root: NodePtr, new_node: Rc<RefCell<Node>>) -> NodePtr {
384        // Remove unnecessary 'mut'
385        let root = match root {
386            Some(r) => r,
387            None => return Some(new_node),
388        };
389
390        Node::push_delta(&root);
391
392        let (start, id) = (
393            new_node.borrow().marker.interval.start,
394            new_node.borrow().marker.id,
395        );
396
397        let mut root_mut = root.borrow_mut();
398        let (root_start, root_id) = (root_mut.marker.interval.start, root_mut.marker.id);
399
400        if start < root_start || (start == root_start && id < root_id) {
401            root_mut.left = Self::insert_recursive(root_mut.left.take(), Rc::clone(&new_node));
402            root_mut.left.as_ref().unwrap().borrow_mut().parent = Rc::downgrade(&root);
403        } else {
404            root_mut.right = Self::insert_recursive(root_mut.right.take(), Rc::clone(&new_node));
405            root_mut.right.as_ref().unwrap().borrow_mut().parent = Rc::downgrade(&root);
406        }
407
408        drop(root_mut);
409        Node::update_stats(&root);
410        Self::balance(root)
411    }
412
413    /// Recursive helper for delete
414    fn delete_recursive(
415        root: NodePtr,
416        start: u64,
417        id: MarkerId,
418        marker_map: &mut HashMap<MarkerId, Rc<RefCell<Node>>>,
419    ) -> NodePtr {
420        // Remove unnecessary 'mut'
421        let root = root?;
422
423        Node::push_delta(&root);
424
425        let mut root_mut = root.borrow_mut();
426        let (root_start, root_id) = (root_mut.marker.interval.start, root_mut.marker.id);
427
428        match start.cmp(&root_start) {
429            Ordering::Less => {
430                root_mut.left = Self::delete_recursive(root_mut.left.take(), start, id, marker_map);
431            }
432            Ordering::Greater => {
433                root_mut.right =
434                    Self::delete_recursive(root_mut.right.take(), start, id, marker_map);
435            }
436            Ordering::Equal => match id.cmp(&root_id) {
437                Ordering::Less => {
438                    root_mut.left =
439                        Self::delete_recursive(root_mut.left.take(), start, id, marker_map);
440                }
441                Ordering::Greater => {
442                    root_mut.right =
443                        Self::delete_recursive(root_mut.right.take(), start, id, marker_map);
444                }
445                Ordering::Equal => {
446                    return Self::perform_node_deletion(root_mut, Rc::clone(&root), marker_map);
447                }
448            },
449        }
450
451        drop(root_mut);
452        Node::update_stats(&root);
453        Self::balance(root)
454    }
455
456    /// Handles the actual structural changes for deletion.
457    fn perform_node_deletion(
458        mut node: RefMut<Node>,
459        node_rc: Rc<RefCell<Node>>,
460        marker_map: &mut HashMap<MarkerId, Rc<RefCell<Node>>>,
461    ) -> NodePtr {
462        if node.left.is_none() {
463            let right = node.right.take();
464            if let Some(ref r) = right {
465                r.borrow_mut().parent = node.parent.clone();
466            }
467            right
468        } else if node.right.is_none() {
469            let left = node.left.take();
470            if let Some(ref l) = left {
471                l.borrow_mut().parent = node.parent.clone();
472            }
473            left
474        } else {
475            let successor_rc = Self::min_node(node.right.as_ref().unwrap());
476
477            let (_successor_start, successor_id) = {
478                let s = successor_rc.borrow();
479                (s.marker.interval.start, s.marker.id)
480            };
481
482            // Capture the original node's marker identity BEFORE we swap
483            // it away. After the swap `successor_rc` carries this
484            // marker, and that's what delete_recursive must now search
485            // for in the right subtree to remove it.
486            let (orig_start, orig_id) = (node.marker.interval.start, node.marker.id);
487
488            mem::swap(&mut node.marker, &mut successor_rc.borrow_mut().marker);
489
490            // `node_rc` now carries the successor's marker, so external
491            // references (marker_map) to `successor_id` must point here
492            // instead of at the old successor node (which is about to
493            // be removed). Without this update, `get_position(successor_id)`
494            // would keep resolving via the orphaned successor node and
495            // return stale data — the root cause of end-of-line inlay
496            // hints flipping to the start of their line after a nearby
497            // delete, because the orphan misses all subsequent
498            // adjust_for_edit calls.
499            marker_map.insert(successor_id, Rc::clone(&node_rc));
500
501            // Remove the old successor node. After the swap it holds
502            // the original marker (orig_start, orig_id), so search by
503            // THAT key — not by (successor_start, successor_id) which
504            // no longer corresponds to any node in the subtree.
505            node.right = Self::delete_recursive(node.right.take(), orig_start, orig_id, marker_map);
506
507            drop(node);
508            Node::update_stats(&node_rc);
509            Self::balance(node_rc)
510        }
511    }
512
513    /// Finds the minimum node in a subtree (for deletion)
514    fn min_node(node_rc: &Rc<RefCell<Node>>) -> Rc<RefCell<Node>> {
515        let mut current = Rc::clone(node_rc);
516        loop {
517            Node::push_delta(&current);
518
519            // Fix E0506: Clone the next node pointer before the borrow (Ref<Node>) on
520            // `current` is dropped and potentially prevents reassignment.
521            let next_left_opt = current.borrow().left.clone();
522
523            if let Some(next) = next_left_opt {
524                current = next;
525            } else {
526                break current;
527            }
528        }
529    }
530
531    /// CORRECTED Recursive helper for `adjust_for_edit` (O(log n) lazy update)
532    fn adjust_recursive(node_opt: &mut NodePtr, pos: u64, delta: i64) {
533        let node_rc = match node_opt {
534            Some(n) => n,
535            None => return,
536        };
537
538        Node::push_delta(node_rc);
539
540        let mut node = node_rc.borrow_mut();
541        let start = node.marker.interval.start;
542        // Left-gravity markers stay put when text is inserted exactly at their
543        // position (the insertion goes after them); right-gravity markers (the
544        // default) are pushed forward. Gravity only changes behaviour for
545        // insertions (delta > 0) landing exactly on the boundary.
546        let left_gravity = !node.marker.right_gravity;
547
548        if pos <= start {
549            // CASE 1: Edit is at or before this node's start.
550            // This node and everything to its right must be shifted.
551
552            // Whether this node's own start should shift. A left-gravity marker
553            // does NOT move when the insertion lands exactly on it.
554            let stay_put = left_gravity && delta > 0 && pos == start;
555
556            // 1. Shift the current node's start position directly, clamping at `pos` if needed.
557            if !stay_put {
558                if delta < 0 {
559                    node.marker.interval.start = (start as i64 + delta).max(pos as i64) as u64;
560                } else {
561                    node.marker.interval.start = (start as i64 + delta) as u64;
562                }
563            }
564
565            // 2. Handle the right subtree.
566            // For insertions strictly before this node's start, every node to
567            // the right has start > pos and shifts uniformly, so lazy
568            // propagation is safe and efficient. When the insertion lands
569            // exactly on this node's start (pos == start), the right subtree may
570            // contain other markers also sitting at `pos` whose gravity must be
571            // respected individually, so recurse instead of shifting them all.
572            // Deletions always recurse so nodes can clamp to `pos`.
573            if delta < 0 || pos == start {
574                Self::adjust_recursive(&mut node.right, pos, delta);
575            } else if let Some(ref right) = node.right {
576                right.borrow_mut().lazy_delta += delta;
577            }
578
579            // 3. Recurse left, as it may contain markers spanning the edit pos.
580            Self::adjust_recursive(&mut node.left, pos, delta);
581        } else {
582            // pos > start
583            // CASE 2: This node's start is BEFORE the edit.
584            // Its start is unaffected. We only need to check the right subtree
585            // for nodes that might be affected.
586            Self::adjust_recursive(&mut node.right, pos, delta);
587        }
588
589        // Handle the interval span case (where the edit falls inside [start, end]).
590        // A left-gravity marker's end stays put for an insertion landing exactly
591        // on it, matching its start behaviour above.
592        let end = node.marker.interval.end;
593        let shift_end = if left_gravity && delta > 0 {
594            end > pos
595        } else {
596            end >= pos
597        };
598        if shift_end {
599            node.marker.interval.end =
600                (end as i64 + delta).max(node.marker.interval.start as i64) as u64;
601        }
602
603        drop(node);
604        Node::update_stats(node_rc);
605    }
606
607    /// Recursive helper for query
608    fn query_recursive(
609        node_opt: &NodePtr,
610        query_start: u64,
611        query_end: u64,
612        results: &mut Vec<Marker>,
613    ) {
614        let node_rc = match node_opt {
615            Some(n) => n,
616            None => return,
617        };
618
619        Node::push_delta(node_rc);
620        let node = node_rc.borrow();
621
622        let i = &node.marker.interval;
623        if i.start <= query_end && i.end >= query_start {
624            results.push(node.marker.clone());
625        }
626
627        if node.left.is_some() && node.left.as_ref().unwrap().borrow().max_end >= query_start {
628            Self::query_recursive(&node.left, query_start, query_end, results);
629        }
630
631        if node.right.is_some() && node.marker.interval.start <= query_end {
632            Self::query_recursive(&node.right, query_start, query_end, results);
633        }
634    }
635
636    // --- AVL Balancing ---
637
638    fn balance(node: Rc<RefCell<Node>>) -> NodePtr {
639        let bf = Node::balance_factor(&node);
640
641        if bf > 1 {
642            let left_rc = node.borrow().left.as_ref().unwrap().clone();
643            if Node::balance_factor(&left_rc) < 0 {
644                // Fix RefCell borrow issue: extract left child before rotating
645                let left_child = node.borrow_mut().left.take().unwrap();
646                node.borrow_mut().left = Self::rotate_left(left_child);
647            }
648            Self::rotate_right(node)
649        } else if bf < -1 {
650            let right_rc = node.borrow().right.as_ref().unwrap().clone();
651            if Node::balance_factor(&right_rc) > 0 {
652                // Fix RefCell borrow issue: extract right child before rotating
653                let right_child = node.borrow_mut().right.take().unwrap();
654                node.borrow_mut().right = Self::rotate_right(right_child);
655            }
656            Self::rotate_left(node)
657        } else {
658            Some(node)
659        }
660    }
661
662    fn rotate_left(node_rc: Rc<RefCell<Node>>) -> NodePtr {
663        Node::push_delta(&node_rc);
664        let x_rc = node_rc.borrow_mut().right.take().unwrap();
665        Node::push_delta(&x_rc);
666
667        let mut y = node_rc.borrow_mut();
668        let mut x = x_rc.borrow_mut();
669
670        y.right = x.left.take();
671        if let Some(ref r) = y.right {
672            r.borrow_mut().parent = Rc::downgrade(&node_rc);
673        }
674        x.left = Some(Rc::clone(&node_rc));
675        x.parent = y.parent.clone();
676        y.parent = Rc::downgrade(&x_rc);
677
678        drop(x);
679        drop(y);
680
681        Node::update_stats(&node_rc);
682        Node::update_stats(&x_rc);
683        Some(x_rc)
684    }
685
686    fn rotate_right(node_rc: Rc<RefCell<Node>>) -> NodePtr {
687        Node::push_delta(&node_rc);
688        let x_rc = node_rc.borrow_mut().left.take().unwrap();
689        Node::push_delta(&x_rc);
690
691        let mut y = node_rc.borrow_mut();
692        let mut x = x_rc.borrow_mut();
693
694        y.left = x.right.take();
695        if let Some(ref l) = y.left {
696            l.borrow_mut().parent = Rc::downgrade(&node_rc);
697        }
698        x.right = Some(Rc::clone(&node_rc));
699        x.parent = y.parent.clone();
700        y.parent = Rc::downgrade(&x_rc);
701
702        drop(x);
703        drop(y);
704
705        Node::update_stats(&node_rc);
706        Node::update_stats(&x_rc);
707        Some(x_rc)
708    }
709}
710
711#[cfg(test)]
712mod tests {
713    use super::*;
714
715    /// Helper to insert and return the ID, making test setup cleaner.
716    fn insert_marker(tree: &mut IntervalTree, start: u64, end: u64) -> MarkerId {
717        tree.insert(start, end)
718    }
719
720    /// Helper to get position and unwrap, or panic with a clear message.
721    fn get_pos(tree: &IntervalTree, id: MarkerId) -> (u64, u64) {
722        tree.get_position(id)
723            .unwrap_or_else(|| panic!("Marker ID {} not found.", id))
724    }
725
726    // --- Insertion gravity (issue #2053) ---
727
728    #[test]
729    fn test_left_gravity_marker_stays_on_insert_at_position() {
730        // A left-gravity point marker models the end of a fixed-width
731        // highlight (e.g. a search match). Inserting text exactly at its
732        // position must NOT move it, so the highlight does not grow.
733        let mut tree = IntervalTree::new();
734        let m = tree.insert_left_gravity(3, 3);
735
736        // Insert 4 bytes immediately at the marker.
737        tree.adjust_for_edit(3, 4);
738
739        assert_eq!(
740            get_pos(&tree, m),
741            (3, 3),
742            "left-gravity marker must stay put when text is inserted at its position"
743        );
744    }
745
746    #[test]
747    fn test_right_gravity_marker_moves_on_insert_at_position() {
748        // The default (right gravity) point marker moves forward when text is
749        // inserted exactly at its position.
750        let mut tree = IntervalTree::new();
751        let m = tree.insert(3, 3);
752
753        tree.adjust_for_edit(3, 4);
754
755        assert_eq!(get_pos(&tree, m), (7, 7));
756    }
757
758    #[test]
759    fn test_left_gravity_marker_still_shifts_on_insert_before() {
760        // Left gravity only changes the exact-boundary case; insertions
761        // strictly before the marker still shift it.
762        let mut tree = IntervalTree::new();
763        let m = tree.insert_left_gravity(5, 5);
764
765        tree.adjust_for_edit(2, 3);
766
767        assert_eq!(get_pos(&tree, m), (8, 8));
768    }
769
770    #[test]
771    fn test_search_match_does_not_grow_on_adjacent_insert() {
772        // Reproduces #2053 at the marker level: a match highlight spanning
773        // [0, 3) is modelled by a right-gravity start marker at 0 and a
774        // left-gravity end marker at 3. Typing immediately after the match
775        // (at position 3) must leave both markers anchored to the match.
776        let mut tree = IntervalTree::new();
777        let start = tree.insert(0, 0);
778        let end = tree.insert_left_gravity(3, 3);
779
780        // User types "X" right after the match.
781        tree.adjust_for_edit(3, 1);
782
783        assert_eq!(get_pos(&tree, start), (0, 0));
784        assert_eq!(
785            get_pos(&tree, end),
786            (3, 3),
787            "highlight end must not extend over text typed after the match"
788        );
789    }
790
791    #[test]
792    fn test_adjacent_matches_keep_independent_gravity_at_shared_boundary() {
793        // Two adjacent matches (e.g. searching "ab" in "abab") share a
794        // boundary at position 2: the first match's left-gravity end and the
795        // second match's right-gravity start both sit at 2. Inserting there
796        // must keep the first match fixed while the second match shifts.
797        let mut tree = IntervalTree::new();
798        let m1_start = tree.insert(0, 0);
799        let m1_end = tree.insert_left_gravity(2, 2);
800        let m2_start = tree.insert(2, 2);
801        let m2_end = tree.insert_left_gravity(4, 4);
802
803        // Insert 3 bytes exactly at the shared boundary.
804        tree.adjust_for_edit(2, 3);
805
806        assert_eq!(get_pos(&tree, m1_start), (0, 0));
807        assert_eq!(get_pos(&tree, m1_end), (2, 2), "first match must not grow");
808        assert_eq!(get_pos(&tree, m2_start), (5, 5), "second match shifts");
809        assert_eq!(get_pos(&tree, m2_end), (7, 7));
810    }
811
812    #[test]
813    fn test_initial_insert_and_delete() {
814        let mut tree = IntervalTree::new();
815        let id1 = insert_marker(&mut tree, 10, 20);
816        let id2 = insert_marker(&mut tree, 30, 40);
817
818        assert_eq!(get_pos(&tree, id1), (10, 20));
819        assert_eq!(get_pos(&tree, id2), (30, 40));
820
821        assert!(tree.delete(id1));
822        assert_eq!(tree.get_position(id1), None);
823        assert_eq!(get_pos(&tree, id2), (30, 40));
824    }
825
826    #[test]
827    fn test_basic_edit_adjustment() {
828        let mut tree = IntervalTree::new();
829        let id1 = insert_marker(&mut tree, 10, 20); // Before edit
830        let id2 = insert_marker(&mut tree, 30, 40); // At/After edit
831
832        // Insert 5 characters at position 30
833        tree.adjust_for_edit(30, 5);
834
835        // id1 (10-20) should not move
836        assert_eq!(
837            get_pos(&tree, id1),
838            (10, 20),
839            "Marker before edit should not move."
840        );
841
842        // id2 (30-40) should move to (35-45)
843        assert_eq!(
844            get_pos(&tree, id2),
845            (35, 45),
846            "Marker at/after edit should move."
847        );
848
849        // Delete 10 characters at position 5
850        tree.adjust_for_edit(5, -10); // All markers are after position 5
851
852        // id1 (10-20) is inside the deletion [5, 15) and should be clamped and shrunk.
853        assert_eq!(
854            get_pos(&tree, id1),
855            (5, 10),
856            "Marker moved back by deletion."
857        );
858
859        // id2 (35-45) -> (25-35)
860        assert_eq!(
861            get_pos(&tree, id2),
862            (25, 35),
863            "Marker moved back by deletion."
864        );
865    }
866
867    #[test]
868    fn test_problematic_lazy_delta_scenario() {
869        // This test replicates the tricky tree structure to ensure the O(log n) lazy
870        // delta propagation works correctly and doesn't over-propagate to left children.
871
872        let mut tree = IntervalTree::new();
873
874        // Setup the tree with specific positions to force a parent/child relationship
875        // that caused the previous bug:
876        // L(100) -> P(200) <- R(300)
877        let id_p = insert_marker(&mut tree, 200, 250); // Parent node (P)
878        let id_r = insert_marker(&mut tree, 300, 350); // Right child (R)
879        let id_l = insert_marker(&mut tree, 100, 150); // Left child (L)
880
881        // --- Verify initial state ---
882        assert_eq!(
883            get_pos(&tree, id_l),
884            (100, 150),
885            "L initial position incorrect."
886        );
887        assert_eq!(
888            get_pos(&tree, id_p),
889            (200, 250),
890            "P initial position incorrect."
891        );
892        assert_eq!(
893            get_pos(&tree, id_r),
894            (300, 350),
895            "R initial position incorrect."
896        );
897
898        // --- Apply the problematic edit ---
899        // Edit: Insert 50 characters at position 150 (P=150, delta=+50)
900        // L(100) should NOT move (100 < 150).
901        // P(200) and R(300) should move (+50).
902        tree.adjust_for_edit(150, 50);
903
904        // --- Verify corrected final state ---
905
906        // L(100) should have its end expanded (100 < 150, but 150 >= 150).
907        assert_eq!(
908            get_pos(&tree, id_l),
909            (100, 200),
910            "L(100) should expand to (100, 200)."
911        );
912
913        // P(200) should be shifted (200 >= 150) -> 250
914        assert_eq!(
915            get_pos(&tree, id_p),
916            (250, 300),
917            "P(200) did not shift correctly. Should be 250."
918        );
919
920        // R(300) should be shifted (300 >= 150) -> 350
921        assert_eq!(
922            get_pos(&tree, id_r),
923            (350, 400),
924            "R(300) did not shift correctly. Should be 350."
925        );
926    }
927
928    #[test]
929    fn test_interval_spanning_edit() {
930        let mut tree = IntervalTree::new();
931        // Marker S starts before edit, but spans it.
932        let id_s = insert_marker(&mut tree, 50, 200);
933
934        // Edit: Insert 10 characters at position 100 (P=100, delta=+10)
935        tree.adjust_for_edit(100, 10);
936
937        // S(50, 200) starts before 100, so its start (50) is fixed.
938        // Its end (200) is at/after 100, so its end should move to 210.
939        assert_eq!(
940            get_pos(&tree, id_s),
941            (50, 210),
942            "Spanning marker end did not move correctly."
943        );
944    }
945
946    #[test]
947    fn test_deletion_engulfing_marker_start() {
948        let mut tree = IntervalTree::new();
949        let id1 = insert_marker(&mut tree, 8, 20);
950
951        // Delete 10 chars at pos 5. Deletion is on [5, 15).
952        // Marker is on [8, 20). The part [8, 15) is deleted.
953        // New start should be clamped at the deletion position: 5.
954        // End is adjusted by delta: 20 - 10 = 10.
955        // So new interval should be (5, 10).
956        tree.adjust_for_edit(5, -10);
957
958        assert_eq!(
959            get_pos(&tree, id1),
960            (5, 10),
961            "Marker should be clamped and shrunk."
962        );
963    }
964
965    #[test]
966    fn test_zero_length_marker() {
967        let mut tree = IntervalTree::new();
968        let id1 = insert_marker(&mut tree, 10, 10);
969
970        // Insertion at the marker's position should push it.
971        tree.adjust_for_edit(10, 5);
972        assert_eq!(
973            get_pos(&tree, id1),
974            (15, 15),
975            "Insertion at zero-length marker."
976        );
977
978        // Insertion before should also push it.
979        tree.adjust_for_edit(5, 5);
980        assert_eq!(
981            get_pos(&tree, id1),
982            (20, 20),
983            "Insertion before zero-length marker."
984        );
985
986        // Deletion after should not affect it.
987        tree.adjust_for_edit(25, -5);
988        assert_eq!(
989            get_pos(&tree, id1),
990            (20, 20),
991            "Deletion after zero-length marker."
992        );
993
994        // Deletion that contains the marker.
995        tree.adjust_for_edit(15, -10);
996        // Marker at 20. Deletion on [15, 25).
997        // Start becomes max(15, 20-10) = 15.
998        // End becomes max(new_start, 20-10) = max(15, 10) = 15.
999        assert_eq!(
1000            get_pos(&tree, id1),
1001            (15, 15),
1002            "Deletion containing zero-length marker."
1003        );
1004    }
1005
1006    #[test]
1007    fn test_edit_at_pos_zero() {
1008        let mut tree = IntervalTree::new();
1009        let id1 = insert_marker(&mut tree, 10, 20);
1010
1011        // Insertion at pos 0
1012        tree.adjust_for_edit(0, 5);
1013        assert_eq!(get_pos(&tree, id1), (15, 25), "Insertion at pos 0.");
1014
1015        // Deletion at pos 0
1016        tree.adjust_for_edit(0, -5);
1017        assert_eq!(get_pos(&tree, id1), (10, 20), "Deletion at pos 0.");
1018
1019        // Deletion at pos 0 that engulfs the start.
1020        tree.adjust_for_edit(0, -15);
1021        // Marker at (10, 20). Deletion on [0, 15).
1022        // New start becomes max(0, 10-15) = 0.
1023        // New end becomes max(new_start, 20-15) = max(0, 5) = 5.
1024        assert_eq!(get_pos(&tree, id1), (0, 5), "Engulfing deletion at pos 0.");
1025    }
1026
1027    #[test]
1028    fn test_deletion_preserves_marker_ordering() {
1029        // This test reproduces the bug found in prop_marker_ordering_preserved
1030        // where lazy delta propagation causes ordering violations.
1031        let mut tree = IntervalTree::new();
1032
1033        // Create markers in order: [0, 10, 20, 30, 40] (spacing=10)
1034        let id0 = insert_marker(&mut tree, 0, 0);
1035        let id1 = insert_marker(&mut tree, 10, 10);
1036        let id2 = insert_marker(&mut tree, 20, 20);
1037        let id3 = insert_marker(&mut tree, 30, 30);
1038        let id4 = insert_marker(&mut tree, 40, 40);
1039
1040        // Verify initial state
1041        assert_eq!(get_pos(&tree, id0), (0, 0));
1042        assert_eq!(get_pos(&tree, id1), (10, 10));
1043        assert_eq!(get_pos(&tree, id2), (20, 20));
1044        assert_eq!(get_pos(&tree, id3), (30, 30));
1045        assert_eq!(get_pos(&tree, id4), (40, 40));
1046
1047        // Delete 16 bytes starting at position 5
1048        // This deletes range [5, 21)
1049        // Expected positions after: [0, 5, 5, 14, 24]
1050        tree.adjust_for_edit(5, -16);
1051
1052        // Get all positions
1053        let positions = vec![
1054            get_pos(&tree, id0).0,
1055            get_pos(&tree, id1).0,
1056            get_pos(&tree, id2).0,
1057            get_pos(&tree, id3).0,
1058            get_pos(&tree, id4).0,
1059        ];
1060
1061        // Verify ordering is preserved (no inversions)
1062        for i in 0..positions.len() - 1 {
1063            assert!(
1064                positions[i] <= positions[i + 1],
1065                "Ordering violated at index {}: {:?}[{}]={} > {:?}[{}]={}",
1066                i,
1067                positions,
1068                i,
1069                positions[i],
1070                positions,
1071                i + 1,
1072                positions[i + 1]
1073            );
1074        }
1075
1076        // Verify specific expected positions
1077        assert_eq!(get_pos(&tree, id0), (0, 0), "Marker at 0 should stay at 0");
1078        assert_eq!(
1079            get_pos(&tree, id1),
1080            (5, 5),
1081            "Marker at 10 should clamp to 5"
1082        );
1083        assert_eq!(
1084            get_pos(&tree, id2),
1085            (5, 5),
1086            "Marker at 20 should clamp to 5"
1087        );
1088        assert_eq!(
1089            get_pos(&tree, id3),
1090            (14, 14),
1091            "Marker at 30 should shift to 14"
1092        );
1093        assert_eq!(
1094            get_pos(&tree, id4),
1095            (24, 24),
1096            "Marker at 40 should shift to 24"
1097        );
1098    }
1099}