1use crate::model::marker::{MarkerId, MarkerList};
2use ratatui::style::{Color, Style};
3use std::ops::Range;
4
5pub use fresh_core::overlay::{OverlayHandle, OverlayNamespace};
7
8#[derive(Debug, Clone, PartialEq)]
10pub enum OverlayFace {
11 Underline { color: Color, style: UnderlineStyle },
13 Background { color: Color },
15 Foreground { color: Color },
17 Style { style: Style },
19 ThemedStyle {
25 fallback_style: Style,
27 fg_theme: Option<String>,
29 bg_theme: Option<String>,
31 },
32}
33
34impl OverlayFace {
35 pub fn from_options(options: &fresh_core::api::OverlayOptions) -> Self {
40 use crate::view::theme::named_color_from_str;
41 use ratatui::style::Modifier;
42
43 let mut style = Style::default();
44
45 if let Some(ref fg) = options.fg {
46 if let Some((r, g, b)) = fg.as_rgb() {
47 style = style.fg(Color::Rgb(r, g, b));
48 } else if let Some(key) = fg.as_theme_key() {
49 if let Some(color) = named_color_from_str(key) {
50 style = style.fg(color);
51 }
52 }
53 }
54
55 if let Some(ref bg) = options.bg {
56 if let Some((r, g, b)) = bg.as_rgb() {
57 style = style.bg(Color::Rgb(r, g, b));
58 } else if let Some(key) = bg.as_theme_key() {
59 if let Some(color) = named_color_from_str(key) {
60 style = style.bg(color);
61 }
62 }
63 }
64
65 let mut modifiers = Modifier::empty();
66 if options.bold {
67 modifiers |= Modifier::BOLD;
68 }
69 if options.italic {
70 modifiers |= Modifier::ITALIC;
71 }
72 if options.underline {
73 modifiers |= Modifier::UNDERLINED;
74 }
75 if options.strikethrough {
76 modifiers |= Modifier::CROSSED_OUT;
77 }
78 if !modifiers.is_empty() {
79 style = style.add_modifier(modifiers);
80 }
81
82 let fg_theme = options
85 .fg
86 .as_ref()
87 .and_then(|c| c.as_theme_key())
88 .filter(|key| named_color_from_str(key).is_none())
89 .map(String::from);
90 let bg_theme = options
91 .bg
92 .as_ref()
93 .and_then(|c| c.as_theme_key())
94 .filter(|key| named_color_from_str(key).is_none())
95 .map(String::from);
96
97 if fg_theme.is_some() || bg_theme.is_some() {
98 OverlayFace::ThemedStyle {
99 fallback_style: style,
100 fg_theme,
101 bg_theme,
102 }
103 } else {
104 OverlayFace::Style { style }
105 }
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum UnderlineStyle {
112 Straight,
114 Wavy,
116 Dotted,
118 Dashed,
120}
121
122pub type Priority = i32;
125
126#[derive(Debug, Clone)]
129pub struct Overlay {
130 pub handle: OverlayHandle,
132
133 pub namespace: Option<OverlayNamespace>,
135
136 pub start_marker: MarkerId,
138
139 pub end_marker: MarkerId,
141
142 pub face: OverlayFace,
144
145 pub priority: Priority,
147
148 pub message: Option<String>,
150
151 pub extend_to_line_end: bool,
154
155 pub url: Option<String>,
158
159 pub theme_key: Option<&'static str>,
163}
164
165impl Overlay {
166 pub fn new(marker_list: &mut MarkerList, range: Range<usize>, face: OverlayFace) -> Self {
175 let start_marker = marker_list.create(range.start, true); let end_marker = marker_list.create(range.end, false); Self {
179 handle: OverlayHandle::new(),
180 namespace: None,
181 start_marker,
182 end_marker,
183 face,
184 priority: 0,
185 message: None,
186 extend_to_line_end: false,
187 url: None,
188 theme_key: None,
189 }
190 }
191
192 pub fn with_namespace(
194 marker_list: &mut MarkerList,
195 range: Range<usize>,
196 face: OverlayFace,
197 namespace: OverlayNamespace,
198 ) -> Self {
199 let mut overlay = Self::new(marker_list, range, face);
200 overlay.namespace = Some(namespace);
201 overlay
202 }
203
204 pub fn with_priority(
206 marker_list: &mut MarkerList,
207 range: Range<usize>,
208 face: OverlayFace,
209 priority: Priority,
210 ) -> Self {
211 let mut overlay = Self::new(marker_list, range, face);
212 overlay.priority = priority;
213 overlay
214 }
215
216 pub fn with_message(mut self, message: String) -> Self {
218 self.message = Some(message);
219 self
220 }
221
222 pub fn with_priority_value(mut self, priority: Priority) -> Self {
224 self.priority = priority;
225 self
226 }
227
228 pub fn with_namespace_value(mut self, namespace: OverlayNamespace) -> Self {
230 self.namespace = Some(namespace);
231 self
232 }
233
234 pub fn with_extend_to_line_end(mut self, extend: bool) -> Self {
236 self.extend_to_line_end = extend;
237 self
238 }
239
240 pub fn with_theme_key(mut self, key: &'static str) -> Self {
242 self.theme_key = Some(key);
243 self
244 }
245
246 pub fn range(&self, marker_list: &MarkerList) -> Range<usize> {
249 let start = marker_list.get_position(self.start_marker).unwrap_or(0);
250 let end = marker_list.get_position(self.end_marker).unwrap_or(0);
251 start..end
252 }
253
254 pub fn contains(&self, position: usize, marker_list: &MarkerList) -> bool {
256 self.range(marker_list).contains(&position)
257 }
258
259 pub fn overlaps(&self, range: &Range<usize>, marker_list: &MarkerList) -> bool {
261 let self_range = self.range(marker_list);
262 self_range.start < range.end && range.start < self_range.end
263 }
264}
265
266#[derive(Debug, Clone)]
269pub struct OverlayManager {
270 overlays: Vec<Overlay>,
272}
273
274impl OverlayManager {
275 pub fn new() -> Self {
277 Self {
278 overlays: Vec::new(),
279 }
280 }
281
282 pub fn add(&mut self, overlay: Overlay) -> OverlayHandle {
284 let handle = overlay.handle.clone();
285 self.overlays.push(overlay);
286 self.overlays.sort_by_key(|o| o.priority);
288 handle
289 }
290
291 pub fn extend<I: IntoIterator<Item = Overlay>>(&mut self, overlays: I) {
298 self.overlays.extend(overlays);
299 self.overlays.sort_by_key(|o| o.priority);
300 }
301
302 pub fn remove_by_handle(
304 &mut self,
305 handle: &OverlayHandle,
306 marker_list: &mut MarkerList,
307 ) -> bool {
308 if let Some(pos) = self.overlays.iter().position(|o| &o.handle == handle) {
309 let overlay = self.overlays.remove(pos);
310 marker_list.delete(overlay.start_marker);
311 marker_list.delete(overlay.end_marker);
312 true
313 } else {
314 false
315 }
316 }
317
318 pub fn clear_namespace(&mut self, namespace: &OverlayNamespace, marker_list: &mut MarkerList) {
320 let markers_to_delete: Vec<_> = self
322 .overlays
323 .iter()
324 .filter(|o| o.namespace.as_ref() == Some(namespace))
325 .flat_map(|o| vec![o.start_marker, o.end_marker])
326 .collect();
327
328 self.overlays
330 .retain(|o| o.namespace.as_ref() != Some(namespace));
331
332 for marker_id in markers_to_delete {
334 marker_list.delete(marker_id);
335 }
336 }
337
338 pub fn replace_range_in_namespace(
343 &mut self,
344 namespace: &OverlayNamespace,
345 range: &Range<usize>,
346 mut new_overlays: Vec<Overlay>,
347 marker_list: &mut MarkerList,
348 ) {
349 let mut markers_to_delete = Vec::new();
350
351 self.overlays.retain(|overlay| {
352 let in_namespace = overlay.namespace.as_ref() == Some(namespace);
353 if in_namespace && overlay.overlaps(range, marker_list) {
354 markers_to_delete.push(overlay.start_marker);
355 markers_to_delete.push(overlay.end_marker);
356 false
357 } else {
358 true
359 }
360 });
361
362 for marker_id in markers_to_delete {
363 marker_list.delete(marker_id);
364 }
365
366 if !new_overlays.is_empty() {
367 self.overlays.append(&mut new_overlays);
368 self.overlays.sort_by_key(|o| o.priority);
369 }
370 }
371
372 pub fn remove_in_range(&mut self, range: &Range<usize>, marker_list: &mut MarkerList) {
374 let markers_to_delete: Vec<_> = self
376 .overlays
377 .iter()
378 .filter(|o| o.overlaps(range, marker_list))
379 .flat_map(|o| vec![o.start_marker, o.end_marker])
380 .collect();
381
382 self.overlays.retain(|o| !o.overlaps(range, marker_list));
384
385 for marker_id in markers_to_delete {
387 marker_list.delete(marker_id);
388 }
389 }
390
391 pub fn clear(&mut self, marker_list: &mut MarkerList) {
393 for overlay in &self.overlays {
395 marker_list.delete(overlay.start_marker);
396 marker_list.delete(overlay.end_marker);
397 }
398
399 self.overlays.clear();
400 }
401
402 pub fn at_position(&self, position: usize, marker_list: &MarkerList) -> Vec<&Overlay> {
404 self.overlays
405 .iter()
406 .filter(|o| {
407 let range = o.range(marker_list);
408 range.contains(&position)
409 })
410 .collect()
411 }
412
413 pub fn in_range(&self, range: &Range<usize>, marker_list: &MarkerList) -> Vec<&Overlay> {
415 self.overlays
416 .iter()
417 .filter(|o| o.overlaps(range, marker_list))
418 .collect()
419 }
420
421 pub fn query_viewport(
430 &self,
431 start: usize,
432 end: usize,
433 marker_list: &MarkerList,
434 ) -> Vec<(&Overlay, Range<usize>)> {
435 use std::collections::HashMap;
436
437 let visible_markers = marker_list.query_range(start, end);
440
441 let marker_positions: HashMap<_, _> = visible_markers
443 .into_iter()
444 .map(|(id, start, _end)| (id, start))
445 .collect();
446
447 self.overlays
453 .iter()
454 .filter_map(|overlay| {
455 let start_in_vp = marker_positions.get(&overlay.start_marker).copied();
456 let end_in_vp = marker_positions.get(&overlay.end_marker).copied();
457
458 if start_in_vp.is_none() && end_in_vp.is_none() {
461 return None;
462 }
463
464 let start_pos =
466 start_in_vp.or_else(|| marker_list.get_position(overlay.start_marker))?;
467 let end_pos = end_in_vp.or_else(|| marker_list.get_position(overlay.end_marker))?;
468
469 let range = start_pos..end_pos;
470
471 let included = if range.start == range.end {
476 range.start >= start && range.start <= end
477 } else {
478 range.start < end && range.end > start
479 };
480
481 if included {
482 Some((overlay, range))
483 } else {
484 None
485 }
486 })
487 .collect()
488 }
489
490 pub fn get_by_handle(&self, handle: &OverlayHandle) -> Option<&Overlay> {
492 self.overlays.iter().find(|o| &o.handle == handle)
493 }
494
495 pub fn get_by_handle_mut(&mut self, handle: &OverlayHandle) -> Option<&mut Overlay> {
497 self.overlays.iter_mut().find(|o| &o.handle == handle)
498 }
499
500 pub fn len(&self) -> usize {
502 self.overlays.len()
503 }
504
505 pub fn is_empty(&self) -> bool {
507 self.overlays.is_empty()
508 }
509
510 pub fn all(&self) -> &[Overlay] {
512 &self.overlays
513 }
514}
515
516impl Default for OverlayManager {
517 fn default() -> Self {
518 Self::new()
519 }
520}
521
522impl Overlay {
524 pub fn error(
526 marker_list: &mut MarkerList,
527 range: Range<usize>,
528 message: Option<String>,
529 ) -> Self {
530 let mut overlay = Self::with_priority(
531 marker_list,
532 range,
533 OverlayFace::Underline {
534 color: Color::Red,
535 style: UnderlineStyle::Wavy,
536 },
537 10, );
539 overlay.message = message;
540 overlay
541 }
542
543 pub fn warning(
545 marker_list: &mut MarkerList,
546 range: Range<usize>,
547 message: Option<String>,
548 ) -> Self {
549 let mut overlay = Self::with_priority(
550 marker_list,
551 range,
552 OverlayFace::Underline {
553 color: Color::Yellow,
554 style: UnderlineStyle::Wavy,
555 },
556 5, );
558 overlay.message = message;
559 overlay
560 }
561
562 pub fn info(
564 marker_list: &mut MarkerList,
565 range: Range<usize>,
566 message: Option<String>,
567 ) -> Self {
568 let mut overlay = Self::with_priority(
569 marker_list,
570 range,
571 OverlayFace::Underline {
572 color: Color::Blue,
573 style: UnderlineStyle::Wavy,
574 },
575 3, );
577 overlay.message = message;
578 overlay
579 }
580
581 pub fn hint(
583 marker_list: &mut MarkerList,
584 range: Range<usize>,
585 message: Option<String>,
586 ) -> Self {
587 let mut overlay = Self::with_priority(
588 marker_list,
589 range,
590 OverlayFace::Underline {
591 color: Color::Gray,
592 style: UnderlineStyle::Dotted,
593 },
594 1, );
596 overlay.message = message;
597 overlay
598 }
599
600 pub fn selection(marker_list: &mut MarkerList, range: Range<usize>) -> Self {
602 let mut overlay = Self::with_priority(
603 marker_list,
604 range,
605 OverlayFace::Background {
606 color: Color::Rgb(38, 79, 120), },
608 -10, );
610 overlay.theme_key = Some("editor.selection_bg");
611 overlay
612 }
613
614 pub fn search_match(marker_list: &mut MarkerList, range: Range<usize>) -> Self {
616 let mut overlay = Self::with_priority(
617 marker_list,
618 range,
619 OverlayFace::Background {
620 color: Color::Rgb(72, 72, 0), },
622 -5, );
624 overlay.theme_key = Some("search.match_bg");
625 overlay
626 }
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632
633 #[test]
634 fn test_overlay_creation_with_markers() {
635 let mut marker_list = MarkerList::new();
636 marker_list.set_buffer_size(100);
637
638 let overlay = Overlay::new(
639 &mut marker_list,
640 5..10,
641 OverlayFace::Background { color: Color::Red },
642 );
643
644 assert_eq!(marker_list.get_position(overlay.start_marker), Some(5));
645 assert_eq!(marker_list.get_position(overlay.end_marker), Some(10));
646 assert_eq!(overlay.range(&marker_list), 5..10);
647 }
648
649 #[test]
650 fn test_overlay_adjusts_with_insert() {
651 let mut marker_list = MarkerList::new();
652 marker_list.set_buffer_size(100);
653
654 let overlay = Overlay::new(
655 &mut marker_list,
656 10..20,
657 OverlayFace::Background { color: Color::Red },
658 );
659
660 marker_list.adjust_for_insert(5, 10);
662
663 assert_eq!(overlay.range(&marker_list), 20..30);
665 }
666
667 #[test]
668 fn test_overlay_adjusts_with_delete() {
669 let mut marker_list = MarkerList::new();
670 marker_list.set_buffer_size(100);
671
672 let overlay = Overlay::new(
673 &mut marker_list,
674 20..30,
675 OverlayFace::Background { color: Color::Red },
676 );
677
678 marker_list.adjust_for_delete(5, 10);
680
681 assert_eq!(overlay.range(&marker_list), 10..20);
683 }
684
685 #[test]
686 fn test_overlay_manager_add_remove() {
687 let mut marker_list = MarkerList::new();
688 marker_list.set_buffer_size(100);
689 let mut manager = OverlayManager::new();
690
691 let overlay = Overlay::new(
692 &mut marker_list,
693 5..10,
694 OverlayFace::Background { color: Color::Red },
695 );
696
697 let handle = manager.add(overlay);
698 assert_eq!(manager.len(), 1);
699
700 manager.remove_by_handle(&handle, &mut marker_list);
701 assert_eq!(manager.len(), 0);
702 }
703
704 #[test]
705 fn test_overlay_namespace_clear() {
706 let mut marker_list = MarkerList::new();
707 marker_list.set_buffer_size(100);
708 let mut manager = OverlayManager::new();
709
710 let ns = OverlayNamespace::from_string("todo".to_string());
711
712 let overlay1 = Overlay::with_namespace(
714 &mut marker_list,
715 5..10,
716 OverlayFace::Background { color: Color::Red },
717 ns.clone(),
718 );
719 let overlay2 = Overlay::with_namespace(
720 &mut marker_list,
721 15..20,
722 OverlayFace::Background { color: Color::Blue },
723 ns.clone(),
724 );
725 let overlay3 = Overlay::new(
727 &mut marker_list,
728 25..30,
729 OverlayFace::Background {
730 color: Color::Green,
731 },
732 );
733
734 manager.add(overlay1);
735 manager.add(overlay2);
736 manager.add(overlay3);
737 assert_eq!(manager.len(), 3);
738
739 manager.clear_namespace(&ns, &mut marker_list);
741 assert_eq!(manager.len(), 1); }
743
744 #[test]
745 fn test_overlay_priority_sorting() {
746 let mut marker_list = MarkerList::new();
747 marker_list.set_buffer_size(100);
748 let mut manager = OverlayManager::new();
749
750 manager.add(Overlay::with_priority(
751 &mut marker_list,
752 5..10,
753 OverlayFace::Background { color: Color::Red },
754 10,
755 ));
756 manager.add(Overlay::with_priority(
757 &mut marker_list,
758 5..10,
759 OverlayFace::Background { color: Color::Blue },
760 5,
761 ));
762 manager.add(Overlay::with_priority(
763 &mut marker_list,
764 5..10,
765 OverlayFace::Background {
766 color: Color::Green,
767 },
768 15,
769 ));
770
771 let overlays = manager.at_position(7, &marker_list);
772 assert_eq!(overlays.len(), 3);
773 assert_eq!(overlays[0].priority, 5);
775 assert_eq!(overlays[1].priority, 10);
776 assert_eq!(overlays[2].priority, 15);
777 }
778
779 #[test]
780 fn test_overlay_contains_and_overlaps() {
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 assert!(!overlay.contains(9, &marker_list));
791 assert!(overlay.contains(10, &marker_list));
792 assert!(overlay.contains(15, &marker_list));
793 assert!(overlay.contains(19, &marker_list));
794 assert!(!overlay.contains(20, &marker_list));
795
796 assert!(!overlay.overlaps(&(0..10), &marker_list));
797 assert!(overlay.overlaps(&(5..15), &marker_list));
798 assert!(overlay.overlaps(&(15..25), &marker_list));
799 assert!(!overlay.overlaps(&(20..30), &marker_list));
800 }
801}