use crate::ast::{Position, Span};
pub struct LineIndex {
line_starts: Vec<usize>,
}
impl LineIndex {
pub fn new(source: &str) -> Self {
let mut line_starts = vec![0usize];
for (i, ch) in source.char_indices() {
if ch == '\n' {
line_starts.push(i + 1);
}
}
Self { line_starts }
}
pub fn position(&self, offset: usize) -> Position {
let line_idx = match self.line_starts.binary_search(&offset) {
Ok(exact) => exact, Err(ins) => ins - 1, };
let col = offset - self.line_starts[line_idx];
Position::new(line_idx + 1, col + 1, offset)
}
pub fn span(&self, range: rowan::TextRange) -> Span {
let start: usize = range.start().into();
let end: usize = range.end().into();
Span::new(self.position(start), self.position(end))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_line() {
let idx = LineIndex::new("listen 80;");
assert_eq!(idx.position(0), Position::new(1, 1, 0));
assert_eq!(idx.position(7), Position::new(1, 8, 7));
}
#[test]
fn multi_line() {
let src = "http {\n listen 80;\n}\n";
let idx = LineIndex::new(src);
assert_eq!(idx.position(0), Position::new(1, 1, 0));
assert_eq!(idx.position(7), Position::new(2, 1, 7));
assert_eq!(idx.position(11), Position::new(2, 5, 11)); assert_eq!(idx.position(22), Position::new(3, 1, 22));
}
#[test]
fn span_conversion() {
let src = "listen 80;";
let idx = LineIndex::new(src);
let range = rowan::TextRange::new(0.into(), 6.into());
let span = idx.span(range);
assert_eq!(span.start, Position::new(1, 1, 0));
assert_eq!(span.end, Position::new(1, 7, 6));
}
}