use std::{borrow::Cow, ops::Range};
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct Span {
pub start: u32,
pub end: u32,
}
impl Span {
pub fn new(start: u32, end: u32) -> Self {
Self { start, end }
}
pub fn empty(at: u32) -> Self {
Self { start: at, end: at }
}
pub fn contains(self, offset: u32) -> bool {
self.start <= offset && offset < self.end
}
#[expect(clippy::cast_possible_truncation)] pub fn at(base: u32, range: Range<usize>) -> Self {
Self::new(base + range.start as u32, base + range.end as u32)
}
pub fn slice(self, source: &str) -> &str {
&source[self.start as usize..self.end as usize]
}
pub fn is_empty(self) -> bool {
self.start == self.end
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct Segment {
pub span: Span,
pub padding: u8,
}
impl Segment {
pub fn new(span: Span, padding: u8) -> Self {
Self { span, padding }
}
pub fn join<'s>(source: &'s str, pieces: &[Segment]) -> Cow<'s, str> {
match pieces {
[single] if single.padding == 0 => Cow::Borrowed(single.span.slice(source)),
_ => {
let mut out = String::new();
for (i, piece) in pieces.iter().enumerate() {
if i > 0 {
out.push('\n');
}
out.extend(std::iter::repeat_n(' ', usize::from(piece.padding)));
out.push_str(piece.span.slice(source));
}
Cow::Owned(out)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn span_basics() {
let span = Span::at(10, 2..5);
assert_eq!(span, Span::new(12, 15));
assert!(!span.is_empty());
assert!(Span::empty(3).is_empty());
assert_eq!(Span::new(2, 5).slice("abcdefg"), "cde");
}
#[test]
fn segment_join() {
let source = "> a\n> b\n";
let a = Segment::new(Span::new(2, 3), 0);
let b = Segment::new(Span::new(6, 9), 2);
assert!(matches!(Segment::join(source, &[a]), Cow::Borrowed("a")));
assert_eq!(Segment::join(source, &[b]), " b");
assert_eq!(Segment::join(source, &[a, b]), "a\n b");
}
}