use libcst_native::tokenize;
pub struct SpanIndex<'a> {
src: &'a str,
line_starts: Vec<usize>, }
impl<'a> SpanIndex<'a> {
pub fn new(src: &'a str) -> Self {
let mut line_starts = vec![0usize];
for (i, b) in src.bytes().enumerate() {
if b == b'\n' {
line_starts.push(i + 1);
}
}
SpanIndex { src, line_starts }
}
pub fn src(&self) -> &'a str {
self.src
}
pub fn line_col(&self, byte_off: usize) -> (usize, usize) {
let line_idx = match self.line_starts.binary_search(&byte_off) {
Ok(i) => i,
Err(i) => i - 1,
};
let line_start = self.line_starts[line_idx];
let col_chars = self.src[line_start..byte_off].chars().count();
(line_idx + 1, col_chars + 1)
}
}
pub(crate) fn anchor_of_subslice(src: &str, sub: &str) -> usize {
sub.as_ptr() as usize - src.as_ptr() as usize
}
pub fn lambda_anchors(src: &str) -> Option<Vec<(usize, usize)>> {
tokenize(src)
.map(|toks| {
toks.iter()
.filter(|t| t.string == "lambda")
.map(|t| {
(
t.start_pos.line_number(),
t.start_pos.char_column_number() + 1,
)
})
.collect()
})
.ok()
}
pub fn strip_bom(src: &str) -> &str {
src.strip_prefix('\u{feff}').unwrap_or(src)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn line_col_counts_chars_not_bytes() {
let src = "x = 'é'\ndef f():\n pass\n"; let idx = SpanIndex::new(src);
let byte_off = src.find("def").unwrap();
assert_eq!(idx.line_col(byte_off), (2, 1)); }
#[test]
fn anchor_of_subslice_is_exact() {
let src = "def greet():\n pass\n";
let name = &src[4..9]; assert_eq!(anchor_of_subslice(src, name), 4);
}
#[test]
fn lambda_anchors_in_source_order() {
let src = "a = lambda: 1\nb = lambda y: y\n";
let anchors = lambda_anchors(src).expect("tokenize must succeed on valid Python");
assert_eq!(anchors, vec![(1, 5), (2, 5)]); }
#[test]
fn strip_bom_removes_bom() {
let with_bom = "\u{feff}def f(): pass\n";
assert_eq!(strip_bom(with_bom), "def f(): pass\n");
}
#[test]
fn strip_bom_noop_without_bom() {
let src = "def f(): pass\n";
assert_eq!(strip_bom(src), src);
}
}