use std::path::PathBuf;
use crate::span::FileId;
#[derive(Debug, Clone, PartialEq, Eq)]
struct LineIndex {
line_starts: Vec<u32>,
}
impl LineIndex {
fn new(text: &str) -> Self {
let mut line_starts = vec![0u32];
for (offset, byte) in text.bytes().enumerate() {
if byte == b'\n' {
line_starts.push((offset + 1) as u32);
}
}
Self { line_starts }
}
fn line_col(&self, offset: u32) -> (u32, u32) {
let line = match self.line_starts.binary_search(&offset) {
Ok(exact) => exact,
Err(insertion_point) => insertion_point - 1,
};
let column = offset - self.line_starts[line] + 1;
((line + 1) as u32, column)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceFile {
pub id: FileId,
pub path: PathBuf,
pub text: String,
line_index: LineIndex,
}
impl SourceFile {
pub fn new(id: FileId, path: impl Into<PathBuf>, text: impl Into<String>) -> Self {
let text = text.into();
let line_index = LineIndex::new(&text);
Self {
id,
path: path.into(),
text,
line_index,
}
}
pub fn line_col(&self, offset: u32) -> (u32, u32) {
self.line_index.line_col(offset)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first_line_is_one_one() {
let file = SourceFile::new(FileId(0), "test.vr", "let x;\n");
assert_eq!(file.line_col(0), (1, 1));
assert_eq!(file.line_col(4), (1, 5));
}
#[test]
fn second_line_offsets_convert_through_the_line_index() {
let file = SourceFile::new(FileId(0), "test.vr", "let x;\nlet y;\n");
assert_eq!(file.line_col(7), (2, 1));
assert_eq!(file.line_col(11), (2, 5));
}
#[test]
fn third_line_after_two_newlines() {
let file = SourceFile::new(FileId(0), "test.vr", "a\nb\nc");
assert_eq!(file.line_col(4), (3, 1));
}
}