pub struct SourceMap<'a> {
src: &'a str,
line_starts: Vec<u32>,
}
impl<'a> SourceMap<'a> {
pub fn new(src: &'a str) -> Self {
let mut line_starts = vec![0u32];
for (i, b) in src.bytes().enumerate() {
if b == b'\n' {
line_starts.push((i + 1) as u32);
}
}
SourceMap { src, line_starts }
}
pub fn line_col(&self, offset: u32) -> (usize, usize) {
let line = self.line_index(offset);
let start = self.line_starts[line] as usize;
let col = self
.src
.get(start..offset as usize)
.map(|s| s.chars().count())
.unwrap_or(0);
(line + 1, col + 1)
}
pub fn utf16_position(&self, offset: u32) -> (u32, u32) {
let line = self.line_index(offset);
let start = self.line_starts[line] as usize;
let col: u32 = self
.src
.get(start..offset as usize)
.map(|s| s.chars().map(|c| c.len_utf16() as u32).sum())
.unwrap_or(0);
(line as u32, col)
}
fn line_index(&self, offset: u32) -> usize {
match self.line_starts.binary_search(&offset) {
Ok(i) => i,
Err(i) => i - 1,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolves_line_and_col() {
let sm = SourceMap::new("ab\ncde\nf");
assert_eq!(sm.line_col(0), (1, 1)); assert_eq!(sm.line_col(1), (1, 2)); assert_eq!(sm.line_col(3), (2, 1)); assert_eq!(sm.line_col(5), (2, 3)); assert_eq!(sm.line_col(7), (3, 1)); }
#[test]
fn utf16_columns_count_code_units() {
let sm = SourceMap::new("😀x");
assert_eq!(sm.utf16_position(0), (0, 0)); assert_eq!(sm.utf16_position(4), (0, 2)); }
}