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_namespace_fixed_end(
217 marker_list: &mut MarkerList,
218 range: Range<usize>,
219 face: OverlayFace,
220 namespace: OverlayNamespace,
221 ) -> Self {
222 let start_marker = marker_list.create(range.start, true); let end_marker = marker_list.create_left_gravity(range.end);
224
225 Self {
226 handle: OverlayHandle::new(),
227 namespace: Some(namespace),
228 start_marker,
229 end_marker,
230 face,
231 priority: 0,
232 message: None,
233 extend_to_line_end: false,
234 url: None,
235 theme_key: None,
236 }
237 }
238
239 pub fn with_priority(
241 marker_list: &mut MarkerList,
242 range: Range<usize>,
243 face: OverlayFace,
244 priority: Priority,
245 ) -> Self {
246 let mut overlay = Self::new(marker_list, range, face);
247 overlay.priority = priority;
248 overlay
249 }
250
251 pub fn with_message(mut self, message: String) -> Self {
253 self.message = Some(message);
254 self
255 }
256
257 pub fn with_priority_value(mut self, priority: Priority) -> Self {
259 self.priority = priority;
260 self
261 }
262
263 pub fn with_namespace_value(mut self, namespace: OverlayNamespace) -> Self {
265 self.namespace = Some(namespace);
266 self
267 }
268
269 pub fn with_extend_to_line_end(mut self, extend: bool) -> Self {
271 self.extend_to_line_end = extend;
272 self
273 }
274
275 pub fn with_theme_key(mut self, key: &'static str) -> Self {
277 self.theme_key = Some(key);
278 self
279 }
280
281 pub fn range(&self, marker_list: &MarkerList) -> Range<usize> {
284 let start = marker_list.get_position(self.start_marker).unwrap_or(0);
285 let end = marker_list.get_position(self.end_marker).unwrap_or(0);
286 start..end
287 }
288
289 pub fn contains(&self, position: usize, marker_list: &MarkerList) -> bool {
291 self.range(marker_list).contains(&position)
292 }
293
294 pub fn overlaps(&self, range: &Range<usize>, marker_list: &MarkerList) -> bool {
296 let self_range = self.range(marker_list);
297 self_range.start < range.end && range.start < self_range.end
298 }
299}
300
301#[derive(Debug, Clone)]
304pub struct OverlayManager {
305 overlays: Vec<Overlay>,
307 marker_to_idx: HashMap<MarkerId, usize>,
311}
312
313impl OverlayManager {
314 pub fn new() -> Self {
316 Self {
317 overlays: Vec::new(),
318 marker_to_idx: HashMap::new(),
319 }
320 }
321
322 pub fn add(&mut self, overlay: Overlay) -> OverlayHandle {
324 let handle = overlay.handle.clone();
325 let priority = overlay.priority;
329 let pos = self.overlays.partition_point(|o| o.priority <= priority);
330 self.overlays.insert(pos, overlay);
331 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 handle
339 }
340
341 pub fn extend<I: IntoIterator<Item = Overlay>>(&mut self, overlays: I) {
348 self.overlays.extend(overlays);
349 self.overlays.sort_by_key(|o| o.priority);
350 self.rebuild_marker_index();
351 }
352
353 pub fn remove_by_handle(
355 &mut self,
356 handle: &OverlayHandle,
357 marker_list: &mut MarkerList,
358 ) -> bool {
359 if let Some(pos) = self.overlays.iter().position(|o| &o.handle == handle) {
360 let overlay = self.overlays.remove(pos);
361 self.marker_to_idx.remove(&overlay.start_marker);
362 self.marker_to_idx.remove(&overlay.end_marker);
363 for (i, o) in self.overlays.iter().enumerate().skip(pos) {
365 self.marker_to_idx.insert(o.start_marker, i);
366 self.marker_to_idx.insert(o.end_marker, i);
367 }
368 marker_list.delete(overlay.start_marker);
369 marker_list.delete(overlay.end_marker);
370 true
371 } else {
372 false
373 }
374 }
375
376 pub fn clear_namespace(&mut self, namespace: &OverlayNamespace, marker_list: &mut MarkerList) {
378 let mut indices: Vec<usize> = self
379 .overlays
380 .iter()
381 .enumerate()
382 .filter_map(|(i, o)| (o.namespace.as_ref() == Some(namespace)).then_some(i))
383 .collect();
384 if indices.is_empty() {
385 return;
386 }
387 indices.sort_unstable_by(|a, b| b.cmp(a));
388 for idx in indices {
389 self.swap_remove_at(idx, marker_list);
390 }
391 self.overlays.sort_by_key(|o| o.priority);
393 self.rebuild_marker_index();
394 }
395
396 pub fn replace_range_in_namespace(
401 &mut self,
402 namespace: &OverlayNamespace,
403 range: &Range<usize>,
404 mut new_overlays: Vec<Overlay>,
405 marker_list: &mut MarkerList,
406 ) {
407 if range.start < range.end {
411 let hits = marker_list.query_range(range.start, range.end);
412 let mut candidates: Vec<usize> = hits
413 .iter()
414 .filter_map(|(mid, _, _)| self.marker_to_idx.get(mid).copied())
415 .collect();
416 candidates.sort_unstable();
417 candidates.dedup();
418 let mut to_remove: Vec<usize> = candidates
419 .into_iter()
420 .filter(|&idx| {
421 let o = &self.overlays[idx];
422 if o.namespace.as_ref() != Some(namespace) {
423 return false;
424 }
425 let start = marker_list.get_position(o.start_marker).unwrap_or(0);
426 let end = marker_list.get_position(o.end_marker).unwrap_or(0);
427 if start < end {
428 start < range.end && range.start < end
430 } else {
431 let lo = start.min(end);
441 let hi = start.max(end);
442 lo <= range.end && range.start <= hi
443 }
444 })
445 .collect();
446 to_remove.sort_unstable_by(|a, b| b.cmp(a));
447 for idx in to_remove {
448 self.swap_remove_at(idx, marker_list);
449 }
450 }
451
452 if !new_overlays.is_empty() {
453 self.overlays.append(&mut new_overlays);
454 }
455 self.overlays.sort_by_key(|o| o.priority);
456 self.rebuild_marker_index();
457 }
458
459 pub fn remove_in_range(&mut self, range: &Range<usize>, marker_list: &mut MarkerList) {
461 if range.start >= range.end {
469 return;
470 }
471 let hits = marker_list.query_range(range.start, range.end);
472 if hits.is_empty() {
473 return;
474 }
475 let mut candidates: Vec<usize> = hits
476 .iter()
477 .filter_map(|(mid, _, _)| self.marker_to_idx.get(mid).copied())
478 .collect();
479 candidates.sort_unstable();
480 candidates.dedup();
481
482 let mut to_remove: Vec<usize> = candidates
483 .into_iter()
484 .filter(|&idx| {
485 let o = &self.overlays[idx];
486 let start = marker_list.get_position(o.start_marker).unwrap_or(0);
487 let end = marker_list.get_position(o.end_marker).unwrap_or(0);
488 start < range.end && range.start < end
489 })
490 .collect();
491 if to_remove.is_empty() {
492 return;
493 }
494 to_remove.sort_unstable_by(|a, b| b.cmp(a));
495 for idx in to_remove {
496 self.swap_remove_at(idx, marker_list);
497 }
498 self.overlays.sort_by_key(|o| o.priority);
500 self.rebuild_marker_index();
501 }
502
503 pub fn remove_in_range_for_namespace(
509 &mut self,
510 range: &Range<usize>,
511 namespace: &OverlayNamespace,
512 marker_list: &mut MarkerList,
513 ) {
514 if range.start >= range.end {
515 return;
516 }
517 let hits = marker_list.query_range(range.start, range.end);
518 if hits.is_empty() {
519 return;
520 }
521 let mut candidates: Vec<usize> = hits
522 .iter()
523 .filter_map(|(mid, _, _)| self.marker_to_idx.get(mid).copied())
524 .collect();
525 candidates.sort_unstable();
526 candidates.dedup();
527
528 let mut to_remove: Vec<usize> = candidates
529 .into_iter()
530 .filter(|&idx| {
531 let o = &self.overlays[idx];
532 if o.namespace.as_ref() != Some(namespace) {
533 return false;
534 }
535 let start = marker_list.get_position(o.start_marker).unwrap_or(0);
536 let end = marker_list.get_position(o.end_marker).unwrap_or(0);
537 start < range.end && range.start < end
538 })
539 .collect();
540 if to_remove.is_empty() {
541 return;
542 }
543 to_remove.sort_unstable_by(|a, b| b.cmp(a));
544 for idx in to_remove {
545 self.swap_remove_at(idx, marker_list);
546 }
547 self.overlays.sort_by_key(|o| o.priority);
549 self.rebuild_marker_index();
550 }
551
552 pub fn clear(&mut self, marker_list: &mut MarkerList) {
554 for overlay in &self.overlays {
555 marker_list.delete(overlay.start_marker);
556 marker_list.delete(overlay.end_marker);
557 }
558 self.overlays.clear();
559 self.marker_to_idx.clear();
560 }
561
562 fn swap_remove_at(&mut self, idx: usize, marker_list: &mut MarkerList) {
566 let removed = self.overlays.swap_remove(idx);
567 self.marker_to_idx.remove(&removed.start_marker);
568 self.marker_to_idx.remove(&removed.end_marker);
569 marker_list.delete(removed.start_marker);
570 marker_list.delete(removed.end_marker);
571 if let Some(moved) = self.overlays.get(idx) {
572 self.marker_to_idx.insert(moved.start_marker, idx);
573 self.marker_to_idx.insert(moved.end_marker, idx);
574 }
575 }
576
577 fn rebuild_marker_index(&mut self) {
580 self.marker_to_idx.clear();
581 for (i, o) in self.overlays.iter().enumerate() {
582 self.marker_to_idx.insert(o.start_marker, i);
583 self.marker_to_idx.insert(o.end_marker, i);
584 }
585 }
586
587 pub fn at_position(&self, position: usize, marker_list: &MarkerList) -> Vec<&Overlay> {
589 self.overlays
590 .iter()
591 .filter(|o| {
592 let range = o.range(marker_list);
593 range.contains(&position)
594 })
595 .collect()
596 }
597
598 pub fn in_range(&self, range: &Range<usize>, marker_list: &MarkerList) -> Vec<&Overlay> {
600 self.overlays
601 .iter()
602 .filter(|o| o.overlaps(range, marker_list))
603 .collect()
604 }
605
606 pub fn query_viewport(
615 &self,
616 start: usize,
617 end: usize,
618 marker_list: &MarkerList,
619 ) -> Vec<(&Overlay, Range<usize>)> {
620 use std::collections::HashMap;
621
622 let visible_markers = marker_list.query_range(start, end);
625
626 let marker_positions: HashMap<_, _> = visible_markers
628 .into_iter()
629 .map(|(id, start, _end)| (id, start))
630 .collect();
631
632 self.overlays
638 .iter()
639 .filter_map(|overlay| {
640 let start_in_vp = marker_positions.get(&overlay.start_marker).copied();
641 let end_in_vp = marker_positions.get(&overlay.end_marker).copied();
642
643 if start_in_vp.is_none() && end_in_vp.is_none() {
646 return None;
647 }
648
649 let start_pos =
651 start_in_vp.or_else(|| marker_list.get_position(overlay.start_marker))?;
652 let end_pos = end_in_vp.or_else(|| marker_list.get_position(overlay.end_marker))?;
653
654 let range = start_pos..end_pos;
655
656 let included = if range.start == range.end {
661 range.start >= start && range.start <= end
662 } else {
663 range.start < end && range.end > start
664 };
665
666 if included {
667 Some((overlay, range))
668 } else {
669 None
670 }
671 })
672 .collect()
673 }
674
675 pub fn get_by_handle(&self, handle: &OverlayHandle) -> Option<&Overlay> {
677 self.overlays.iter().find(|o| &o.handle == handle)
678 }
679
680 pub fn get_by_handle_mut(&mut self, handle: &OverlayHandle) -> Option<&mut Overlay> {
682 self.overlays.iter_mut().find(|o| &o.handle == handle)
683 }
684
685 pub fn len(&self) -> usize {
687 self.overlays.len()
688 }
689
690 pub fn is_empty(&self) -> bool {
692 self.overlays.is_empty()
693 }
694
695 pub fn all(&self) -> &[Overlay] {
697 &self.overlays
698 }
699
700 #[cfg(test)]
704 fn check_invariants(&self) {
705 assert_eq!(
706 self.marker_to_idx.len(),
707 self.overlays.len() * 2,
708 "marker_to_idx size != 2 * overlays.len()"
709 );
710 for (i, o) in self.overlays.iter().enumerate() {
711 assert_eq!(
712 self.marker_to_idx.get(&o.start_marker).copied(),
713 Some(i),
714 "start_marker {:?} of overlay {} mismapped",
715 o.start_marker,
716 i,
717 );
718 assert_eq!(
719 self.marker_to_idx.get(&o.end_marker).copied(),
720 Some(i),
721 "end_marker {:?} of overlay {} mismapped",
722 o.end_marker,
723 i,
724 );
725 }
726 }
730
731 #[cfg(test)]
733 fn assert_priority_sorted(&self) {
734 for w in self.overlays.windows(2) {
735 assert!(
736 w[0].priority <= w[1].priority,
737 "priority order broken: {} after {}",
738 w[1].priority,
739 w[0].priority,
740 );
741 }
742 }
743}
744
745impl Default for OverlayManager {
746 fn default() -> Self {
747 Self::new()
748 }
749}
750
751impl Overlay {
753 pub fn error(
755 marker_list: &mut MarkerList,
756 range: Range<usize>,
757 message: Option<String>,
758 ) -> Self {
759 let mut overlay = Self::with_priority(
760 marker_list,
761 range,
762 OverlayFace::Underline {
763 color: Color::Red,
764 style: UnderlineStyle::Wavy,
765 },
766 10, );
768 overlay.message = message;
769 overlay
770 }
771
772 pub fn warning(
774 marker_list: &mut MarkerList,
775 range: Range<usize>,
776 message: Option<String>,
777 ) -> Self {
778 let mut overlay = Self::with_priority(
779 marker_list,
780 range,
781 OverlayFace::Underline {
782 color: Color::Yellow,
783 style: UnderlineStyle::Wavy,
784 },
785 5, );
787 overlay.message = message;
788 overlay
789 }
790
791 pub fn info(
793 marker_list: &mut MarkerList,
794 range: Range<usize>,
795 message: Option<String>,
796 ) -> Self {
797 let mut overlay = Self::with_priority(
798 marker_list,
799 range,
800 OverlayFace::Underline {
801 color: Color::Blue,
802 style: UnderlineStyle::Wavy,
803 },
804 3, );
806 overlay.message = message;
807 overlay
808 }
809
810 pub fn hint(
812 marker_list: &mut MarkerList,
813 range: Range<usize>,
814 message: Option<String>,
815 ) -> Self {
816 let mut overlay = Self::with_priority(
817 marker_list,
818 range,
819 OverlayFace::Underline {
820 color: Color::Gray,
821 style: UnderlineStyle::Dotted,
822 },
823 1, );
825 overlay.message = message;
826 overlay
827 }
828
829 pub fn selection(marker_list: &mut MarkerList, range: Range<usize>) -> Self {
831 let mut overlay = Self::with_priority(
832 marker_list,
833 range,
834 OverlayFace::Background {
835 color: Color::Rgb(38, 79, 120), },
837 -10, );
839 overlay.theme_key = Some("editor.selection_bg");
840 overlay
841 }
842
843 pub fn search_match(marker_list: &mut MarkerList, range: Range<usize>) -> Self {
845 let mut overlay = Self::with_priority(
846 marker_list,
847 range,
848 OverlayFace::Background {
849 color: Color::Rgb(72, 72, 0), },
851 -5, );
853 overlay.theme_key = Some("search.match_bg");
854 overlay
855 }
856}
857
858#[cfg(test)]
859mod tests {
860 use super::*;
861
862 #[test]
863 fn test_overlay_creation_with_markers() {
864 let mut marker_list = MarkerList::new();
865 marker_list.set_buffer_size(100);
866
867 let overlay = Overlay::new(
868 &mut marker_list,
869 5..10,
870 OverlayFace::Background { color: Color::Red },
871 );
872
873 assert_eq!(marker_list.get_position(overlay.start_marker), Some(5));
874 assert_eq!(marker_list.get_position(overlay.end_marker), Some(10));
875 assert_eq!(overlay.range(&marker_list), 5..10);
876 }
877
878 #[test]
879 fn test_overlay_adjusts_with_insert() {
880 let mut marker_list = MarkerList::new();
881 marker_list.set_buffer_size(100);
882
883 let overlay = Overlay::new(
884 &mut marker_list,
885 10..20,
886 OverlayFace::Background { color: Color::Red },
887 );
888
889 marker_list.adjust_for_insert(5, 10);
891
892 assert_eq!(overlay.range(&marker_list), 20..30);
894 }
895
896 #[test]
897 fn test_overlay_adjusts_with_delete() {
898 let mut marker_list = MarkerList::new();
899 marker_list.set_buffer_size(100);
900
901 let overlay = Overlay::new(
902 &mut marker_list,
903 20..30,
904 OverlayFace::Background { color: Color::Red },
905 );
906
907 marker_list.adjust_for_delete(5, 10);
909
910 assert_eq!(overlay.range(&marker_list), 10..20);
912 }
913
914 #[test]
915 fn test_overlay_manager_add_remove() {
916 let mut marker_list = MarkerList::new();
917 marker_list.set_buffer_size(100);
918 let mut manager = OverlayManager::new();
919
920 let overlay = Overlay::new(
921 &mut marker_list,
922 5..10,
923 OverlayFace::Background { color: Color::Red },
924 );
925
926 let handle = manager.add(overlay);
927 assert_eq!(manager.len(), 1);
928
929 manager.remove_by_handle(&handle, &mut marker_list);
930 assert_eq!(manager.len(), 0);
931 }
932
933 #[test]
934 fn test_overlay_namespace_clear() {
935 let mut marker_list = MarkerList::new();
936 marker_list.set_buffer_size(100);
937 let mut manager = OverlayManager::new();
938
939 let ns = OverlayNamespace::from_string("todo".to_string());
940
941 let overlay1 = Overlay::with_namespace(
943 &mut marker_list,
944 5..10,
945 OverlayFace::Background { color: Color::Red },
946 ns.clone(),
947 );
948 let overlay2 = Overlay::with_namespace(
949 &mut marker_list,
950 15..20,
951 OverlayFace::Background { color: Color::Blue },
952 ns.clone(),
953 );
954 let overlay3 = Overlay::new(
956 &mut marker_list,
957 25..30,
958 OverlayFace::Background {
959 color: Color::Green,
960 },
961 );
962
963 manager.add(overlay1);
964 manager.add(overlay2);
965 manager.add(overlay3);
966 assert_eq!(manager.len(), 3);
967
968 manager.clear_namespace(&ns, &mut marker_list);
970 assert_eq!(manager.len(), 1); }
972
973 #[test]
974 fn test_overlay_priority_sorting() {
975 let mut marker_list = MarkerList::new();
976 marker_list.set_buffer_size(100);
977 let mut manager = OverlayManager::new();
978
979 manager.add(Overlay::with_priority(
980 &mut marker_list,
981 5..10,
982 OverlayFace::Background { color: Color::Red },
983 10,
984 ));
985 manager.add(Overlay::with_priority(
986 &mut marker_list,
987 5..10,
988 OverlayFace::Background { color: Color::Blue },
989 5,
990 ));
991 manager.add(Overlay::with_priority(
992 &mut marker_list,
993 5..10,
994 OverlayFace::Background {
995 color: Color::Green,
996 },
997 15,
998 ));
999
1000 let overlays = manager.at_position(7, &marker_list);
1001 assert_eq!(overlays.len(), 3);
1002 assert_eq!(overlays[0].priority, 5);
1004 assert_eq!(overlays[1].priority, 10);
1005 assert_eq!(overlays[2].priority, 15);
1006 }
1007
1008 #[test]
1009 fn test_overlay_contains_and_overlaps() {
1010 let mut marker_list = MarkerList::new();
1011 marker_list.set_buffer_size(100);
1012
1013 let overlay = Overlay::new(
1014 &mut marker_list,
1015 10..20,
1016 OverlayFace::Background { color: Color::Red },
1017 );
1018
1019 assert!(!overlay.contains(9, &marker_list));
1020 assert!(overlay.contains(10, &marker_list));
1021 assert!(overlay.contains(15, &marker_list));
1022 assert!(overlay.contains(19, &marker_list));
1023 assert!(!overlay.contains(20, &marker_list));
1024
1025 assert!(!overlay.overlaps(&(0..10), &marker_list));
1026 assert!(overlay.overlaps(&(5..15), &marker_list));
1027 assert!(overlay.overlaps(&(15..25), &marker_list));
1028 assert!(!overlay.overlaps(&(20..30), &marker_list));
1029 }
1030
1031 #[test]
1032 fn test_overlay_remove_in_range_keeps_only_disjoint() {
1033 let mut marker_list = MarkerList::new();
1034 marker_list.set_buffer_size(200);
1035 let mut manager = OverlayManager::new();
1036
1037 manager.add(Overlay::new(
1038 &mut marker_list,
1039 0..5,
1040 OverlayFace::Background { color: Color::Red },
1041 ));
1042 manager.add(Overlay::new(
1043 &mut marker_list,
1044 10..20,
1045 OverlayFace::Background { color: Color::Blue },
1046 ));
1047 manager.add(Overlay::new(
1048 &mut marker_list,
1049 30..40,
1050 OverlayFace::Background {
1051 color: Color::Green,
1052 },
1053 ));
1054 manager.add(Overlay::new(
1055 &mut marker_list,
1056 50..60,
1057 OverlayFace::Background {
1058 color: Color::Yellow,
1059 },
1060 ));
1061
1062 manager.remove_in_range(&(15..35), &mut marker_list);
1064
1065 let kept: Vec<_> = manager
1066 .all()
1067 .iter()
1068 .map(|o| o.range(&marker_list))
1069 .collect();
1070 assert_eq!(kept, vec![0..5, 50..60]);
1071 }
1072
1073 #[test]
1074 fn test_overlay_remove_in_range_deletes_markers() {
1075 let mut marker_list = MarkerList::new();
1076 marker_list.set_buffer_size(100);
1077 let mut manager = OverlayManager::new();
1078
1079 let overlay = Overlay::new(
1080 &mut marker_list,
1081 10..20,
1082 OverlayFace::Background { color: Color::Red },
1083 );
1084 let start_id = overlay.start_marker;
1085 let end_id = overlay.end_marker;
1086 manager.add(overlay);
1087
1088 manager.remove_in_range(&(0..50), &mut marker_list);
1089
1090 assert_eq!(manager.len(), 0);
1091 assert_eq!(marker_list.get_position(start_id), None);
1092 assert_eq!(marker_list.get_position(end_id), None);
1093 }
1094
1095 #[test]
1096 fn test_overlay_remove_in_range_endpoint_semantics() {
1097 let mut marker_list = MarkerList::new();
1099 marker_list.set_buffer_size(100);
1100 let mut manager = OverlayManager::new();
1101
1102 manager.add(Overlay::new(
1103 &mut marker_list,
1104 10..20,
1105 OverlayFace::Background { color: Color::Red },
1106 ));
1107
1108 manager.remove_in_range(&(20..30), &mut marker_list);
1109 assert_eq!(manager.len(), 1);
1110 manager.remove_in_range(&(0..10), &mut marker_list);
1111 assert_eq!(manager.len(), 1);
1112 manager.remove_in_range(&(19..21), &mut marker_list);
1113 assert_eq!(manager.len(), 0);
1114 }
1115
1116 #[test]
1125 fn perf_full_buffer_rebuild_pass() {
1126 const LINES: usize = 500;
1127 const LINE_BYTES: usize = 50;
1128 const OVERLAYS_PER_LINE: usize = 5;
1129
1130 let mut marker_list = MarkerList::new();
1131 marker_list.set_buffer_size(LINES * LINE_BYTES);
1132 let mut manager = OverlayManager::new();
1133
1134 let overlay_byte = |line: usize, k: usize| -> usize {
1135 line * LINE_BYTES + k * (LINE_BYTES / OVERLAYS_PER_LINE)
1136 };
1137 let make_overlay = |ml: &mut MarkerList, line: usize, k: usize| {
1138 let s = overlay_byte(line, k);
1139 Overlay::new(
1140 ml,
1141 s..(s + 2),
1142 OverlayFace::Background { color: Color::Red },
1143 )
1144 };
1145
1146 for line in 0..LINES {
1148 for k in 0..OVERLAYS_PER_LINE {
1149 let o = make_overlay(&mut marker_list, line, k);
1150 manager.add(o);
1151 }
1152 }
1153 let initial = LINES * OVERLAYS_PER_LINE;
1154
1155 let start = std::time::Instant::now();
1157 for line in 0..LINES {
1158 let line_range = (line * LINE_BYTES)..((line + 1) * LINE_BYTES);
1159 manager.remove_in_range(&line_range, &mut marker_list);
1160 for k in 0..OVERLAYS_PER_LINE {
1161 let o = make_overlay(&mut marker_list, line, k);
1162 manager.add(o);
1163 }
1164 }
1165 let elapsed = start.elapsed();
1166
1167 eprintln!(
1168 "[perf] overlay full-buffer rebuild ({LINES} lines, {} entries steady): \
1169 {:?} total, {:?}/line",
1170 initial,
1171 elapsed,
1172 elapsed / LINES as u32,
1173 );
1174 assert_eq!(manager.len(), initial);
1175 }
1176
1177 mod proptests {
1178 use super::*;
1179 use proptest::prelude::*;
1180
1181 #[derive(Debug, Clone)]
1182 enum Op {
1183 Add {
1184 start: usize,
1185 len: usize,
1186 priority: i32,
1187 ns_idx: u8,
1188 },
1189 RemoveInRange {
1190 start: usize,
1191 end: usize,
1192 },
1193 ClearNamespace {
1194 ns_idx: u8,
1195 },
1196 ReplaceRange {
1197 start: usize,
1198 end: usize,
1199 ns_idx: u8,
1200 new_overlays: Vec<(usize, usize, i32)>,
1203 },
1204 }
1205
1206 const BUFFER_SIZE: usize = 200;
1207 const MAX_OVERLAY_LEN: usize = 4;
1208 const MIN_QUERY_LEN: usize = MAX_OVERLAY_LEN + 1;
1209
1210 fn arb_overlay_spec() -> impl Strategy<Value = (usize, usize, i32)> {
1211 (
1212 0..(BUFFER_SIZE - MAX_OVERLAY_LEN),
1213 1..=MAX_OVERLAY_LEN,
1214 -5i32..=5i32,
1215 )
1216 }
1217
1218 fn arb_op() -> impl Strategy<Value = Op> {
1219 prop_oneof![
1220 3 => arb_overlay_spec().prop_flat_map(|(start, len, priority)| {
1221 (Just(start), Just(len), Just(priority), 0u8..3u8)
1222 }).prop_map(|(start, len, priority, ns_idx)| Op::Add {
1223 start, len, priority, ns_idx,
1224 }),
1225 2 => (0..BUFFER_SIZE, MIN_QUERY_LEN..=BUFFER_SIZE)
1226 .prop_map(|(start, qlen)| {
1227 let s = start.min(BUFFER_SIZE - 1);
1228 let e = (s + qlen).min(BUFFER_SIZE);
1229 Op::RemoveInRange { start: s, end: e }
1230 }),
1231 1 => (0u8..3u8).prop_map(|ns_idx| Op::ClearNamespace { ns_idx }),
1232 1 => (
1233 0..BUFFER_SIZE,
1234 MIN_QUERY_LEN..=BUFFER_SIZE,
1235 0u8..3u8,
1236 prop::collection::vec(arb_overlay_spec(), 0..4),
1237 )
1238 .prop_map(|(start, qlen, ns_idx, new_overlays)| {
1239 let s = start.min(BUFFER_SIZE - 1);
1240 let e = (s + qlen).min(BUFFER_SIZE);
1241 Op::ReplaceRange { start: s, end: e, ns_idx, new_overlays }
1242 }),
1243 ]
1244 }
1245
1246 fn nsf(idx: u8) -> OverlayNamespace {
1247 OverlayNamespace::from_string(format!("ns{idx}"))
1248 }
1249
1250 proptest! {
1251 #[test]
1259 fn prop_marker_index_consistent(ops in prop::collection::vec(arb_op(), 0..30)) {
1260 let mut marker_list = MarkerList::new();
1261 marker_list.set_buffer_size(BUFFER_SIZE);
1262 let mut manager = OverlayManager::new();
1263
1264 for op in ops {
1265 match op {
1266 Op::Add { start, len, priority, ns_idx } => {
1267 let o = Overlay::with_namespace(
1268 &mut marker_list,
1269 start..(start + len),
1270 OverlayFace::Background { color: Color::Red },
1271 nsf(ns_idx),
1272 );
1273 let mut o = o;
1274 o.priority = priority;
1275 manager.add(o);
1276 manager.check_invariants();
1277 manager.assert_priority_sorted();
1278 }
1279 Op::RemoveInRange { start, end } => {
1280 manager.remove_in_range(&(start..end), &mut marker_list);
1281 for (o, rng) in manager.query_viewport(start, end, &marker_list) {
1282 let overlaps = rng.start < end && start < rng.end;
1283 prop_assert!(
1284 !overlaps,
1285 "overlay {:?} (handle {:?}) survived remove_in_range({start}..{end})",
1286 rng, o.handle,
1287 );
1288 }
1289 manager.check_invariants();
1290 }
1291 Op::ClearNamespace { ns_idx } => {
1292 manager.clear_namespace(&nsf(ns_idx), &mut marker_list);
1293 manager.check_invariants();
1294 manager.assert_priority_sorted();
1295 }
1296 Op::ReplaceRange { start, end, ns_idx, new_overlays } => {
1297 let new: Vec<Overlay> = new_overlays.into_iter().map(|(s, l, p)| {
1298 let mut o = Overlay::with_namespace(
1299 &mut marker_list,
1300 s..(s + l),
1301 OverlayFace::Background { color: Color::Blue },
1302 nsf(ns_idx),
1303 );
1304 o.priority = p;
1305 o
1306 }).collect();
1307 manager.replace_range_in_namespace(
1308 &nsf(ns_idx),
1309 &(start..end),
1310 new,
1311 &mut marker_list,
1312 );
1313 manager.check_invariants();
1314 manager.assert_priority_sorted();
1315 }
1316 }
1317 }
1318 }
1319 }
1320 }
1321}