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 char_offset(&self, line: usize, column: usize) -> Option<usize> {
let (start, _) = self.line_bounds(line)?;
Some(start.saturating_add(self.char_column(line, column)?))
}
pub(crate) fn char_column(&self, line: usize, column: usize) -> Option<usize> {
let (start, end) = self.line_bounds(line)?;
let content = self.source.get(start..end)?;
Some(
content
.char_indices()
.nth(column)
.map_or(content.len(), |(offset, _)| offset),
)
}
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 find<'sources, S: SnapshotSource>(
sources: &'sources [S],
path: &str,
) -> Option<&'sources S> {
sources
.binary_search_by(|source| source.path().cmp(path))
.ok()
.and_then(|index| sources.get(index))
}
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))
}