use crate::model::marker::{MarkerId, MarkerList};
use ratatui::style::{Color, Style};
use std::ops::Range;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct OverlayHandle(pub String);
impl OverlayHandle {
pub fn new() -> Self {
static NEXT_HANDLE: AtomicU64 = AtomicU64::new(1);
Self(format!(
"ovl_{}",
NEXT_HANDLE.fetch_add(1, Ordering::Relaxed)
))
}
pub fn from_string(s: String) -> Self {
Self(s)
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for OverlayHandle {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct OverlayNamespace(pub String);
impl OverlayNamespace {
pub fn new() -> Self {
static NEXT_NAMESPACE: AtomicU64 = AtomicU64::new(1);
Self(format!(
"ns_{}",
NEXT_NAMESPACE.fetch_add(1, Ordering::Relaxed)
))
}
pub fn from_string(s: String) -> Self {
Self(s)
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for OverlayNamespace {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum OverlayFace {
Underline { color: Color, style: UnderlineStyle },
Background { color: Color },
Foreground { color: Color },
Style { style: Style },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnderlineStyle {
Straight,
Wavy,
Dotted,
Dashed,
}
pub type Priority = i32;
#[derive(Debug, Clone)]
pub struct Overlay {
pub handle: OverlayHandle,
pub namespace: Option<OverlayNamespace>,
pub start_marker: MarkerId,
pub end_marker: MarkerId,
pub face: OverlayFace,
pub priority: Priority,
pub message: Option<String>,
}
impl Overlay {
pub fn new(marker_list: &mut MarkerList, range: Range<usize>, face: OverlayFace) -> Self {
let start_marker = marker_list.create(range.start, true); let end_marker = marker_list.create(range.end, false);
Self {
handle: OverlayHandle::new(),
namespace: None,
start_marker,
end_marker,
face,
priority: 0,
message: None,
}
}
pub fn with_namespace(
marker_list: &mut MarkerList,
range: Range<usize>,
face: OverlayFace,
namespace: OverlayNamespace,
) -> Self {
let mut overlay = Self::new(marker_list, range, face);
overlay.namespace = Some(namespace);
overlay
}
pub fn with_priority(
marker_list: &mut MarkerList,
range: Range<usize>,
face: OverlayFace,
priority: Priority,
) -> Self {
let mut overlay = Self::new(marker_list, range, face);
overlay.priority = priority;
overlay
}
pub fn with_message(mut self, message: String) -> Self {
self.message = Some(message);
self
}
pub fn with_priority_value(mut self, priority: Priority) -> Self {
self.priority = priority;
self
}
pub fn with_namespace_value(mut self, namespace: OverlayNamespace) -> Self {
self.namespace = Some(namespace);
self
}
pub fn range(&self, marker_list: &MarkerList) -> Range<usize> {
let start = marker_list.get_position(self.start_marker).unwrap_or(0);
let end = marker_list.get_position(self.end_marker).unwrap_or(0);
start..end
}
pub fn contains(&self, position: usize, marker_list: &MarkerList) -> bool {
self.range(marker_list).contains(&position)
}
pub fn overlaps(&self, range: &Range<usize>, marker_list: &MarkerList) -> bool {
let self_range = self.range(marker_list);
self_range.start < range.end && range.start < self_range.end
}
}
#[derive(Debug, Clone)]
pub struct OverlayManager {
overlays: Vec<Overlay>,
}
impl OverlayManager {
pub fn new() -> Self {
Self {
overlays: Vec::new(),
}
}
pub fn add(&mut self, overlay: Overlay) -> OverlayHandle {
let handle = overlay.handle.clone();
self.overlays.push(overlay);
self.overlays.sort_by_key(|o| o.priority);
handle
}
pub fn remove_by_handle(
&mut self,
handle: &OverlayHandle,
marker_list: &mut MarkerList,
) -> bool {
if let Some(pos) = self.overlays.iter().position(|o| &o.handle == handle) {
let overlay = self.overlays.remove(pos);
marker_list.delete(overlay.start_marker);
marker_list.delete(overlay.end_marker);
true
} else {
false
}
}
pub fn clear_namespace(&mut self, namespace: &OverlayNamespace, marker_list: &mut MarkerList) {
let markers_to_delete: Vec<_> = self
.overlays
.iter()
.filter(|o| o.namespace.as_ref() == Some(namespace))
.flat_map(|o| vec![o.start_marker, o.end_marker])
.collect();
self.overlays
.retain(|o| o.namespace.as_ref() != Some(namespace));
for marker_id in markers_to_delete {
marker_list.delete(marker_id);
}
}
pub fn remove_in_range(&mut self, range: &Range<usize>, marker_list: &mut MarkerList) {
let markers_to_delete: Vec<_> = self
.overlays
.iter()
.filter(|o| o.overlaps(range, marker_list))
.flat_map(|o| vec![o.start_marker, o.end_marker])
.collect();
self.overlays.retain(|o| !o.overlaps(range, marker_list));
for marker_id in markers_to_delete {
marker_list.delete(marker_id);
}
}
pub fn clear(&mut self, marker_list: &mut MarkerList) {
for overlay in &self.overlays {
marker_list.delete(overlay.start_marker);
marker_list.delete(overlay.end_marker);
}
self.overlays.clear();
}
pub fn at_position(&self, position: usize, marker_list: &MarkerList) -> Vec<&Overlay> {
self.overlays
.iter()
.filter(|o| {
let range = o.range(marker_list);
range.contains(&position)
})
.collect()
}
pub fn in_range(&self, range: &Range<usize>, marker_list: &MarkerList) -> Vec<&Overlay> {
self.overlays
.iter()
.filter(|o| o.overlaps(range, marker_list))
.collect()
}
pub fn query_viewport(
&self,
start: usize,
end: usize,
marker_list: &MarkerList,
) -> Vec<(&Overlay, Range<usize>)> {
use std::collections::HashMap;
let visible_markers = marker_list.query_range(start, end);
let marker_positions: HashMap<_, _> = visible_markers
.into_iter()
.map(|(id, start, _end)| (id, start))
.collect();
self.overlays
.iter()
.filter_map(|overlay| {
let start_pos = marker_positions.get(&overlay.start_marker)?;
let end_pos = marker_positions.get(&overlay.end_marker)?;
let range = *start_pos..*end_pos;
if range.start < end && range.end > start {
Some((overlay, range))
} else {
None
}
})
.collect()
}
pub fn get_by_handle(&self, handle: &OverlayHandle) -> Option<&Overlay> {
self.overlays.iter().find(|o| &o.handle == handle)
}
pub fn get_by_handle_mut(&mut self, handle: &OverlayHandle) -> Option<&mut Overlay> {
self.overlays.iter_mut().find(|o| &o.handle == handle)
}
pub fn len(&self) -> usize {
self.overlays.len()
}
pub fn is_empty(&self) -> bool {
self.overlays.is_empty()
}
pub fn all(&self) -> &[Overlay] {
&self.overlays
}
}
impl Default for OverlayManager {
fn default() -> Self {
Self::new()
}
}
impl Overlay {
pub fn error(
marker_list: &mut MarkerList,
range: Range<usize>,
message: Option<String>,
) -> Self {
let mut overlay = Self::with_priority(
marker_list,
range,
OverlayFace::Underline {
color: Color::Red,
style: UnderlineStyle::Wavy,
},
10, );
overlay.message = message;
overlay
}
pub fn warning(
marker_list: &mut MarkerList,
range: Range<usize>,
message: Option<String>,
) -> Self {
let mut overlay = Self::with_priority(
marker_list,
range,
OverlayFace::Underline {
color: Color::Yellow,
style: UnderlineStyle::Wavy,
},
5, );
overlay.message = message;
overlay
}
pub fn info(
marker_list: &mut MarkerList,
range: Range<usize>,
message: Option<String>,
) -> Self {
let mut overlay = Self::with_priority(
marker_list,
range,
OverlayFace::Underline {
color: Color::Blue,
style: UnderlineStyle::Wavy,
},
3, );
overlay.message = message;
overlay
}
pub fn hint(
marker_list: &mut MarkerList,
range: Range<usize>,
message: Option<String>,
) -> Self {
let mut overlay = Self::with_priority(
marker_list,
range,
OverlayFace::Underline {
color: Color::Gray,
style: UnderlineStyle::Dotted,
},
1, );
overlay.message = message;
overlay
}
pub fn selection(marker_list: &mut MarkerList, range: Range<usize>) -> Self {
Self::with_priority(
marker_list,
range,
OverlayFace::Background {
color: Color::Rgb(38, 79, 120), },
-10, )
}
pub fn search_match(marker_list: &mut MarkerList, range: Range<usize>) -> Self {
Self::with_priority(
marker_list,
range,
OverlayFace::Background {
color: Color::Rgb(72, 72, 0), },
-5, )
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_overlay_creation_with_markers() {
let mut marker_list = MarkerList::new();
marker_list.set_buffer_size(100);
let overlay = Overlay::new(
&mut marker_list,
5..10,
OverlayFace::Background { color: Color::Red },
);
assert_eq!(marker_list.get_position(overlay.start_marker), Some(5));
assert_eq!(marker_list.get_position(overlay.end_marker), Some(10));
assert_eq!(overlay.range(&marker_list), 5..10);
}
#[test]
fn test_overlay_adjusts_with_insert() {
let mut marker_list = MarkerList::new();
marker_list.set_buffer_size(100);
let overlay = Overlay::new(
&mut marker_list,
10..20,
OverlayFace::Background { color: Color::Red },
);
marker_list.adjust_for_insert(5, 10);
assert_eq!(overlay.range(&marker_list), 20..30);
}
#[test]
fn test_overlay_adjusts_with_delete() {
let mut marker_list = MarkerList::new();
marker_list.set_buffer_size(100);
let overlay = Overlay::new(
&mut marker_list,
20..30,
OverlayFace::Background { color: Color::Red },
);
marker_list.adjust_for_delete(5, 10);
assert_eq!(overlay.range(&marker_list), 10..20);
}
#[test]
fn test_overlay_manager_add_remove() {
let mut marker_list = MarkerList::new();
marker_list.set_buffer_size(100);
let mut manager = OverlayManager::new();
let overlay = Overlay::new(
&mut marker_list,
5..10,
OverlayFace::Background { color: Color::Red },
);
let handle = manager.add(overlay);
assert_eq!(manager.len(), 1);
manager.remove_by_handle(&handle, &mut marker_list);
assert_eq!(manager.len(), 0);
}
#[test]
fn test_overlay_namespace_clear() {
let mut marker_list = MarkerList::new();
marker_list.set_buffer_size(100);
let mut manager = OverlayManager::new();
let ns = OverlayNamespace::from_string("todo".to_string());
let overlay1 = Overlay::with_namespace(
&mut marker_list,
5..10,
OverlayFace::Background { color: Color::Red },
ns.clone(),
);
let overlay2 = Overlay::with_namespace(
&mut marker_list,
15..20,
OverlayFace::Background { color: Color::Blue },
ns.clone(),
);
let overlay3 = Overlay::new(
&mut marker_list,
25..30,
OverlayFace::Background {
color: Color::Green,
},
);
manager.add(overlay1);
manager.add(overlay2);
manager.add(overlay3);
assert_eq!(manager.len(), 3);
manager.clear_namespace(&ns, &mut marker_list);
assert_eq!(manager.len(), 1); }
#[test]
fn test_overlay_priority_sorting() {
let mut marker_list = MarkerList::new();
marker_list.set_buffer_size(100);
let mut manager = OverlayManager::new();
manager.add(Overlay::with_priority(
&mut marker_list,
5..10,
OverlayFace::Background { color: Color::Red },
10,
));
manager.add(Overlay::with_priority(
&mut marker_list,
5..10,
OverlayFace::Background { color: Color::Blue },
5,
));
manager.add(Overlay::with_priority(
&mut marker_list,
5..10,
OverlayFace::Background {
color: Color::Green,
},
15,
));
let overlays = manager.at_position(7, &marker_list);
assert_eq!(overlays.len(), 3);
assert_eq!(overlays[0].priority, 5);
assert_eq!(overlays[1].priority, 10);
assert_eq!(overlays[2].priority, 15);
}
#[test]
fn test_overlay_contains_and_overlaps() {
let mut marker_list = MarkerList::new();
marker_list.set_buffer_size(100);
let overlay = Overlay::new(
&mut marker_list,
10..20,
OverlayFace::Background { color: Color::Red },
);
assert!(!overlay.contains(9, &marker_list));
assert!(overlay.contains(10, &marker_list));
assert!(overlay.contains(15, &marker_list));
assert!(overlay.contains(19, &marker_list));
assert!(!overlay.contains(20, &marker_list));
assert!(!overlay.overlaps(&(0..10), &marker_list));
assert!(overlay.overlaps(&(5..15), &marker_list));
assert!(overlay.overlaps(&(15..25), &marker_list));
assert!(!overlay.overlaps(&(20..30), &marker_list));
}
}