1use crate::model::marker::{MarkerId, MarkerList};
2use ratatui::style::{Color, Style};
3use std::collections::HashMap;
4use std::ops::Range;
5
6pub use fresh_core::overlay::{OverlayHandle, OverlayNamespace};
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum OverlayFace {
12 Underline { color: Color, style: UnderlineStyle },
14 Background { color: Color },
16 Foreground { color: Color },
18 Style { style: Style },
20 ThemedStyle {
26 fallback_style: Style,
28 fg_theme: Option<String>,
30 bg_theme: Option<String>,
32 fg_on_collision_only: bool,
35 },
36}
37
38impl OverlayFace {
39 pub fn from_options(options: &fresh_core::api::OverlayOptions) -> Self {
44 use crate::view::theme::named_color_from_str;
45 use ratatui::style::Modifier;
46
47 let mut style = Style::default();
48
49 if let Some(ref fg) = options.fg {
50 if let Some((r, g, b)) = fg.as_rgb() {
51 style = style.fg(Color::Rgb(r, g, b));
52 } else if let Some(key) = fg.as_theme_key() {
53 if let Some(color) = named_color_from_str(key) {
54 style = style.fg(color);
55 }
56 }
57 }
58
59 if let Some(ref bg) = options.bg {
60 if let Some((r, g, b)) = bg.as_rgb() {
61 style = style.bg(Color::Rgb(r, g, b));
62 } else if let Some(key) = bg.as_theme_key() {
63 if let Some(color) = named_color_from_str(key) {
64 style = style.bg(color);
65 }
66 }
67 }
68
69 let mut modifiers = Modifier::empty();
70 if options.bold {
71 modifiers |= Modifier::BOLD;
72 }
73 if options.italic {
74 modifiers |= Modifier::ITALIC;
75 }
76 if options.underline {
77 modifiers |= Modifier::UNDERLINED;
78 }
79 if options.strikethrough {
80 modifiers |= Modifier::CROSSED_OUT;
81 }
82 if !modifiers.is_empty() {
83 style = style.add_modifier(modifiers);
84 }
85
86 let fg_theme = options
89 .fg
90 .as_ref()
91 .and_then(|c| c.as_theme_key())
92 .filter(|key| named_color_from_str(key).is_none())
93 .map(String::from);
94 let bg_theme = options
95 .bg
96 .as_ref()
97 .and_then(|c| c.as_theme_key())
98 .filter(|key| named_color_from_str(key).is_none())
99 .map(String::from);
100
101 if fg_theme.is_some() || bg_theme.is_some() {
102 OverlayFace::ThemedStyle {
103 fallback_style: style,
104 fg_theme,
105 bg_theme,
106 fg_on_collision_only: options.fg_on_collision_only,
107 }
108 } else {
109 OverlayFace::Style { style }
110 }
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum UnderlineStyle {
117 Straight,
119 Wavy,
121 Dotted,
123 Dashed,
125}
126
127pub type Priority = i32;
130
131#[derive(Debug, Clone)]
134pub struct Overlay {
135 pub handle: OverlayHandle,
137
138 pub namespace: Option<OverlayNamespace>,
140
141 pub start_marker: MarkerId,
143
144 pub end_marker: MarkerId,
146
147 pub face: OverlayFace,
149
150 pub priority: Priority,
152
153 pub message: Option<String>,
155
156 pub extend_to_line_end: bool,
159
160 pub url: Option<String>,
163
164 pub theme_key: Option<&'static str>,
168}
169
170impl Overlay {
171 pub fn new(marker_list: &mut MarkerList, range: Range<usize>, face: OverlayFace) -> Self {
180 let start_marker = marker_list.create(range.start, true); let end_marker = marker_list.create(range.end, false); Self {
184 handle: OverlayHandle::new(),
185 namespace: None,
186 start_marker,
187 end_marker,
188 face,
189 priority: 0,
190 message: None,
191 extend_to_line_end: false,
192 url: None,
193 theme_key: None,
194 }
195 }
196
197 pub fn with_namespace(
199 marker_list: &mut MarkerList,
200 range: Range<usize>,
201 face: OverlayFace,
202 namespace: OverlayNamespace,
203 ) -> Self {
204 let mut overlay = Self::new(marker_list, range, face);
205 overlay.namespace = Some(namespace);
206 overlay
207 }
208
209 pub fn with_priority(
211 marker_list: &mut MarkerList,
212 range: Range<usize>,
213 face: OverlayFace,
214 priority: Priority,
215 ) -> Self {
216 let mut overlay = Self::new(marker_list, range, face);
217 overlay.priority = priority;
218 overlay
219 }
220
221 pub fn with_message(mut self, message: String) -> Self {
223 self.message = Some(message);
224 self
225 }
226
227 pub fn with_priority_value(mut self, priority: Priority) -> Self {
229 self.priority = priority;
230 self
231 }
232
233 pub fn with_namespace_value(mut self, namespace: OverlayNamespace) -> Self {
235 self.namespace = Some(namespace);
236 self
237 }
238
239 pub fn with_extend_to_line_end(mut self, extend: bool) -> Self {
241 self.extend_to_line_end = extend;
242 self
243 }
244
245 pub fn with_theme_key(mut self, key: &'static str) -> Self {
247 self.theme_key = Some(key);
248 self
249 }
250
251 pub fn range(&self, marker_list: &MarkerList) -> Range<usize> {
254 let start = marker_list.get_position(self.start_marker).unwrap_or(0);
255 let end = marker_list.get_position(self.end_marker).unwrap_or(0);
256 start..end
257 }
258
259 pub fn contains(&self, position: usize, marker_list: &MarkerList) -> bool {
261 self.range(marker_list).contains(&position)
262 }
263
264 pub fn overlaps(&self, range: &Range<usize>, marker_list: &MarkerList) -> bool {
266 let self_range = self.range(marker_list);
267 self_range.start < range.end && range.start < self_range.end
268 }
269}
270
271#[derive(Debug, Clone)]
274pub struct OverlayManager {
275 overlays: Vec<Overlay>,
277 marker_to_idx: HashMap<MarkerId, usize>,
281}
282
283impl OverlayManager {
284 pub fn new() -> Self {
286 Self {
287 overlays: Vec::new(),
288 marker_to_idx: HashMap::new(),
289 }
290 }
291
292 pub fn add(&mut self, overlay: Overlay) -> OverlayHandle {
294 let handle = overlay.handle.clone();
295 let priority = overlay.priority;
299 let pos = self.overlays.partition_point(|o| o.priority <= priority);
300 self.overlays.insert(pos, overlay);
301 for (i, o) in self.overlays.iter().enumerate().skip(pos) {
305 self.marker_to_idx.insert(o.start_marker, i);
306 self.marker_to_idx.insert(o.end_marker, i);
307 }
308 handle
309 }
310
311 pub fn extend<I: IntoIterator<Item = Overlay>>(&mut self, overlays: I) {
318 self.overlays.extend(overlays);
319 self.overlays.sort_by_key(|o| o.priority);
320 self.rebuild_marker_index();
321 }
322
323 pub fn remove_by_handle(
325 &mut self,
326 handle: &OverlayHandle,
327 marker_list: &mut MarkerList,
328 ) -> bool {
329 if let Some(pos) = self.overlays.iter().position(|o| &o.handle == handle) {
330 let overlay = self.overlays.remove(pos);
331 self.marker_to_idx.remove(&overlay.start_marker);
332 self.marker_to_idx.remove(&overlay.end_marker);
333 for (i, o) in self.overlays.iter().enumerate().skip(pos) {
335 self.marker_to_idx.insert(o.start_marker, i);
336 self.marker_to_idx.insert(o.end_marker, i);
337 }
338 marker_list.delete(overlay.start_marker);
339 marker_list.delete(overlay.end_marker);
340 true
341 } else {
342 false
343 }
344 }
345
346 pub fn clear_namespace(&mut self, namespace: &OverlayNamespace, marker_list: &mut MarkerList) {
348 let mut indices: Vec<usize> = self
349 .overlays
350 .iter()
351 .enumerate()
352 .filter_map(|(i, o)| (o.namespace.as_ref() == Some(namespace)).then_some(i))
353 .collect();
354 if indices.is_empty() {
355 return;
356 }
357 indices.sort_unstable_by(|a, b| b.cmp(a));
358 for idx in indices {
359 self.swap_remove_at(idx, marker_list);
360 }
361 self.overlays.sort_by_key(|o| o.priority);
363 self.rebuild_marker_index();
364 }
365
366 pub fn replace_range_in_namespace(
371 &mut self,
372 namespace: &OverlayNamespace,
373 range: &Range<usize>,
374 mut new_overlays: Vec<Overlay>,
375 marker_list: &mut MarkerList,
376 ) {
377 if range.start < range.end {
381 let hits = marker_list.query_range(range.start, range.end);
382 let mut candidates: Vec<usize> = hits
383 .iter()
384 .filter_map(|(mid, _, _)| self.marker_to_idx.get(mid).copied())
385 .collect();
386 candidates.sort_unstable();
387 candidates.dedup();
388 let mut to_remove: Vec<usize> = candidates
389 .into_iter()
390 .filter(|&idx| {
391 let o = &self.overlays[idx];
392 if o.namespace.as_ref() != Some(namespace) {
393 return false;
394 }
395 let start = marker_list.get_position(o.start_marker).unwrap_or(0);
396 let end = marker_list.get_position(o.end_marker).unwrap_or(0);
397 start < range.end && range.start < end
398 })
399 .collect();
400 to_remove.sort_unstable_by(|a, b| b.cmp(a));
401 for idx in to_remove {
402 self.swap_remove_at(idx, marker_list);
403 }
404 }
405
406 if !new_overlays.is_empty() {
407 self.overlays.append(&mut new_overlays);
408 }
409 self.overlays.sort_by_key(|o| o.priority);
410 self.rebuild_marker_index();
411 }
412
413 pub fn remove_in_range(&mut self, range: &Range<usize>, marker_list: &mut MarkerList) {
415 if range.start >= range.end {
423 return;
424 }
425 let hits = marker_list.query_range(range.start, range.end);
426 if hits.is_empty() {
427 return;
428 }
429 let mut candidates: Vec<usize> = hits
430 .iter()
431 .filter_map(|(mid, _, _)| self.marker_to_idx.get(mid).copied())
432 .collect();
433 candidates.sort_unstable();
434 candidates.dedup();
435
436 let mut to_remove: Vec<usize> = candidates
437 .into_iter()
438 .filter(|&idx| {
439 let o = &self.overlays[idx];
440 let start = marker_list.get_position(o.start_marker).unwrap_or(0);
441 let end = marker_list.get_position(o.end_marker).unwrap_or(0);
442 start < range.end && range.start < end
443 })
444 .collect();
445 if to_remove.is_empty() {
446 return;
447 }
448 to_remove.sort_unstable_by(|a, b| b.cmp(a));
449 for idx in to_remove {
450 self.swap_remove_at(idx, marker_list);
451 }
452 self.overlays.sort_by_key(|o| o.priority);
454 self.rebuild_marker_index();
455 }
456
457 pub fn clear(&mut self, marker_list: &mut MarkerList) {
459 for overlay in &self.overlays {
460 marker_list.delete(overlay.start_marker);
461 marker_list.delete(overlay.end_marker);
462 }
463 self.overlays.clear();
464 self.marker_to_idx.clear();
465 }
466
467 fn swap_remove_at(&mut self, idx: usize, marker_list: &mut MarkerList) {
471 let removed = self.overlays.swap_remove(idx);
472 self.marker_to_idx.remove(&removed.start_marker);
473 self.marker_to_idx.remove(&removed.end_marker);
474 marker_list.delete(removed.start_marker);
475 marker_list.delete(removed.end_marker);
476 if let Some(moved) = self.overlays.get(idx) {
477 self.marker_to_idx.insert(moved.start_marker, idx);
478 self.marker_to_idx.insert(moved.end_marker, idx);
479 }
480 }
481
482 fn rebuild_marker_index(&mut self) {
485 self.marker_to_idx.clear();
486 for (i, o) in self.overlays.iter().enumerate() {
487 self.marker_to_idx.insert(o.start_marker, i);
488 self.marker_to_idx.insert(o.end_marker, i);
489 }
490 }
491
492 pub fn at_position(&self, position: usize, marker_list: &MarkerList) -> Vec<&Overlay> {
494 self.overlays
495 .iter()
496 .filter(|o| {
497 let range = o.range(marker_list);
498 range.contains(&position)
499 })
500 .collect()
501 }
502
503 pub fn in_range(&self, range: &Range<usize>, marker_list: &MarkerList) -> Vec<&Overlay> {
505 self.overlays
506 .iter()
507 .filter(|o| o.overlaps(range, marker_list))
508 .collect()
509 }
510
511 pub fn query_viewport(
520 &self,
521 start: usize,
522 end: usize,
523 marker_list: &MarkerList,
524 ) -> Vec<(&Overlay, Range<usize>)> {
525 use std::collections::HashMap;
526
527 let visible_markers = marker_list.query_range(start, end);
530
531 let marker_positions: HashMap<_, _> = visible_markers
533 .into_iter()
534 .map(|(id, start, _end)| (id, start))
535 .collect();
536
537 self.overlays
543 .iter()
544 .filter_map(|overlay| {
545 let start_in_vp = marker_positions.get(&overlay.start_marker).copied();
546 let end_in_vp = marker_positions.get(&overlay.end_marker).copied();
547
548 if start_in_vp.is_none() && end_in_vp.is_none() {
551 return None;
552 }
553
554 let start_pos =
556 start_in_vp.or_else(|| marker_list.get_position(overlay.start_marker))?;
557 let end_pos = end_in_vp.or_else(|| marker_list.get_position(overlay.end_marker))?;
558
559 let range = start_pos..end_pos;
560
561 let included = if range.start == range.end {
566 range.start >= start && range.start <= end
567 } else {
568 range.start < end && range.end > start
569 };
570
571 if included {
572 Some((overlay, range))
573 } else {
574 None
575 }
576 })
577 .collect()
578 }
579
580 pub fn get_by_handle(&self, handle: &OverlayHandle) -> Option<&Overlay> {
582 self.overlays.iter().find(|o| &o.handle == handle)
583 }
584
585 pub fn get_by_handle_mut(&mut self, handle: &OverlayHandle) -> Option<&mut Overlay> {
587 self.overlays.iter_mut().find(|o| &o.handle == handle)
588 }
589
590 pub fn len(&self) -> usize {
592 self.overlays.len()
593 }
594
595 pub fn is_empty(&self) -> bool {
597 self.overlays.is_empty()
598 }
599
600 pub fn all(&self) -> &[Overlay] {
602 &self.overlays
603 }
604
605 #[cfg(test)]
609 fn check_invariants(&self) {
610 assert_eq!(
611 self.marker_to_idx.len(),
612 self.overlays.len() * 2,
613 "marker_to_idx size != 2 * overlays.len()"
614 );
615 for (i, o) in self.overlays.iter().enumerate() {
616 assert_eq!(
617 self.marker_to_idx.get(&o.start_marker).copied(),
618 Some(i),
619 "start_marker {:?} of overlay {} mismapped",
620 o.start_marker,
621 i,
622 );
623 assert_eq!(
624 self.marker_to_idx.get(&o.end_marker).copied(),
625 Some(i),
626 "end_marker {:?} of overlay {} mismapped",
627 o.end_marker,
628 i,
629 );
630 }
631 }
635
636 #[cfg(test)]
638 fn assert_priority_sorted(&self) {
639 for w in self.overlays.windows(2) {
640 assert!(
641 w[0].priority <= w[1].priority,
642 "priority order broken: {} after {}",
643 w[1].priority,
644 w[0].priority,
645 );
646 }
647 }
648}
649
650impl Default for OverlayManager {
651 fn default() -> Self {
652 Self::new()
653 }
654}
655
656impl Overlay {
658 pub fn error(
660 marker_list: &mut MarkerList,
661 range: Range<usize>,
662 message: Option<String>,
663 ) -> Self {
664 let mut overlay = Self::with_priority(
665 marker_list,
666 range,
667 OverlayFace::Underline {
668 color: Color::Red,
669 style: UnderlineStyle::Wavy,
670 },
671 10, );
673 overlay.message = message;
674 overlay
675 }
676
677 pub fn warning(
679 marker_list: &mut MarkerList,
680 range: Range<usize>,
681 message: Option<String>,
682 ) -> Self {
683 let mut overlay = Self::with_priority(
684 marker_list,
685 range,
686 OverlayFace::Underline {
687 color: Color::Yellow,
688 style: UnderlineStyle::Wavy,
689 },
690 5, );
692 overlay.message = message;
693 overlay
694 }
695
696 pub fn info(
698 marker_list: &mut MarkerList,
699 range: Range<usize>,
700 message: Option<String>,
701 ) -> Self {
702 let mut overlay = Self::with_priority(
703 marker_list,
704 range,
705 OverlayFace::Underline {
706 color: Color::Blue,
707 style: UnderlineStyle::Wavy,
708 },
709 3, );
711 overlay.message = message;
712 overlay
713 }
714
715 pub fn hint(
717 marker_list: &mut MarkerList,
718 range: Range<usize>,
719 message: Option<String>,
720 ) -> Self {
721 let mut overlay = Self::with_priority(
722 marker_list,
723 range,
724 OverlayFace::Underline {
725 color: Color::Gray,
726 style: UnderlineStyle::Dotted,
727 },
728 1, );
730 overlay.message = message;
731 overlay
732 }
733
734 pub fn selection(marker_list: &mut MarkerList, range: Range<usize>) -> Self {
736 let mut overlay = Self::with_priority(
737 marker_list,
738 range,
739 OverlayFace::Background {
740 color: Color::Rgb(38, 79, 120), },
742 -10, );
744 overlay.theme_key = Some("editor.selection_bg");
745 overlay
746 }
747
748 pub fn search_match(marker_list: &mut MarkerList, range: Range<usize>) -> Self {
750 let mut overlay = Self::with_priority(
751 marker_list,
752 range,
753 OverlayFace::Background {
754 color: Color::Rgb(72, 72, 0), },
756 -5, );
758 overlay.theme_key = Some("search.match_bg");
759 overlay
760 }
761}
762
763#[cfg(test)]
764mod tests {
765 use super::*;
766
767 #[test]
768 fn test_overlay_creation_with_markers() {
769 let mut marker_list = MarkerList::new();
770 marker_list.set_buffer_size(100);
771
772 let overlay = Overlay::new(
773 &mut marker_list,
774 5..10,
775 OverlayFace::Background { color: Color::Red },
776 );
777
778 assert_eq!(marker_list.get_position(overlay.start_marker), Some(5));
779 assert_eq!(marker_list.get_position(overlay.end_marker), Some(10));
780 assert_eq!(overlay.range(&marker_list), 5..10);
781 }
782
783 #[test]
784 fn test_overlay_adjusts_with_insert() {
785 let mut marker_list = MarkerList::new();
786 marker_list.set_buffer_size(100);
787
788 let overlay = Overlay::new(
789 &mut marker_list,
790 10..20,
791 OverlayFace::Background { color: Color::Red },
792 );
793
794 marker_list.adjust_for_insert(5, 10);
796
797 assert_eq!(overlay.range(&marker_list), 20..30);
799 }
800
801 #[test]
802 fn test_overlay_adjusts_with_delete() {
803 let mut marker_list = MarkerList::new();
804 marker_list.set_buffer_size(100);
805
806 let overlay = Overlay::new(
807 &mut marker_list,
808 20..30,
809 OverlayFace::Background { color: Color::Red },
810 );
811
812 marker_list.adjust_for_delete(5, 10);
814
815 assert_eq!(overlay.range(&marker_list), 10..20);
817 }
818
819 #[test]
820 fn test_overlay_manager_add_remove() {
821 let mut marker_list = MarkerList::new();
822 marker_list.set_buffer_size(100);
823 let mut manager = OverlayManager::new();
824
825 let overlay = Overlay::new(
826 &mut marker_list,
827 5..10,
828 OverlayFace::Background { color: Color::Red },
829 );
830
831 let handle = manager.add(overlay);
832 assert_eq!(manager.len(), 1);
833
834 manager.remove_by_handle(&handle, &mut marker_list);
835 assert_eq!(manager.len(), 0);
836 }
837
838 #[test]
839 fn test_overlay_namespace_clear() {
840 let mut marker_list = MarkerList::new();
841 marker_list.set_buffer_size(100);
842 let mut manager = OverlayManager::new();
843
844 let ns = OverlayNamespace::from_string("todo".to_string());
845
846 let overlay1 = Overlay::with_namespace(
848 &mut marker_list,
849 5..10,
850 OverlayFace::Background { color: Color::Red },
851 ns.clone(),
852 );
853 let overlay2 = Overlay::with_namespace(
854 &mut marker_list,
855 15..20,
856 OverlayFace::Background { color: Color::Blue },
857 ns.clone(),
858 );
859 let overlay3 = Overlay::new(
861 &mut marker_list,
862 25..30,
863 OverlayFace::Background {
864 color: Color::Green,
865 },
866 );
867
868 manager.add(overlay1);
869 manager.add(overlay2);
870 manager.add(overlay3);
871 assert_eq!(manager.len(), 3);
872
873 manager.clear_namespace(&ns, &mut marker_list);
875 assert_eq!(manager.len(), 1); }
877
878 #[test]
879 fn test_overlay_priority_sorting() {
880 let mut marker_list = MarkerList::new();
881 marker_list.set_buffer_size(100);
882 let mut manager = OverlayManager::new();
883
884 manager.add(Overlay::with_priority(
885 &mut marker_list,
886 5..10,
887 OverlayFace::Background { color: Color::Red },
888 10,
889 ));
890 manager.add(Overlay::with_priority(
891 &mut marker_list,
892 5..10,
893 OverlayFace::Background { color: Color::Blue },
894 5,
895 ));
896 manager.add(Overlay::with_priority(
897 &mut marker_list,
898 5..10,
899 OverlayFace::Background {
900 color: Color::Green,
901 },
902 15,
903 ));
904
905 let overlays = manager.at_position(7, &marker_list);
906 assert_eq!(overlays.len(), 3);
907 assert_eq!(overlays[0].priority, 5);
909 assert_eq!(overlays[1].priority, 10);
910 assert_eq!(overlays[2].priority, 15);
911 }
912
913 #[test]
914 fn test_overlay_contains_and_overlaps() {
915 let mut marker_list = MarkerList::new();
916 marker_list.set_buffer_size(100);
917
918 let overlay = Overlay::new(
919 &mut marker_list,
920 10..20,
921 OverlayFace::Background { color: Color::Red },
922 );
923
924 assert!(!overlay.contains(9, &marker_list));
925 assert!(overlay.contains(10, &marker_list));
926 assert!(overlay.contains(15, &marker_list));
927 assert!(overlay.contains(19, &marker_list));
928 assert!(!overlay.contains(20, &marker_list));
929
930 assert!(!overlay.overlaps(&(0..10), &marker_list));
931 assert!(overlay.overlaps(&(5..15), &marker_list));
932 assert!(overlay.overlaps(&(15..25), &marker_list));
933 assert!(!overlay.overlaps(&(20..30), &marker_list));
934 }
935
936 #[test]
937 fn test_overlay_remove_in_range_keeps_only_disjoint() {
938 let mut marker_list = MarkerList::new();
939 marker_list.set_buffer_size(200);
940 let mut manager = OverlayManager::new();
941
942 manager.add(Overlay::new(
943 &mut marker_list,
944 0..5,
945 OverlayFace::Background { color: Color::Red },
946 ));
947 manager.add(Overlay::new(
948 &mut marker_list,
949 10..20,
950 OverlayFace::Background { color: Color::Blue },
951 ));
952 manager.add(Overlay::new(
953 &mut marker_list,
954 30..40,
955 OverlayFace::Background {
956 color: Color::Green,
957 },
958 ));
959 manager.add(Overlay::new(
960 &mut marker_list,
961 50..60,
962 OverlayFace::Background {
963 color: Color::Yellow,
964 },
965 ));
966
967 manager.remove_in_range(&(15..35), &mut marker_list);
969
970 let kept: Vec<_> = manager
971 .all()
972 .iter()
973 .map(|o| o.range(&marker_list))
974 .collect();
975 assert_eq!(kept, vec![0..5, 50..60]);
976 }
977
978 #[test]
979 fn test_overlay_remove_in_range_deletes_markers() {
980 let mut marker_list = MarkerList::new();
981 marker_list.set_buffer_size(100);
982 let mut manager = OverlayManager::new();
983
984 let overlay = Overlay::new(
985 &mut marker_list,
986 10..20,
987 OverlayFace::Background { color: Color::Red },
988 );
989 let start_id = overlay.start_marker;
990 let end_id = overlay.end_marker;
991 manager.add(overlay);
992
993 manager.remove_in_range(&(0..50), &mut marker_list);
994
995 assert_eq!(manager.len(), 0);
996 assert_eq!(marker_list.get_position(start_id), None);
997 assert_eq!(marker_list.get_position(end_id), None);
998 }
999
1000 #[test]
1001 fn test_overlay_remove_in_range_endpoint_semantics() {
1002 let mut marker_list = MarkerList::new();
1004 marker_list.set_buffer_size(100);
1005 let mut manager = OverlayManager::new();
1006
1007 manager.add(Overlay::new(
1008 &mut marker_list,
1009 10..20,
1010 OverlayFace::Background { color: Color::Red },
1011 ));
1012
1013 manager.remove_in_range(&(20..30), &mut marker_list);
1014 assert_eq!(manager.len(), 1);
1015 manager.remove_in_range(&(0..10), &mut marker_list);
1016 assert_eq!(manager.len(), 1);
1017 manager.remove_in_range(&(19..21), &mut marker_list);
1018 assert_eq!(manager.len(), 0);
1019 }
1020
1021 #[test]
1030 fn perf_full_buffer_rebuild_pass() {
1031 const LINES: usize = 500;
1032 const LINE_BYTES: usize = 50;
1033 const OVERLAYS_PER_LINE: usize = 5;
1034
1035 let mut marker_list = MarkerList::new();
1036 marker_list.set_buffer_size(LINES * LINE_BYTES);
1037 let mut manager = OverlayManager::new();
1038
1039 let overlay_byte = |line: usize, k: usize| -> usize {
1040 line * LINE_BYTES + k * (LINE_BYTES / OVERLAYS_PER_LINE)
1041 };
1042 let make_overlay = |ml: &mut MarkerList, line: usize, k: usize| {
1043 let s = overlay_byte(line, k);
1044 Overlay::new(
1045 ml,
1046 s..(s + 2),
1047 OverlayFace::Background { color: Color::Red },
1048 )
1049 };
1050
1051 for line in 0..LINES {
1053 for k in 0..OVERLAYS_PER_LINE {
1054 let o = make_overlay(&mut marker_list, line, k);
1055 manager.add(o);
1056 }
1057 }
1058 let initial = LINES * OVERLAYS_PER_LINE;
1059
1060 let start = std::time::Instant::now();
1062 for line in 0..LINES {
1063 let line_range = (line * LINE_BYTES)..((line + 1) * LINE_BYTES);
1064 manager.remove_in_range(&line_range, &mut marker_list);
1065 for k in 0..OVERLAYS_PER_LINE {
1066 let o = make_overlay(&mut marker_list, line, k);
1067 manager.add(o);
1068 }
1069 }
1070 let elapsed = start.elapsed();
1071
1072 eprintln!(
1073 "[perf] overlay full-buffer rebuild ({LINES} lines, {} entries steady): \
1074 {:?} total, {:?}/line",
1075 initial,
1076 elapsed,
1077 elapsed / LINES as u32,
1078 );
1079 assert_eq!(manager.len(), initial);
1080 }
1081
1082 mod proptests {
1083 use super::*;
1084 use proptest::prelude::*;
1085
1086 #[derive(Debug, Clone)]
1087 enum Op {
1088 Add {
1089 start: usize,
1090 len: usize,
1091 priority: i32,
1092 ns_idx: u8,
1093 },
1094 RemoveInRange {
1095 start: usize,
1096 end: usize,
1097 },
1098 ClearNamespace {
1099 ns_idx: u8,
1100 },
1101 ReplaceRange {
1102 start: usize,
1103 end: usize,
1104 ns_idx: u8,
1105 new_overlays: Vec<(usize, usize, i32)>,
1108 },
1109 }
1110
1111 const BUFFER_SIZE: usize = 200;
1112 const MAX_OVERLAY_LEN: usize = 4;
1113 const MIN_QUERY_LEN: usize = MAX_OVERLAY_LEN + 1;
1114
1115 fn arb_overlay_spec() -> impl Strategy<Value = (usize, usize, i32)> {
1116 (
1117 0..(BUFFER_SIZE - MAX_OVERLAY_LEN),
1118 1..=MAX_OVERLAY_LEN,
1119 -5i32..=5i32,
1120 )
1121 }
1122
1123 fn arb_op() -> impl Strategy<Value = Op> {
1124 prop_oneof![
1125 3 => arb_overlay_spec().prop_flat_map(|(start, len, priority)| {
1126 (Just(start), Just(len), Just(priority), 0u8..3u8)
1127 }).prop_map(|(start, len, priority, ns_idx)| Op::Add {
1128 start, len, priority, ns_idx,
1129 }),
1130 2 => (0..BUFFER_SIZE, MIN_QUERY_LEN..=BUFFER_SIZE)
1131 .prop_map(|(start, qlen)| {
1132 let s = start.min(BUFFER_SIZE - 1);
1133 let e = (s + qlen).min(BUFFER_SIZE);
1134 Op::RemoveInRange { start: s, end: e }
1135 }),
1136 1 => (0u8..3u8).prop_map(|ns_idx| Op::ClearNamespace { ns_idx }),
1137 1 => (
1138 0..BUFFER_SIZE,
1139 MIN_QUERY_LEN..=BUFFER_SIZE,
1140 0u8..3u8,
1141 prop::collection::vec(arb_overlay_spec(), 0..4),
1142 )
1143 .prop_map(|(start, qlen, ns_idx, new_overlays)| {
1144 let s = start.min(BUFFER_SIZE - 1);
1145 let e = (s + qlen).min(BUFFER_SIZE);
1146 Op::ReplaceRange { start: s, end: e, ns_idx, new_overlays }
1147 }),
1148 ]
1149 }
1150
1151 fn nsf(idx: u8) -> OverlayNamespace {
1152 OverlayNamespace::from_string(format!("ns{idx}"))
1153 }
1154
1155 proptest! {
1156 #[test]
1164 fn prop_marker_index_consistent(ops in prop::collection::vec(arb_op(), 0..30)) {
1165 let mut marker_list = MarkerList::new();
1166 marker_list.set_buffer_size(BUFFER_SIZE);
1167 let mut manager = OverlayManager::new();
1168
1169 for op in ops {
1170 match op {
1171 Op::Add { start, len, priority, ns_idx } => {
1172 let o = Overlay::with_namespace(
1173 &mut marker_list,
1174 start..(start + len),
1175 OverlayFace::Background { color: Color::Red },
1176 nsf(ns_idx),
1177 );
1178 let mut o = o;
1179 o.priority = priority;
1180 manager.add(o);
1181 manager.check_invariants();
1182 manager.assert_priority_sorted();
1183 }
1184 Op::RemoveInRange { start, end } => {
1185 manager.remove_in_range(&(start..end), &mut marker_list);
1186 for (o, rng) in manager.query_viewport(start, end, &marker_list) {
1187 let overlaps = rng.start < end && start < rng.end;
1188 prop_assert!(
1189 !overlaps,
1190 "overlay {:?} (handle {:?}) survived remove_in_range({start}..{end})",
1191 rng, o.handle,
1192 );
1193 }
1194 manager.check_invariants();
1195 }
1196 Op::ClearNamespace { ns_idx } => {
1197 manager.clear_namespace(&nsf(ns_idx), &mut marker_list);
1198 manager.check_invariants();
1199 manager.assert_priority_sorted();
1200 }
1201 Op::ReplaceRange { start, end, ns_idx, new_overlays } => {
1202 let new: Vec<Overlay> = new_overlays.into_iter().map(|(s, l, p)| {
1203 let mut o = Overlay::with_namespace(
1204 &mut marker_list,
1205 s..(s + l),
1206 OverlayFace::Background { color: Color::Blue },
1207 nsf(ns_idx),
1208 );
1209 o.priority = p;
1210 o
1211 }).collect();
1212 manager.replace_range_in_namespace(
1213 &nsf(ns_idx),
1214 &(start..end),
1215 new,
1216 &mut marker_list,
1217 );
1218 manager.check_invariants();
1219 manager.assert_priority_sorted();
1220 }
1221 }
1222 }
1223 }
1224 }
1225 }
1226}