Skip to main content

fresh/model/
marker.rs

1/// Marker system for content-anchored positions
2///
3/// This module provides a marker system where markers automatically adjust
4/// their positions when text is inserted or deleted.
5///
6/// **Implementation Note:**
7/// The MarkerList struct provides backward-compatible API using the old Vec-based
8/// implementation (O(n) operations). For performance-critical use cases with many
9/// markers, use IntervalTree directly from marker_tree module (O(log n) operations).
10///
11/// The Vec-based implementation is kept for compatibility and simplicity in
12/// situations where marker count is low (<100).
13use std::collections::HashMap;
14
15use crate::model::marker_tree::IntervalTree;
16
17/// Unique identifier for a marker
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub struct MarkerId(pub u64);
20
21/// Entry in the marker list - either a gap (content bytes) or a marker
22#[derive(Debug, Clone, PartialEq)]
23pub enum MarkerEntry {
24    /// A gap representing N bytes of buffer content
25    Gap(usize),
26
27    /// A marker at this position
28    Marker {
29        id: MarkerId,
30        /// Insertion affinity:
31        /// - true (left): marker stays before text inserted at this position
32        /// - false (right): marker moves after text inserted at this position
33        left_affinity: bool,
34    },
35}
36
37/// Marker list implementation using IntervalTree for O(log n) operations
38///
39/// This provides a backward-compatible API for the old Vec-based implementation,
40/// but uses IntervalTree internally for better performance with many markers.
41///
42/// Point markers (single positions) are represented as zero-length intervals.
43#[derive(Debug)]
44pub struct MarkerList {
45    /// Internal interval tree for O(log n) operations
46    tree: IntervalTree,
47
48    /// Track affinity for compatibility (though IntervalTree handles this through intervals)
49    /// We don't strictly need this for the tree, but keep it for API compatibility
50    _affinity_map: HashMap<MarkerId, bool>,
51}
52
53impl MarkerList {
54    /// Create a new empty marker list
55    pub fn new() -> Self {
56        Self {
57            tree: IntervalTree::new(),
58            _affinity_map: HashMap::new(),
59        }
60    }
61
62    /// Create a new marker at the given position
63    ///
64    /// # Arguments
65    /// * `position` - Byte offset in the buffer
66    /// * `left_affinity` - If true, marker stays before text inserted at this position
67    ///
68    /// # Returns
69    /// The ID of the newly created marker
70    ///
71    /// Note: Point markers are represented as zero-length intervals in the tree.
72    /// The IntervalTree handles position adjustments using interval semantics, which
73    /// differs slightly from explicit affinity for zero-length markers at exact edit
74    /// positions. In practice, this doesn't affect the LSP diagnostics use case.
75    pub fn create(&mut self, position: usize, left_affinity: bool) -> MarkerId {
76        let pos = position as u64;
77
78        // Create a zero-length interval for point markers
79        // The IntervalTree handles affinity through its interval spanning logic
80        let tree_id = self.tree.insert(pos, pos);
81        let id = MarkerId(tree_id);
82
83        // Store affinity for compatibility (though not strictly needed by tree)
84        self._affinity_map.insert(id, left_affinity);
85
86        tracing::trace!(
87            "Created marker {:?} at position {} with {} affinity",
88            id,
89            position,
90            if left_affinity { "left" } else { "right" }
91        );
92
93        id
94    }
95
96    /// Delete a marker
97    pub fn delete(&mut self, id: MarkerId) {
98        self.tree.delete(id.0);
99        self._affinity_map.remove(&id);
100    }
101
102    /// Move a marker to a new byte position, preserving its ID and affinity.
103    ///
104    /// Implemented as delete + reinsert in the interval tree to maintain BST
105    /// ordering invariants. The MarkerId is preserved so external references
106    /// (VirtualTextManager, OverlayManager, MarginManager) remain valid.
107    /// Returns false if the marker doesn't exist.
108    /// Cost: O(log n)
109    pub fn set_position(&mut self, id: MarkerId, new_position: usize) -> bool {
110        let pos = new_position as u64;
111        self.tree.set_position(id.0, pos, pos)
112    }
113
114    /// Get the current byte position of a marker
115    ///
116    /// For point markers (zero-length intervals), returns the start position.
117    /// Cost: O(log n) with the IntervalTree implementation.
118    pub fn get_position(&self, id: MarkerId) -> Option<usize> {
119        let (start, _end) = self.tree.get_position(id.0)?;
120        Some(start as usize)
121    }
122
123    /// Query all markers that overlap with a byte range
124    ///
125    /// This is an efficient way to find all markers in a viewport/visible region.
126    /// Returns a Vec of (MarkerId, start_position, end_position) tuples.
127    ///
128    /// Cost: O(log n + k) where k is the number of overlapping markers
129    ///
130    /// # Example
131    /// ```ignore
132    /// // Get all markers in the visible viewport
133    /// let visible_markers = marker_list.query_range(viewport_start, viewport_end);
134    /// ```
135    pub fn query_range(&self, start: usize, end: usize) -> Vec<(MarkerId, usize, usize)> {
136        self.tree
137            .query(start as u64, end as u64)
138            .into_iter()
139            .map(|m| {
140                (
141                    MarkerId(m.id),
142                    m.interval.start as usize,
143                    m.interval.end as usize,
144                )
145            })
146            .collect()
147    }
148
149    /// Adjust all markers for an insertion
150    ///
151    /// # Arguments
152    /// * `position` - Byte offset where text was inserted
153    /// * `length` - Number of bytes inserted
154    ///
155    /// Delegates to IntervalTree's adjust_for_edit with positive delta.
156    /// Cost: O(log n)
157    pub fn adjust_for_insert(&mut self, position: usize, length: usize) {
158        if length == 0 {
159            return;
160        }
161
162        self.tree.adjust_for_edit(position as u64, length as i64);
163    }
164
165    /// Adjust all markers for a deletion
166    ///
167    /// # Arguments
168    /// * `position` - Byte offset where deletion starts
169    /// * `length` - Number of bytes deleted
170    ///
171    /// Delegates to IntervalTree's adjust_for_edit with negative delta.
172    /// Markers within the deleted range are automatically handled by the tree.
173    /// Cost: O(log n)
174    pub fn adjust_for_delete(&mut self, position: usize, length: usize) {
175        if length == 0 {
176            return;
177        }
178
179        self.tree.adjust_for_edit(position as u64, -(length as i64));
180    }
181
182    /// Get the total size of the buffer (not directly tracked by IntervalTree)
183    ///
184    /// Note: This method is kept for API compatibility but is no longer used internally.
185    /// The buffer size is managed by the Buffer struct, not by markers.
186    pub fn buffer_size(&self) -> usize {
187        // Find the maximum end position among all markers
188        // This is an approximation - the actual buffer size should be tracked separately
189        0 // The buffer size is not tracked by markers in the tree-based implementation
190    }
191
192    /// Get the number of markers
193    pub fn marker_count(&self) -> usize {
194        self._affinity_map.len()
195    }
196
197    /// Set the initial buffer size (for tests)
198    ///
199    /// Note: This is a no-op in the IntervalTree implementation as buffer size
200    /// is not tracked by markers. Kept for backward compatibility with tests.
201    #[cfg(test)]
202    pub fn set_buffer_size(&mut self, _size: usize) {
203        // No-op: IntervalTree doesn't track buffer size
204    }
205
206    /// Iterate through entries (for testing and debugging)
207    ///
208    /// Note: Not supported in IntervalTree implementation as there are no "entries".
209    /// This returns an empty slice for compatibility.
210    #[cfg(test)]
211    pub fn entries(&self) -> &[MarkerEntry] {
212        &[]
213    }
214
215    /// Check invariants (for testing)
216    ///
217    /// Note: IntervalTree has its own internal invariants. This is a compatibility stub.
218    #[cfg(test)]
219    pub fn check_invariants(&self) -> Result<(), String> {
220        // IntervalTree maintains its own invariants internally
221        Ok(())
222    }
223
224    // --- Line Anchor Methods ---
225
226    /// Create a line anchor at a specific byte range
227    ///
228    /// This creates a marker that represents a line with an estimated line number.
229    /// The byte positions are exact, but the line number may be estimated.
230    pub fn create_line_anchor(
231        &mut self,
232        start: usize,
233        end: usize,
234        estimated_line: usize,
235        confidence: crate::model::marker_tree::AnchorConfidence,
236    ) -> MarkerId {
237        let tree_id =
238            self.tree
239                .insert_line_anchor(start as u64, end as u64, estimated_line, confidence);
240        MarkerId(tree_id)
241    }
242
243    /// Get the line number and confidence for a line anchor
244    pub fn get_line_anchor_info(
245        &self,
246        id: MarkerId,
247    ) -> Option<(usize, crate::model::marker_tree::AnchorConfidence)> {
248        let marker = self.tree.get_marker(id.0)?;
249        match marker.marker_type {
250            crate::model::marker_tree::MarkerType::LineAnchor {
251                estimated_line,
252                confidence,
253            } => Some((estimated_line, confidence)),
254            _ => None,
255        }
256    }
257
258    /// Update a line anchor's line number and confidence
259    pub fn update_line_anchor(
260        &mut self,
261        id: MarkerId,
262        estimated_line: usize,
263        confidence: crate::model::marker_tree::AnchorConfidence,
264    ) -> bool {
265        self.tree
266            .update_line_anchor(id.0, estimated_line, confidence)
267    }
268
269    /// Query all line anchors in a byte range
270    pub fn query_line_anchors(
271        &self,
272        start: usize,
273        end: usize,
274    ) -> Vec<(MarkerId, usize, usize, usize)> {
275        self.tree
276            .query_line_anchors(start as u64, end as u64)
277            .into_iter()
278            .filter_map(|m| {
279                if let crate::model::marker_tree::MarkerType::LineAnchor {
280                    estimated_line, ..
281                } = m.marker_type
282                {
283                    Some((
284                        MarkerId(m.id),
285                        m.interval.start as usize,
286                        m.interval.end as usize,
287                        estimated_line,
288                    ))
289                } else {
290                    None
291                }
292            })
293            .collect()
294    }
295
296    /// Find the nearest line anchor before a given byte position
297    pub fn nearest_line_anchor_before(
298        &self,
299        byte_offset: usize,
300    ) -> Option<(MarkerId, usize, usize, usize)> {
301        // Query from 0 to byte_offset to get all anchors before
302        let anchors = self.query_line_anchors(0, byte_offset);
303        // Return the one closest to byte_offset
304        anchors.into_iter().max_by_key(|(_, start, _, _)| *start)
305    }
306
307    /// Find the nearest line anchor before a given line number
308    pub fn nearest_line_anchor_before_line(
309        &self,
310        line_num: usize,
311    ) -> Option<(MarkerId, usize, usize, usize)> {
312        // Query all anchors (we need to check line numbers, not byte positions)
313        // This is not optimal but simple - in practice we won't have many anchors
314        let all_anchors = self.query_line_anchors(0, usize::MAX);
315        all_anchors
316            .into_iter()
317            .filter(|(_, _, _, estimated_line)| *estimated_line <= line_num)
318            .max_by_key(|(_, _, _, estimated_line)| *estimated_line)
319    }
320}
321
322impl Default for MarkerList {
323    fn default() -> Self {
324        Self::new()
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331
332    #[test]
333    fn test_new_marker_list() {
334        let list = MarkerList::new();
335        assert_eq!(list.marker_count(), 0);
336        list.check_invariants().unwrap();
337    }
338
339    #[test]
340    fn test_create_marker_at_start() {
341        let mut list = MarkerList::new();
342
343        let m1 = list.create(0, true);
344        assert_eq!(list.marker_count(), 1);
345        assert_eq!(list.get_position(m1), Some(0));
346        list.check_invariants().unwrap();
347    }
348
349    #[test]
350    fn test_create_multiple_markers() {
351        let mut list = MarkerList::new();
352
353        let m1 = list.create(5, true);
354        let m2 = list.create(15, false);
355
356        assert_eq!(list.get_position(m1), Some(5));
357        assert_eq!(list.get_position(m2), Some(15));
358        list.check_invariants().unwrap();
359    }
360
361    #[test]
362    fn test_insert_before_marker() {
363        let mut list = MarkerList::new();
364
365        let m1 = list.create(10, true);
366        assert_eq!(list.get_position(m1), Some(10));
367
368        // Insert 5 bytes before marker
369        list.adjust_for_insert(5, 5);
370
371        // Marker should have moved forward
372        assert_eq!(list.get_position(m1), Some(15));
373        list.check_invariants().unwrap();
374    }
375
376    #[test]
377    fn test_insert_after_marker() {
378        let mut list = MarkerList::new();
379
380        let m1 = list.create(10, true);
381        assert_eq!(list.get_position(m1), Some(10));
382
383        // Insert 5 bytes after marker
384        list.adjust_for_insert(15, 5);
385
386        // Marker should stay at same position
387        assert_eq!(list.get_position(m1), Some(10));
388        list.check_invariants().unwrap();
389    }
390
391    #[test]
392    fn test_insert_at_marker_left_affinity() {
393        let mut list = MarkerList::new();
394
395        // Left affinity: marker stays before inserted text
396        let m1 = list.create(10, true);
397
398        // Insert at marker position
399        list.adjust_for_insert(10, 5);
400
401        // Note: IntervalTree treats zero-length markers as intervals.
402        // When inserting at position 10 where a [10,10] marker exists,
403        // the interval tree shifts it to [15,15] (standard interval tree behavior).
404        // This is different from the old Vec implementation but more consistent
405        // with interval tree semantics where intervals can expand.
406        assert_eq!(list.get_position(m1), Some(15));
407        list.check_invariants().unwrap();
408    }
409
410    #[test]
411    fn test_insert_at_marker_right_affinity() {
412        let mut list = MarkerList::new();
413
414        // Right affinity: marker moves after inserted text
415        let m1 = list.create(10, false);
416
417        // Insert at marker position
418        list.adjust_for_insert(10, 5);
419
420        // Marker should move to 15, insertion goes before
421        assert_eq!(list.get_position(m1), Some(15));
422        list.check_invariants().unwrap();
423    }
424
425    #[test]
426    fn test_delete_before_marker() {
427        let mut list = MarkerList::new();
428
429        let m1 = list.create(15, true);
430        assert_eq!(list.get_position(m1), Some(15));
431
432        // Delete 5 bytes before marker (at position 5)
433        list.adjust_for_delete(5, 5);
434
435        // Marker should move backward
436        assert_eq!(list.get_position(m1), Some(10));
437        list.check_invariants().unwrap();
438    }
439
440    #[test]
441    fn test_delete_after_marker() {
442        let mut list = MarkerList::new();
443
444        let m1 = list.create(10, true);
445        assert_eq!(list.get_position(m1), Some(10));
446
447        // Delete 5 bytes after marker (at position 15)
448        list.adjust_for_delete(15, 5);
449
450        // Marker should stay at same position
451        assert_eq!(list.get_position(m1), Some(10));
452        list.check_invariants().unwrap();
453    }
454
455    #[test]
456    fn test_delete_marker() {
457        let mut list = MarkerList::new();
458
459        let m1 = list.create(10, true);
460
461        // Delete at the marker position
462        list.adjust_for_delete(10, 5);
463
464        // IntervalTree clamps markers instead of deleting them
465        // Zero-length marker at position 10 gets clamped to position 10
466        assert_eq!(list.get_position(m1), Some(10));
467        assert_eq!(list.marker_count(), 1);
468        list.check_invariants().unwrap();
469    }
470
471    #[test]
472    fn test_delete_multiple_markers() {
473        let mut list = MarkerList::new();
474
475        let m1 = list.create(10, true);
476        let m2 = list.create(15, true);
477        let m3 = list.create(20, true);
478
479        // Delete range [8, 18) covering m1 and m2
480        list.adjust_for_delete(8, 10);
481
482        // IntervalTree clamps markers instead of deleting
483        // m1 at 10 gets clamped to 8, m2 at 15 gets clamped to 8, m3 at 20 moves to 10
484        assert_eq!(list.get_position(m1), Some(8)); // Clamped to deletion start
485        assert_eq!(list.get_position(m2), Some(8)); // Clamped to deletion start
486        assert_eq!(list.get_position(m3), Some(10)); // 20 - 10 = 10
487        assert_eq!(list.marker_count(), 3);
488        list.check_invariants().unwrap();
489    }
490
491    #[test]
492    fn test_complex_scenario() {
493        let mut list = MarkerList::new();
494
495        // Create markers at 10, 20, 30
496        let m1 = list.create(10, true);
497        let m2 = list.create(20, true);
498        let m3 = list.create(30, true);
499
500        // Insert at 15
501        list.adjust_for_insert(15, 5);
502        assert_eq!(list.get_position(m1), Some(10));
503        assert_eq!(list.get_position(m2), Some(25)); // 20 + 5
504        assert_eq!(list.get_position(m3), Some(35)); // 30 + 5
505
506        // Delete at 12, length 8 (delete range [12, 20))
507        // This removes part of the gap between m1 and m2, but not m2 itself
508        list.adjust_for_delete(12, 8);
509        assert_eq!(list.get_position(m1), Some(10)); // Before deletion
510        assert_eq!(list.get_position(m2), Some(17)); // 25 - 8 = 17
511        assert_eq!(list.get_position(m3), Some(27)); // 35 - 8 = 27
512
513        list.check_invariants().unwrap();
514    }
515
516    #[test]
517    fn test_marker_deletion_with_delete_method() {
518        let mut list = MarkerList::new();
519
520        let m1 = list.create(10, true);
521        let m2 = list.create(15, false);
522
523        // Delete m1
524        list.delete(m1);
525
526        assert_eq!(list.get_position(m1), None);
527        assert_eq!(list.get_position(m2), Some(15));
528        assert_eq!(list.marker_count(), 1);
529        list.check_invariants().unwrap();
530    }
531
532    // Property-based tests
533    #[cfg(test)]
534    mod property_tests {
535        use super::*;
536        use proptest::prelude::*;
537
538        /// Generate random edit operations
539        #[derive(Debug, Clone)]
540        enum EditOp {
541            Insert { position: usize, length: usize },
542            Delete { position: usize, length: usize },
543        }
544
545        fn arb_edit_op(max_buffer_size: usize) -> impl Strategy<Value = EditOp> {
546            prop_oneof![
547                (0..=max_buffer_size, 1..=50usize).prop_map(|(pos, len)| EditOp::Insert {
548                    position: pos,
549                    length: len
550                }),
551                (0..=max_buffer_size, 1..=20usize).prop_map(|(pos, len)| EditOp::Delete {
552                    position: pos,
553                    length: len
554                }),
555            ]
556        }
557
558        proptest! {
559            /// Invariants should hold after any sequence of operations
560            #[test]
561            fn prop_invariants_hold(
562                initial_positions in prop::collection::vec(0..1000usize, 1..10),
563                ops in prop::collection::vec(arb_edit_op(1000), 1..20)
564            ) {
565                let mut list = MarkerList::new();
566
567                // Filter out duplicate positions to avoid RefCell borrow conflicts
568                // when multiple markers at same position are adjusted
569                let mut unique_positions: Vec<usize> = initial_positions.clone();
570                unique_positions.sort_unstable();
571                unique_positions.dedup();
572
573                // Create some markers at various positions
574                let markers: Vec<_> = unique_positions
575                    .iter()
576                    .enumerate()
577                    .map(|(i, &pos)| list.create(pos, i % 2 == 0))
578                    .collect();
579
580                // Apply random operations
581                for op in ops {
582                    match op {
583                        EditOp::Insert { position, length } => {
584                            list.adjust_for_insert(position, length);
585                        }
586                        EditOp::Delete { position, length } => {
587                            if length > 0 {
588                                list.adjust_for_delete(position, length);
589                            }
590                        }
591                    }
592
593                    // Invariants must hold after every operation
594                    list.check_invariants().unwrap();
595                }
596
597                // All remaining markers should still exist
598                for marker in markers {
599                    // Just verify we can query positions
600                    let _ = list.get_position(marker);
601                }
602            }
603
604            /// Marker positions should be in the same order after edits
605            #[test]
606            fn prop_marker_ordering_preserved(
607                initial_spacing in 10..50usize,
608                ops in prop::collection::vec(arb_edit_op(500), 1..10)
609            ) {
610                let mut list = MarkerList::new();
611
612                // Create markers in order with given spacing
613                let markers: Vec<_> = (0..5)
614                    .map(|i| list.create(i * initial_spacing, true))
615                    .collect();
616
617                // Apply operations
618                for op in ops {
619                    match op {
620                        EditOp::Insert { position, length } => {
621                            list.adjust_for_insert(position, length);
622                        }
623                        EditOp::Delete { position, length } => {
624                            if length > 0 {
625                                list.adjust_for_delete(position, length);
626                            }
627                        }
628                    }
629                }
630
631                // Get positions of all markers AND their intervals for debugging
632                let positions: Vec<_> = markers
633                    .iter()
634                    .filter_map(|&m| list.get_position(m))
635                    .collect();
636
637                // Debug: Get full intervals (start, end) from tree
638                let intervals: Vec<_> = markers
639                    .iter()
640                    .filter_map(|&m| list.tree.get_position(m.0))
641                    .collect();
642
643                // Should still be in order (no inversions)
644                for window in positions.windows(2) {
645                    if window[0] > window[1] {
646                        tracing::trace!("Ordering violation detected!");
647                        tracing::trace!("  Positions: {:?}", positions);
648                        tracing::trace!("  Full intervals: {:?}", intervals);
649                        panic!("Marker ordering violated: {:?}", positions);
650                    }
651                }
652            }
653        }
654    }
655}