use ropey::Rope;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LineCol {
pub line: usize,
pub col: usize,
}
#[derive(Debug, Clone)]
pub struct LineIndex {
line_starts: Vec<usize>,
source: String,
}
impl LineIndex {
pub fn build(rope: &Rope) -> Self {
let source: String = rope.chunks().collect();
let mut line_starts = vec![0usize];
for (i, b) in source.as_bytes().iter().enumerate() {
if *b == b'\n' {
line_starts.push(i + 1);
}
}
Self {
line_starts,
source,
}
}
pub fn build_from_str(text: &str) -> Self {
let rope = Rope::from_str(text);
Self::build(&rope)
}
pub fn line_count(&self) -> usize {
self.line_starts.len()
}
pub fn line_col_to_byte(&self, lc: LineCol) -> Option<usize> {
let start = *self.line_starts.get(lc.line)?;
let offset = start + lc.col;
if offset > self.source.len() {
return None;
}
Some(offset)
}
pub fn byte_to_line_col(&self, byte_offset: usize) -> Option<LineCol> {
if byte_offset > self.source.len() {
return None;
}
let line = match self.line_starts.binary_search(&byte_offset) {
Ok(exact) => exact,
Err(insert) => insert - 1,
};
let col = byte_offset - self.line_starts[line];
Some(LineCol { line, col })
}
pub fn line_start_byte(&self, line: usize) -> Option<usize> {
self.line_starts.get(line).copied()
}
pub fn line_end_byte(&self, line: usize) -> Option<usize> {
if line >= self.line_starts.len() {
return None;
}
if line + 1 < self.line_starts.len() {
Some(self.line_starts[line + 1])
} else {
Some(self.source.len())
}
}
pub fn utf16_col_to_byte(&self, line: usize, utf16_col: usize) -> Option<usize> {
let line_start = *self.line_starts.get(line)?;
let line_end = self.line_end_byte(line)?;
let line_text = &self.source[line_start..line_end];
let mut utf16_units = 0usize;
for (byte_idx, ch) in line_text.char_indices() {
if utf16_units == utf16_col {
return Some(line_start + byte_idx);
}
utf16_units += ch.len_utf16();
}
if utf16_units == utf16_col {
return Some(line_start + line_text.len());
}
None
}
pub fn byte_to_utf16_col(&self, byte_offset: usize) -> Option<(usize, usize)> {
let lc = self.byte_to_line_col(byte_offset)?;
let line_start = self.line_starts[lc.line];
let prefix = &self.source[line_start..byte_offset];
let utf16_col: usize = prefix.chars().map(|c| c.len_utf16()).sum();
Some((lc.line, utf16_col))
}
}