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