use std::cmp::Ordering;
use std::collections::HashMap;
use std::sync::Arc;
use crate::style::Style;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Span {
pub start: usize,
pub end: usize,
pub style: Style,
pub meta: Option<Arc<HashMap<String, String>>>,
}
impl Span {
pub fn new(start: usize, end: usize, style: Style) -> Self {
Span {
start,
end,
style,
meta: None,
}
}
pub fn with_meta(
start: usize,
end: usize,
style: Style,
meta: Option<Arc<HashMap<String, String>>>,
) -> Self {
Span {
start,
end,
style,
meta,
}
}
pub fn is_empty(&self) -> bool {
self.end <= self.start
}
pub fn split(&self, offset: usize) -> (Span, Option<Span>) {
if offset < self.start || offset >= self.end {
return (self.clone(), None);
}
let left = Span::with_meta(self.start, offset, self.style.clone(), self.meta.clone());
let right = Span::with_meta(offset, self.end, self.style.clone(), self.meta.clone());
(left, Some(right))
}
pub fn move_span(&self, offset: usize) -> Span {
Span::with_meta(
self.start.saturating_add(offset),
self.end.saturating_add(offset),
self.style.clone(),
self.meta.clone(),
)
}
pub fn right_crop(&self, offset: usize) -> Span {
Span::with_meta(
self.start,
std::cmp::min(offset, self.end),
self.style.clone(),
self.meta.clone(),
)
}
pub fn extend(&self, cells: usize) -> Span {
Span::with_meta(
self.start,
self.end + cells,
self.style.clone(),
self.meta.clone(),
)
}
}
impl PartialOrd for Span {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Span {
fn cmp(&self, other: &Self) -> Ordering {
(self.start, self.end).cmp(&(other.start, other.end))
}
}
impl std::hash::Hash for Span {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.start.hash(state);
self.end.hash(state);
self.style.hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::sync::Arc;
fn null_style() -> Style {
Style::null()
}
#[test]
fn span_new_has_no_meta() {
let s = Span::new(0, 5, null_style());
assert!(s.meta.is_none());
}
#[test]
fn span_with_meta_stores_meta() {
let mut m = HashMap::new();
m.insert("key".to_string(), "val".to_string());
let arc = Arc::new(m);
let s = Span::with_meta(0, 5, null_style(), Some(arc.clone()));
assert!(s.meta.is_some());
assert_eq!(
s.meta.as_ref().unwrap().get("key").map(|v| v.as_str()),
Some("val")
);
}
#[test]
fn span_meta_equality() {
let mut m = HashMap::new();
m.insert("k".to_string(), "v".to_string());
let s1 = Span::with_meta(0, 5, null_style(), Some(Arc::new(m.clone())));
let s2 = Span::with_meta(0, 5, null_style(), Some(Arc::new(m.clone())));
let s3 = Span::with_meta(0, 5, null_style(), None);
assert_eq!(s1, s2, "spans with equal meta must be equal");
assert_ne!(s1, s3, "span with meta != span without meta");
}
#[test]
fn span_clone_shares_meta_arc() {
let mut m = HashMap::new();
m.insert("x".to_string(), "y".to_string());
let arc = Arc::new(m);
let s = Span::with_meta(1, 4, null_style(), Some(arc.clone()));
let c = s.clone();
assert!(Arc::ptr_eq(
s.meta.as_ref().unwrap(),
c.meta.as_ref().unwrap()
));
}
#[test]
fn split_propagates_meta() {
let mut m = HashMap::new();
m.insert("a".to_string(), "b".to_string());
let s = Span::with_meta(0, 6, null_style(), Some(Arc::new(m)));
let (left, right) = s.split(3);
assert!(left.meta.is_some());
assert!(right.unwrap().meta.is_some());
}
#[test]
fn move_span_propagates_meta() {
let mut m = HashMap::new();
m.insert("p".to_string(), "q".to_string());
let s = Span::with_meta(0, 3, null_style(), Some(Arc::new(m)));
let shifted = s.move_span(10);
assert!(shifted.meta.is_some());
assert_eq!(shifted.start, 10);
assert_eq!(shifted.end, 13);
}
#[test]
fn right_crop_propagates_meta() {
let mut m = HashMap::new();
m.insert("r".to_string(), "s".to_string());
let s = Span::with_meta(0, 10, null_style(), Some(Arc::new(m)));
let cropped = s.right_crop(5);
assert!(cropped.meta.is_some());
assert_eq!(cropped.end, 5);
}
}