use super::palette::StyleId;
use super::span::Span;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ResolvedSpan {
pub start: usize,
pub end: usize,
pub style: StyleId,
pub padded: bool,
}
pub(crate) fn merge_spans(input_len: usize, spans: &[Span]) -> Vec<ResolvedSpan> {
if spans.is_empty() {
return Vec::new();
}
let mut style_map: Vec<Option<(StyleId, u16)>> = vec![None; input_len];
for span in spans {
for slot in &mut style_map[span.start..span.end] {
match slot {
None => *slot = Some((span.style, span.priority)),
Some((_, existing_pri)) if span.priority < *existing_pri => {
*slot = Some((span.style, span.priority));
}
_ => {}
}
}
}
let mut padded_ranges: Vec<(usize, usize)> = spans.iter().filter(|s| s.padded).map(|s| (s.start, s.end)).collect();
padded_ranges.sort_unstable();
let mut result = Vec::new();
let mut i = 0;
while i < input_len {
if let Some((style, _)) = style_map[i] {
let start = i;
while i < input_len && style_map[i].is_some_and(|(s, _)| s == style) {
i += 1;
}
let padded = padded_ranges.binary_search(&(start, i)).is_ok();
result.push(ResolvedSpan {
start,
end: i,
style,
padded,
});
} else {
i += 1;
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
fn red() -> StyleId {
StyleId::new(0)
}
fn blue() -> StyleId {
StyleId::new(1)
}
fn yellow() -> StyleId {
StyleId::new(2)
}
fn padded_span(start: usize, end: usize, style: StyleId, priority: u16) -> Span {
Span {
start,
end,
style,
priority,
padded: true,
}
}
#[test]
fn empty_spans() {
let result = merge_spans(10, &[]);
assert!(result.is_empty());
}
fn resolved(start: usize, end: usize, style: StyleId) -> ResolvedSpan {
ResolvedSpan {
start,
end,
style,
padded: false,
}
}
fn padded(start: usize, end: usize, style: StyleId) -> ResolvedSpan {
ResolvedSpan {
start,
end,
style,
padded: true,
}
}
#[test]
fn single_span() {
let spans = [Span::new(2, 5, red(), 0)];
let result = merge_spans(10, &spans);
assert_eq!(result, vec![resolved(2, 5, red())]);
}
#[test]
fn non_overlapping_spans() {
let spans = [Span::new(0, 3, red(), 0), Span::new(5, 8, blue(), 1)];
let result = merge_spans(10, &spans);
assert_eq!(result, vec![resolved(0, 3, red()), resolved(5, 8, blue())]);
}
#[test]
fn overlapping_higher_priority_wins() {
let spans = [Span::new(0, 6, red(), 0), Span::new(3, 8, blue(), 1)];
let result = merge_spans(10, &spans);
assert_eq!(result, vec![resolved(0, 6, red()), resolved(6, 8, blue())]);
}
#[test]
fn lower_priority_fills_gaps() {
let spans = [Span::new(5, 7, red(), 0), Span::new(0, 10, yellow(), 1)];
let result = merge_spans(10, &spans);
assert_eq!(
result,
vec![
resolved(0, 5, yellow()),
resolved(5, 7, red()),
resolved(7, 10, yellow())
]
);
}
#[test]
fn adjacent_different_styles() {
let spans = [Span::new(0, 3, red(), 0), Span::new(3, 6, blue(), 0)];
let result = merge_spans(6, &spans);
assert_eq!(result, vec![resolved(0, 3, red()), resolved(3, 6, blue())]);
}
#[test]
fn intact_padded_span_keeps_padded_flag() {
let spans = [padded_span(2, 7, red(), 0)];
let result = merge_spans(10, &spans);
assert_eq!(result, vec![padded(2, 7, red())]);
}
#[test]
fn fragmented_padded_span_loses_padded_flag() {
let spans = [Span::new(4, 5, blue(), 0), padded_span(2, 7, red(), 1)];
let result = merge_spans(10, &spans);
assert_eq!(
result,
vec![resolved(2, 4, red()), resolved(4, 5, blue()), resolved(5, 7, red()),]
);
}
#[test]
fn multiple_padded_ranges_in_order() {
let spans = [padded_span(0, 4, red(), 0), padded_span(6, 10, blue(), 1)];
let result = merge_spans(10, &spans);
assert_eq!(result, vec![padded(0, 4, red()), padded(6, 10, blue())]);
}
#[test]
fn overlapping_padded_ranges_keep_padding_on_the_intact_one() {
let spans = [padded_span(4, 13, red(), 0), padded_span(0, 9, blue(), 1)];
let result = merge_spans(13, &spans);
assert_eq!(result, vec![resolved(0, 4, blue()), padded(4, 13, red())]);
}
}