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}