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