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 remove_by_handle(
293 &mut self,
294 handle: &OverlayHandle,
295 marker_list: &mut MarkerList,
296 ) -> bool {
297 if let Some(pos) = self.overlays.iter().position(|o| &o.handle == handle) {
298 let overlay = self.overlays.remove(pos);
299 marker_list.delete(overlay.start_marker);
300 marker_list.delete(overlay.end_marker);
301 true
302 } else {
303 false
304 }
305 }
306
307 pub fn clear_namespace(&mut self, namespace: &OverlayNamespace, marker_list: &mut MarkerList) {
309 let markers_to_delete: Vec<_> = self
311 .overlays
312 .iter()
313 .filter(|o| o.namespace.as_ref() == Some(namespace))
314 .flat_map(|o| vec![o.start_marker, o.end_marker])
315 .collect();
316
317 self.overlays
319 .retain(|o| o.namespace.as_ref() != Some(namespace));
320
321 for marker_id in markers_to_delete {
323 marker_list.delete(marker_id);
324 }
325 }
326
327 pub fn replace_range_in_namespace(
332 &mut self,
333 namespace: &OverlayNamespace,
334 range: &Range<usize>,
335 mut new_overlays: Vec<Overlay>,
336 marker_list: &mut MarkerList,
337 ) {
338 let mut markers_to_delete = Vec::new();
339
340 self.overlays.retain(|overlay| {
341 let in_namespace = overlay.namespace.as_ref() == Some(namespace);
342 if in_namespace && overlay.overlaps(range, marker_list) {
343 markers_to_delete.push(overlay.start_marker);
344 markers_to_delete.push(overlay.end_marker);
345 false
346 } else {
347 true
348 }
349 });
350
351 for marker_id in markers_to_delete {
352 marker_list.delete(marker_id);
353 }
354
355 if !new_overlays.is_empty() {
356 self.overlays.append(&mut new_overlays);
357 self.overlays.sort_by_key(|o| o.priority);
358 }
359 }
360
361 pub fn remove_in_range(&mut self, range: &Range<usize>, marker_list: &mut MarkerList) {
363 let markers_to_delete: Vec<_> = self
365 .overlays
366 .iter()
367 .filter(|o| o.overlaps(range, marker_list))
368 .flat_map(|o| vec![o.start_marker, o.end_marker])
369 .collect();
370
371 self.overlays.retain(|o| !o.overlaps(range, marker_list));
373
374 for marker_id in markers_to_delete {
376 marker_list.delete(marker_id);
377 }
378 }
379
380 pub fn clear(&mut self, marker_list: &mut MarkerList) {
382 for overlay in &self.overlays {
384 marker_list.delete(overlay.start_marker);
385 marker_list.delete(overlay.end_marker);
386 }
387
388 self.overlays.clear();
389 }
390
391 pub fn at_position(&self, position: usize, marker_list: &MarkerList) -> Vec<&Overlay> {
393 self.overlays
394 .iter()
395 .filter(|o| {
396 let range = o.range(marker_list);
397 range.contains(&position)
398 })
399 .collect()
400 }
401
402 pub fn in_range(&self, range: &Range<usize>, marker_list: &MarkerList) -> Vec<&Overlay> {
404 self.overlays
405 .iter()
406 .filter(|o| o.overlaps(range, marker_list))
407 .collect()
408 }
409
410 pub fn query_viewport(
419 &self,
420 start: usize,
421 end: usize,
422 marker_list: &MarkerList,
423 ) -> Vec<(&Overlay, Range<usize>)> {
424 use std::collections::HashMap;
425
426 let visible_markers = marker_list.query_range(start, end);
429
430 let marker_positions: HashMap<_, _> = visible_markers
432 .into_iter()
433 .map(|(id, start, _end)| (id, start))
434 .collect();
435
436 self.overlays
442 .iter()
443 .filter_map(|overlay| {
444 let start_in_vp = marker_positions.get(&overlay.start_marker).copied();
445 let end_in_vp = marker_positions.get(&overlay.end_marker).copied();
446
447 if start_in_vp.is_none() && end_in_vp.is_none() {
450 return None;
451 }
452
453 let start_pos =
455 start_in_vp.or_else(|| marker_list.get_position(overlay.start_marker))?;
456 let end_pos = end_in_vp.or_else(|| marker_list.get_position(overlay.end_marker))?;
457
458 let range = start_pos..end_pos;
459
460 let included = if range.start == range.end {
465 range.start >= start && range.start <= end
466 } else {
467 range.start < end && range.end > start
468 };
469
470 if included {
471 Some((overlay, range))
472 } else {
473 None
474 }
475 })
476 .collect()
477 }
478
479 pub fn get_by_handle(&self, handle: &OverlayHandle) -> Option<&Overlay> {
481 self.overlays.iter().find(|o| &o.handle == handle)
482 }
483
484 pub fn get_by_handle_mut(&mut self, handle: &OverlayHandle) -> Option<&mut Overlay> {
486 self.overlays.iter_mut().find(|o| &o.handle == handle)
487 }
488
489 pub fn len(&self) -> usize {
491 self.overlays.len()
492 }
493
494 pub fn is_empty(&self) -> bool {
496 self.overlays.is_empty()
497 }
498
499 pub fn all(&self) -> &[Overlay] {
501 &self.overlays
502 }
503}
504
505impl Default for OverlayManager {
506 fn default() -> Self {
507 Self::new()
508 }
509}
510
511impl Overlay {
513 pub fn error(
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::Red,
524 style: UnderlineStyle::Wavy,
525 },
526 10, );
528 overlay.message = message;
529 overlay
530 }
531
532 pub fn warning(
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::Yellow,
543 style: UnderlineStyle::Wavy,
544 },
545 5, );
547 overlay.message = message;
548 overlay
549 }
550
551 pub fn info(
553 marker_list: &mut MarkerList,
554 range: Range<usize>,
555 message: Option<String>,
556 ) -> Self {
557 let mut overlay = Self::with_priority(
558 marker_list,
559 range,
560 OverlayFace::Underline {
561 color: Color::Blue,
562 style: UnderlineStyle::Wavy,
563 },
564 3, );
566 overlay.message = message;
567 overlay
568 }
569
570 pub fn hint(
572 marker_list: &mut MarkerList,
573 range: Range<usize>,
574 message: Option<String>,
575 ) -> Self {
576 let mut overlay = Self::with_priority(
577 marker_list,
578 range,
579 OverlayFace::Underline {
580 color: Color::Gray,
581 style: UnderlineStyle::Dotted,
582 },
583 1, );
585 overlay.message = message;
586 overlay
587 }
588
589 pub fn selection(marker_list: &mut MarkerList, range: Range<usize>) -> Self {
591 let mut overlay = Self::with_priority(
592 marker_list,
593 range,
594 OverlayFace::Background {
595 color: Color::Rgb(38, 79, 120), },
597 -10, );
599 overlay.theme_key = Some("editor.selection_bg");
600 overlay
601 }
602
603 pub fn search_match(marker_list: &mut MarkerList, range: Range<usize>) -> Self {
605 let mut overlay = Self::with_priority(
606 marker_list,
607 range,
608 OverlayFace::Background {
609 color: Color::Rgb(72, 72, 0), },
611 -5, );
613 overlay.theme_key = Some("search.match_bg");
614 overlay
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621
622 #[test]
623 fn test_overlay_creation_with_markers() {
624 let mut marker_list = MarkerList::new();
625 marker_list.set_buffer_size(100);
626
627 let overlay = Overlay::new(
628 &mut marker_list,
629 5..10,
630 OverlayFace::Background { color: Color::Red },
631 );
632
633 assert_eq!(marker_list.get_position(overlay.start_marker), Some(5));
634 assert_eq!(marker_list.get_position(overlay.end_marker), Some(10));
635 assert_eq!(overlay.range(&marker_list), 5..10);
636 }
637
638 #[test]
639 fn test_overlay_adjusts_with_insert() {
640 let mut marker_list = MarkerList::new();
641 marker_list.set_buffer_size(100);
642
643 let overlay = Overlay::new(
644 &mut marker_list,
645 10..20,
646 OverlayFace::Background { color: Color::Red },
647 );
648
649 marker_list.adjust_for_insert(5, 10);
651
652 assert_eq!(overlay.range(&marker_list), 20..30);
654 }
655
656 #[test]
657 fn test_overlay_adjusts_with_delete() {
658 let mut marker_list = MarkerList::new();
659 marker_list.set_buffer_size(100);
660
661 let overlay = Overlay::new(
662 &mut marker_list,
663 20..30,
664 OverlayFace::Background { color: Color::Red },
665 );
666
667 marker_list.adjust_for_delete(5, 10);
669
670 assert_eq!(overlay.range(&marker_list), 10..20);
672 }
673
674 #[test]
675 fn test_overlay_manager_add_remove() {
676 let mut marker_list = MarkerList::new();
677 marker_list.set_buffer_size(100);
678 let mut manager = OverlayManager::new();
679
680 let overlay = Overlay::new(
681 &mut marker_list,
682 5..10,
683 OverlayFace::Background { color: Color::Red },
684 );
685
686 let handle = manager.add(overlay);
687 assert_eq!(manager.len(), 1);
688
689 manager.remove_by_handle(&handle, &mut marker_list);
690 assert_eq!(manager.len(), 0);
691 }
692
693 #[test]
694 fn test_overlay_namespace_clear() {
695 let mut marker_list = MarkerList::new();
696 marker_list.set_buffer_size(100);
697 let mut manager = OverlayManager::new();
698
699 let ns = OverlayNamespace::from_string("todo".to_string());
700
701 let overlay1 = Overlay::with_namespace(
703 &mut marker_list,
704 5..10,
705 OverlayFace::Background { color: Color::Red },
706 ns.clone(),
707 );
708 let overlay2 = Overlay::with_namespace(
709 &mut marker_list,
710 15..20,
711 OverlayFace::Background { color: Color::Blue },
712 ns.clone(),
713 );
714 let overlay3 = Overlay::new(
716 &mut marker_list,
717 25..30,
718 OverlayFace::Background {
719 color: Color::Green,
720 },
721 );
722
723 manager.add(overlay1);
724 manager.add(overlay2);
725 manager.add(overlay3);
726 assert_eq!(manager.len(), 3);
727
728 manager.clear_namespace(&ns, &mut marker_list);
730 assert_eq!(manager.len(), 1); }
732
733 #[test]
734 fn test_overlay_priority_sorting() {
735 let mut marker_list = MarkerList::new();
736 marker_list.set_buffer_size(100);
737 let mut manager = OverlayManager::new();
738
739 manager.add(Overlay::with_priority(
740 &mut marker_list,
741 5..10,
742 OverlayFace::Background { color: Color::Red },
743 10,
744 ));
745 manager.add(Overlay::with_priority(
746 &mut marker_list,
747 5..10,
748 OverlayFace::Background { color: Color::Blue },
749 5,
750 ));
751 manager.add(Overlay::with_priority(
752 &mut marker_list,
753 5..10,
754 OverlayFace::Background {
755 color: Color::Green,
756 },
757 15,
758 ));
759
760 let overlays = manager.at_position(7, &marker_list);
761 assert_eq!(overlays.len(), 3);
762 assert_eq!(overlays[0].priority, 5);
764 assert_eq!(overlays[1].priority, 10);
765 assert_eq!(overlays[2].priority, 15);
766 }
767
768 #[test]
769 fn test_overlay_contains_and_overlaps() {
770 let mut marker_list = MarkerList::new();
771 marker_list.set_buffer_size(100);
772
773 let overlay = Overlay::new(
774 &mut marker_list,
775 10..20,
776 OverlayFace::Background { color: Color::Red },
777 );
778
779 assert!(!overlay.contains(9, &marker_list));
780 assert!(overlay.contains(10, &marker_list));
781 assert!(overlay.contains(15, &marker_list));
782 assert!(overlay.contains(19, &marker_list));
783 assert!(!overlay.contains(20, &marker_list));
784
785 assert!(!overlay.overlaps(&(0..10), &marker_list));
786 assert!(overlay.overlaps(&(5..15), &marker_list));
787 assert!(overlay.overlaps(&(15..25), &marker_list));
788 assert!(!overlay.overlaps(&(20..30), &marker_list));
789 }
790}