use std::collections::BTreeMap;
use super::identity::index_of;
pub(crate) struct LineIndex<'source> {
source: &'source str,
starts: Box<[usize]>,
}
pub(crate) type SourceLines<'snapshot> = BTreeMap<&'snapshot str, LineIndex<'snapshot>>;
pub(crate) trait SnapshotSource {
fn path(&self) -> &str;
fn text(&self) -> &str;
fn lines(&self) -> LineIndex<'_> {
LineIndex::new(self.text())
}
}
impl<'source> LineIndex<'source> {
pub(crate) fn new(source: &'source str) -> Self {
let breaks = source.bytes().filter(|byte| *byte == b'\n').count();
let mut starts = Vec::with_capacity(breaks.saturating_add(1));
starts.push(0_usize);
starts.extend(source.match_indices('\n').map(|(index, _)| index + 1));
Self {
source,
starts: starts.into_boxed_slice(),
}
}
pub(crate) fn holds(&self, line: u32, column: u32) -> bool {
let line = index_of(line);
let column = index_of(column);
self.line_bounds(line)
.is_some_and(|bounds| self.within(bounds, column))
}
pub(crate) fn line_bounds(&self, line: usize) -> Option<(usize, usize)> {
let start = self.starts.get(line).copied()?;
let end = match self.starts.get(line + 1) {
Some(next) => next.saturating_sub(1),
None => self.source.len(),
};
Some((start, end))
}
pub(crate) fn source(&self) -> &'source str {
self.source
}
fn within(&self, bounds: (usize, usize), column: usize) -> bool {
let (start, end) = bounds;
let offset = start.saturating_add(column);
offset <= end && self.source.is_char_boundary(offset)
}
}
pub(crate) fn index_sources(sources: &[impl SnapshotSource]) -> Box<[LineIndex<'_>]> {
sources.iter().map(SnapshotSource::lines).collect()
}
pub(crate) fn keyed_lines<'snapshot>(
sources: &'snapshot [impl SnapshotSource],
lines: Box<[LineIndex<'snapshot>]>,
) -> SourceLines<'snapshot> {
sources
.iter()
.map(SnapshotSource::path)
.zip(lines.into_vec())
.collect()
}
pub(crate) fn source_lines(sources: &[impl SnapshotSource]) -> SourceLines<'_> {
keyed_lines(sources, index_sources(sources))
}