use vb6parse::parsers::SyntaxKind;
use vb6parse::parsers::cst::CstNode;
#[derive(Debug, Clone, Default)]
pub struct LineIndex {
line_starts: Vec<u32>,
}
impl LineIndex {
pub fn from_cst_root(root: &CstNode) -> Self {
let mut line_starts = vec![0];
for node in root.descendants() {
if node.is_token() && node.kind() == SyntaxKind::Newline {
line_starts.push(node.end_offset());
}
}
Self { line_starts }
}
pub fn line_count(&self) -> usize {
self.line_starts.len()
}
pub fn position(&self, offset: u32) -> (usize, usize) {
let lo = self.line_starts.partition_point(|start| *start <= offset);
let line_index = lo.saturating_sub(1);
let line = line_index + 1;
let column = (offset - self.line_starts[line_index] + 1) as usize;
(line, column)
}
}
#[cfg(test)]
mod tests {
use super::*;
use vb6parse::parsers::cst::ConcreteSyntaxTree;
fn index_for(source: &str) -> LineIndex {
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert!(failures.is_empty(), "parse failures: {failures:?}");
let cst = cst_opt.expect("CST should parse");
LineIndex::from_cst_root(&cst.to_root_node())
}
#[test]
fn single_line_positions() {
let index = index_for("Foo = 1");
assert_eq!(index.position(0), (1, 1));
assert_eq!(index.position(4), (1, 5));
assert_eq!(index.position(7), (1, 8));
}
#[test]
fn positions_across_newlines() {
let index = index_for("Sub Foo()\n x = 1\nEnd Sub\n");
assert_eq!(index.position(9), (1, 10));
assert_eq!(index.position(10), (2, 1));
assert_eq!(index.position(12), (2, 3));
assert_eq!(index.position(19), (3, 2));
assert_eq!(index.position(24), (3, 7));
assert_eq!(index.position(26), (4, 1));
}
#[test]
fn offset_past_last_newline_clamps_to_last_line() {
let index = index_for("a = 1\n");
assert_eq!(index.position(5), (1, 6));
assert_eq!(index.position(100), (2, 95));
}
#[test]
fn empty_source_has_single_line() {
let index = index_for("");
assert_eq!(index.line_count(), 1);
assert_eq!(index.position(0), (1, 1));
}
#[test]
fn crlf_newlines_advance_one_line() {
let index = index_for("a = 1\r\nb = 2\r\n");
assert_eq!(index.position(6), (1, 7));
assert_eq!(index.position(7), (2, 1));
assert_eq!(index.position(13), (2, 7));
assert_eq!(index.position(14), (3, 1));
}
}