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}