use crate::model::{Container, Line};
use std::ops::Range;
#[derive(Debug, Clone, PartialEq)]
pub struct Span<'a> {
pub container: &'a Container,
pub range: Range<usize>,
}
pub fn runs(lines: &[Line], range: Range<usize>, depth: usize) -> Spans<'_> {
Spans {
lines,
end: range.end.min(lines.len()),
at: range.start,
depth,
same: |a, b| a.same_run(b) && a.instance() == b.instance(),
}
}
pub fn items(lines: &[Line], range: Range<usize>, depth: usize) -> Spans<'_> {
Spans {
lines,
end: range.end.min(lines.len()),
at: range.start,
depth,
same: |a, b| a == b,
}
}
pub fn segment(lines: &[Line], range: Range<usize>, depth: usize) -> Range<usize> {
let end = range.end.min(lines.len());
let mut j = (range.start + 1).min(end);
while j < end && lines[j].containers.len() == depth && lines[j].continues {
j += 1;
}
range.start..j
}
pub struct Spans<'a> {
lines: &'a [Line],
end: usize,
at: usize,
depth: usize,
same: fn(&Container, &Container) -> bool,
}
impl<'a> Iterator for Spans<'a> {
type Item = Span<'a>;
fn next(&mut self) -> Option<Span<'a>> {
while self.at < self.end && self.lines[self.at].containers.len() <= self.depth {
self.at += 1;
}
if self.at >= self.end {
return None;
}
let start = self.at;
let container = &self.lines[start].containers[self.depth];
let mut j = start + 1;
while j < self.end
&& self.lines[j]
.containers
.get(self.depth)
.is_some_and(|c| (self.same)(c, container))
{
j += 1;
}
self.at = j;
Some(Span {
container,
range: start..j,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{Content, LineKind};
fn li(ordinal: u64, instance: u64) -> Container {
Container::ListItem {
ordered: false,
start: 1,
ordinal,
instance,
}
}
fn content(paths: &[Vec<Container>]) -> Content {
let text = vec!["x"; paths.len()].join("\n");
let lines = paths
.iter()
.map(|p| {
let mut l = Line::new(LineKind::Para);
l.containers = p.clone();
l
})
.collect();
Content::new(text, lines)
}
fn spans(it: Spans<'_>) -> Vec<Range<usize>> {
it.map(|s| s.range).collect()
}
#[test]
fn a_run_spans_its_items_and_an_item_spans_its_paragraphs() {
let rt = content(&[vec![li(0, 0)], vec![li(0, 0)], vec![li(1, 0)]]);
assert_eq!(spans(runs(&rt.lines, 0..3, 0)), vec![0..3]);
assert_eq!(spans(items(&rt.lines, 0..3, 0)), vec![0..2, 2..3]);
}
#[test]
fn instance_ends_a_run_that_shape_alone_would_weld() {
let rt = content(&[vec![li(0, 0)], vec![li(0, 1)]]);
assert_eq!(spans(runs(&rt.lines, 0..2, 0)), vec![0..1, 1..2]);
}
#[test]
fn a_line_without_a_container_at_depth_ends_a_run_and_is_skipped() {
let rt = content(&[vec![li(0, 0)], vec![], vec![li(0, 0)]]);
assert_eq!(spans(runs(&rt.lines, 0..3, 0)), vec![0..1, 2..3]);
}
#[test]
fn a_range_bounds_the_parent_two_like_runs_nest_under() {
let rt = content(&[
vec![li(0, 0), Container::Quote { instance: 0 }],
vec![li(1, 0), Container::Quote { instance: 0 }],
]);
assert_eq!(spans(runs(&rt.lines, 0..2, 1)), vec![0..2]);
let per_item: Vec<_> = items(&rt.lines, 0..2, 0)
.flat_map(|item| spans(runs(&rt.lines, item.range, 1)))
.collect();
assert_eq!(per_item, vec![0..1, 1..2]);
}
fn seg(paths: &[Vec<Container>], continues: &[bool], depth: usize) -> Range<usize> {
let mut rt = content(paths);
for (line, &c) in rt.lines.iter_mut().zip(continues) {
line.continues = c;
}
segment(&rt.lines, 0..rt.lines.len(), depth)
}
#[test]
fn a_segment_takes_the_continuations_at_its_own_depth() {
assert_eq!(seg(&[vec![], vec![], vec![]], &[false, true, true], 0), 0..3);
assert_eq!(seg(&[vec![], vec![], vec![]], &[false, true, false], 0), 0..2);
}
#[test]
fn a_continuation_at_another_depth_ends_the_segment() {
let paths = &[vec![], vec![li(0, 0)]];
assert_eq!(seg(paths, &[false, true], 0), 0..1);
}
#[test]
fn a_range_end_past_the_last_line_is_clamped() {
let rt = content(&[vec![li(0, 0)]]);
assert_eq!(spans(runs(&rt.lines, 0..99, 0)), vec![0..1]);
assert_eq!(segment(&rt.lines, 0..99, 0), 0..1);
assert_eq!(segment(&[], 0..99, 0), 0..0);
}
}