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 ratatui::style::Modifier;
41
42 let mut style = Style::default();
43
44 if let Some(ref fg) = options.fg {
45 if let Some((r, g, b)) = fg.as_rgb() {
46 style = style.fg(Color::Rgb(r, g, b));
47 }
48 }
49
50 if let Some(ref bg) = options.bg {
51 if let Some((r, g, b)) = bg.as_rgb() {
52 style = style.bg(Color::Rgb(r, g, b));
53 }
54 }
55
56 let mut modifiers = Modifier::empty();
57 if options.bold {
58 modifiers |= Modifier::BOLD;
59 }
60 if options.italic {
61 modifiers |= Modifier::ITALIC;
62 }
63 if options.underline {
64 modifiers |= Modifier::UNDERLINED;
65 }
66 if options.strikethrough {
67 modifiers |= Modifier::CROSSED_OUT;
68 }
69 if !modifiers.is_empty() {
70 style = style.add_modifier(modifiers);
71 }
72
73 let fg_theme = options
74 .fg
75 .as_ref()
76 .and_then(|c| c.as_theme_key())
77 .map(String::from);
78 let bg_theme = options
79 .bg
80 .as_ref()
81 .and_then(|c| c.as_theme_key())
82 .map(String::from);
83
84 if fg_theme.is_some() || bg_theme.is_some() {
85 OverlayFace::ThemedStyle {
86 fallback_style: style,
87 fg_theme,
88 bg_theme,
89 }
90 } else {
91 OverlayFace::Style { style }
92 }
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum UnderlineStyle {
99 Straight,
101 Wavy,
103 Dotted,
105 Dashed,
107}
108
109pub type Priority = i32;
112
113#[derive(Debug, Clone)]
116pub struct Overlay {
117 pub handle: OverlayHandle,
119
120 pub namespace: Option<OverlayNamespace>,
122
123 pub start_marker: MarkerId,
125
126 pub end_marker: MarkerId,
128
129 pub face: OverlayFace,
131
132 pub priority: Priority,
134
135 pub message: Option<String>,
137
138 pub extend_to_line_end: bool,
141
142 pub url: Option<String>,
145}
146
147impl Overlay {
148 pub fn new(marker_list: &mut MarkerList, range: Range<usize>, face: OverlayFace) -> Self {
157 let start_marker = marker_list.create(range.start, true); let end_marker = marker_list.create(range.end, false); Self {
161 handle: OverlayHandle::new(),
162 namespace: None,
163 start_marker,
164 end_marker,
165 face,
166 priority: 0,
167 message: None,
168 extend_to_line_end: false,
169 url: None,
170 }
171 }
172
173 pub fn with_namespace(
175 marker_list: &mut MarkerList,
176 range: Range<usize>,
177 face: OverlayFace,
178 namespace: OverlayNamespace,
179 ) -> Self {
180 let mut overlay = Self::new(marker_list, range, face);
181 overlay.namespace = Some(namespace);
182 overlay
183 }
184
185 pub fn with_priority(
187 marker_list: &mut MarkerList,
188 range: Range<usize>,
189 face: OverlayFace,
190 priority: Priority,
191 ) -> Self {
192 let mut overlay = Self::new(marker_list, range, face);
193 overlay.priority = priority;
194 overlay
195 }
196
197 pub fn with_message(mut self, message: String) -> Self {
199 self.message = Some(message);
200 self
201 }
202
203 pub fn with_priority_value(mut self, priority: Priority) -> Self {
205 self.priority = priority;
206 self
207 }
208
209 pub fn with_namespace_value(mut self, namespace: OverlayNamespace) -> Self {
211 self.namespace = Some(namespace);
212 self
213 }
214
215 pub fn with_extend_to_line_end(mut self, extend: bool) -> Self {
217 self.extend_to_line_end = extend;
218 self
219 }
220
221 pub fn range(&self, marker_list: &MarkerList) -> Range<usize> {
224 let start = marker_list.get_position(self.start_marker).unwrap_or(0);
225 let end = marker_list.get_position(self.end_marker).unwrap_or(0);
226 start..end
227 }
228
229 pub fn contains(&self, position: usize, marker_list: &MarkerList) -> bool {
231 self.range(marker_list).contains(&position)
232 }
233
234 pub fn overlaps(&self, range: &Range<usize>, marker_list: &MarkerList) -> bool {
236 let self_range = self.range(marker_list);
237 self_range.start < range.end && range.start < self_range.end
238 }
239}
240
241#[derive(Debug, Clone)]
244pub struct OverlayManager {
245 overlays: Vec<Overlay>,
247}
248
249impl OverlayManager {
250 pub fn new() -> Self {
252 Self {
253 overlays: Vec::new(),
254 }
255 }
256
257 pub fn add(&mut self, overlay: Overlay) -> OverlayHandle {
259 let handle = overlay.handle.clone();
260 self.overlays.push(overlay);
261 self.overlays.sort_by_key(|o| o.priority);
263 handle
264 }
265
266 pub fn remove_by_handle(
268 &mut self,
269 handle: &OverlayHandle,
270 marker_list: &mut MarkerList,
271 ) -> bool {
272 if let Some(pos) = self.overlays.iter().position(|o| &o.handle == handle) {
273 let overlay = self.overlays.remove(pos);
274 marker_list.delete(overlay.start_marker);
275 marker_list.delete(overlay.end_marker);
276 true
277 } else {
278 false
279 }
280 }
281
282 pub fn clear_namespace(&mut self, namespace: &OverlayNamespace, marker_list: &mut MarkerList) {
284 let markers_to_delete: Vec<_> = self
286 .overlays
287 .iter()
288 .filter(|o| o.namespace.as_ref() == Some(namespace))
289 .flat_map(|o| vec![o.start_marker, o.end_marker])
290 .collect();
291
292 self.overlays
294 .retain(|o| o.namespace.as_ref() != Some(namespace));
295
296 for marker_id in markers_to_delete {
298 marker_list.delete(marker_id);
299 }
300 }
301
302 pub fn replace_range_in_namespace(
307 &mut self,
308 namespace: &OverlayNamespace,
309 range: &Range<usize>,
310 mut new_overlays: Vec<Overlay>,
311 marker_list: &mut MarkerList,
312 ) {
313 let mut markers_to_delete = Vec::new();
314
315 self.overlays.retain(|overlay| {
316 let in_namespace = overlay.namespace.as_ref() == Some(namespace);
317 if in_namespace && overlay.overlaps(range, marker_list) {
318 markers_to_delete.push(overlay.start_marker);
319 markers_to_delete.push(overlay.end_marker);
320 false
321 } else {
322 true
323 }
324 });
325
326 for marker_id in markers_to_delete {
327 marker_list.delete(marker_id);
328 }
329
330 if !new_overlays.is_empty() {
331 self.overlays.append(&mut new_overlays);
332 self.overlays.sort_by_key(|o| o.priority);
333 }
334 }
335
336 pub fn remove_in_range(&mut self, range: &Range<usize>, marker_list: &mut MarkerList) {
338 let markers_to_delete: Vec<_> = self
340 .overlays
341 .iter()
342 .filter(|o| o.overlaps(range, marker_list))
343 .flat_map(|o| vec![o.start_marker, o.end_marker])
344 .collect();
345
346 self.overlays.retain(|o| !o.overlaps(range, marker_list));
348
349 for marker_id in markers_to_delete {
351 marker_list.delete(marker_id);
352 }
353 }
354
355 pub fn clear(&mut self, marker_list: &mut MarkerList) {
357 for overlay in &self.overlays {
359 marker_list.delete(overlay.start_marker);
360 marker_list.delete(overlay.end_marker);
361 }
362
363 self.overlays.clear();
364 }
365
366 pub fn at_position(&self, position: usize, marker_list: &MarkerList) -> Vec<&Overlay> {
368 self.overlays
369 .iter()
370 .filter(|o| {
371 let range = o.range(marker_list);
372 range.contains(&position)
373 })
374 .collect()
375 }
376
377 pub fn in_range(&self, range: &Range<usize>, marker_list: &MarkerList) -> Vec<&Overlay> {
379 self.overlays
380 .iter()
381 .filter(|o| o.overlaps(range, marker_list))
382 .collect()
383 }
384
385 pub fn query_viewport(
394 &self,
395 start: usize,
396 end: usize,
397 marker_list: &MarkerList,
398 ) -> Vec<(&Overlay, Range<usize>)> {
399 use std::collections::HashMap;
400
401 let visible_markers = marker_list.query_range(start, end);
404
405 let marker_positions: HashMap<_, _> = visible_markers
407 .into_iter()
408 .map(|(id, start, _end)| (id, start))
409 .collect();
410
411 self.overlays
414 .iter()
415 .filter_map(|overlay| {
416 let start_pos = marker_positions.get(&overlay.start_marker)?;
418 let end_pos = marker_positions.get(&overlay.end_marker)?;
419
420 let range = *start_pos..*end_pos;
421
422 let included = if range.start == range.end {
427 range.start >= start && range.start <= end
428 } else {
429 range.start < end && range.end > start
430 };
431
432 if included {
433 Some((overlay, range))
434 } else {
435 None
436 }
437 })
438 .collect()
439 }
440
441 pub fn get_by_handle(&self, handle: &OverlayHandle) -> Option<&Overlay> {
443 self.overlays.iter().find(|o| &o.handle == handle)
444 }
445
446 pub fn get_by_handle_mut(&mut self, handle: &OverlayHandle) -> Option<&mut Overlay> {
448 self.overlays.iter_mut().find(|o| &o.handle == handle)
449 }
450
451 pub fn len(&self) -> usize {
453 self.overlays.len()
454 }
455
456 pub fn is_empty(&self) -> bool {
458 self.overlays.is_empty()
459 }
460
461 pub fn all(&self) -> &[Overlay] {
463 &self.overlays
464 }
465}
466
467impl Default for OverlayManager {
468 fn default() -> Self {
469 Self::new()
470 }
471}
472
473impl Overlay {
475 pub fn error(
477 marker_list: &mut MarkerList,
478 range: Range<usize>,
479 message: Option<String>,
480 ) -> Self {
481 let mut overlay = Self::with_priority(
482 marker_list,
483 range,
484 OverlayFace::Underline {
485 color: Color::Red,
486 style: UnderlineStyle::Wavy,
487 },
488 10, );
490 overlay.message = message;
491 overlay
492 }
493
494 pub fn warning(
496 marker_list: &mut MarkerList,
497 range: Range<usize>,
498 message: Option<String>,
499 ) -> Self {
500 let mut overlay = Self::with_priority(
501 marker_list,
502 range,
503 OverlayFace::Underline {
504 color: Color::Yellow,
505 style: UnderlineStyle::Wavy,
506 },
507 5, );
509 overlay.message = message;
510 overlay
511 }
512
513 pub fn info(
515 marker_list: &mut MarkerList,
516 range: Range<usize>,
517 message: Option<String>,
518 ) -> Self {
519 let mut overlay = Self::with_priority(
520 marker_list,
521 range,
522 OverlayFace::Underline {
523 color: Color::Blue,
524 style: UnderlineStyle::Wavy,
525 },
526 3, );
528 overlay.message = message;
529 overlay
530 }
531
532 pub fn hint(
534 marker_list: &mut MarkerList,
535 range: Range<usize>,
536 message: Option<String>,
537 ) -> Self {
538 let mut overlay = Self::with_priority(
539 marker_list,
540 range,
541 OverlayFace::Underline {
542 color: Color::Gray,
543 style: UnderlineStyle::Dotted,
544 },
545 1, );
547 overlay.message = message;
548 overlay
549 }
550
551 pub fn selection(marker_list: &mut MarkerList, range: Range<usize>) -> Self {
553 Self::with_priority(
554 marker_list,
555 range,
556 OverlayFace::Background {
557 color: Color::Rgb(38, 79, 120), },
559 -10, )
561 }
562
563 pub fn search_match(marker_list: &mut MarkerList, range: Range<usize>) -> Self {
565 Self::with_priority(
566 marker_list,
567 range,
568 OverlayFace::Background {
569 color: Color::Rgb(72, 72, 0), },
571 -5, )
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579
580 #[test]
581 fn test_overlay_creation_with_markers() {
582 let mut marker_list = MarkerList::new();
583 marker_list.set_buffer_size(100);
584
585 let overlay = Overlay::new(
586 &mut marker_list,
587 5..10,
588 OverlayFace::Background { color: Color::Red },
589 );
590
591 assert_eq!(marker_list.get_position(overlay.start_marker), Some(5));
592 assert_eq!(marker_list.get_position(overlay.end_marker), Some(10));
593 assert_eq!(overlay.range(&marker_list), 5..10);
594 }
595
596 #[test]
597 fn test_overlay_adjusts_with_insert() {
598 let mut marker_list = MarkerList::new();
599 marker_list.set_buffer_size(100);
600
601 let overlay = Overlay::new(
602 &mut marker_list,
603 10..20,
604 OverlayFace::Background { color: Color::Red },
605 );
606
607 marker_list.adjust_for_insert(5, 10);
609
610 assert_eq!(overlay.range(&marker_list), 20..30);
612 }
613
614 #[test]
615 fn test_overlay_adjusts_with_delete() {
616 let mut marker_list = MarkerList::new();
617 marker_list.set_buffer_size(100);
618
619 let overlay = Overlay::new(
620 &mut marker_list,
621 20..30,
622 OverlayFace::Background { color: Color::Red },
623 );
624
625 marker_list.adjust_for_delete(5, 10);
627
628 assert_eq!(overlay.range(&marker_list), 10..20);
630 }
631
632 #[test]
633 fn test_overlay_manager_add_remove() {
634 let mut marker_list = MarkerList::new();
635 marker_list.set_buffer_size(100);
636 let mut manager = OverlayManager::new();
637
638 let overlay = Overlay::new(
639 &mut marker_list,
640 5..10,
641 OverlayFace::Background { color: Color::Red },
642 );
643
644 let handle = manager.add(overlay);
645 assert_eq!(manager.len(), 1);
646
647 manager.remove_by_handle(&handle, &mut marker_list);
648 assert_eq!(manager.len(), 0);
649 }
650
651 #[test]
652 fn test_overlay_namespace_clear() {
653 let mut marker_list = MarkerList::new();
654 marker_list.set_buffer_size(100);
655 let mut manager = OverlayManager::new();
656
657 let ns = OverlayNamespace::from_string("todo".to_string());
658
659 let overlay1 = Overlay::with_namespace(
661 &mut marker_list,
662 5..10,
663 OverlayFace::Background { color: Color::Red },
664 ns.clone(),
665 );
666 let overlay2 = Overlay::with_namespace(
667 &mut marker_list,
668 15..20,
669 OverlayFace::Background { color: Color::Blue },
670 ns.clone(),
671 );
672 let overlay3 = Overlay::new(
674 &mut marker_list,
675 25..30,
676 OverlayFace::Background {
677 color: Color::Green,
678 },
679 );
680
681 manager.add(overlay1);
682 manager.add(overlay2);
683 manager.add(overlay3);
684 assert_eq!(manager.len(), 3);
685
686 manager.clear_namespace(&ns, &mut marker_list);
688 assert_eq!(manager.len(), 1); }
690
691 #[test]
692 fn test_overlay_priority_sorting() {
693 let mut marker_list = MarkerList::new();
694 marker_list.set_buffer_size(100);
695 let mut manager = OverlayManager::new();
696
697 manager.add(Overlay::with_priority(
698 &mut marker_list,
699 5..10,
700 OverlayFace::Background { color: Color::Red },
701 10,
702 ));
703 manager.add(Overlay::with_priority(
704 &mut marker_list,
705 5..10,
706 OverlayFace::Background { color: Color::Blue },
707 5,
708 ));
709 manager.add(Overlay::with_priority(
710 &mut marker_list,
711 5..10,
712 OverlayFace::Background {
713 color: Color::Green,
714 },
715 15,
716 ));
717
718 let overlays = manager.at_position(7, &marker_list);
719 assert_eq!(overlays.len(), 3);
720 assert_eq!(overlays[0].priority, 5);
722 assert_eq!(overlays[1].priority, 10);
723 assert_eq!(overlays[2].priority, 15);
724 }
725
726 #[test]
727 fn test_overlay_contains_and_overlaps() {
728 let mut marker_list = MarkerList::new();
729 marker_list.set_buffer_size(100);
730
731 let overlay = Overlay::new(
732 &mut marker_list,
733 10..20,
734 OverlayFace::Background { color: Color::Red },
735 );
736
737 assert!(!overlay.contains(9, &marker_list));
738 assert!(overlay.contains(10, &marker_list));
739 assert!(overlay.contains(15, &marker_list));
740 assert!(overlay.contains(19, &marker_list));
741 assert!(!overlay.contains(20, &marker_list));
742
743 assert!(!overlay.overlaps(&(0..10), &marker_list));
744 assert!(overlay.overlaps(&(5..15), &marker_list));
745 assert!(overlay.overlaps(&(15..25), &marker_list));
746 assert!(!overlay.overlaps(&(20..30), &marker_list));
747 }
748}