#[derive(Debug, Clone)]
pub struct LineIndex {
text: String,
starts: Vec<u32>,
}
impl LineIndex {
pub fn new(text: &str) -> LineIndex {
let mut starts = vec![0];
for (i, b) in text.bytes().enumerate() {
if b == b'\n' {
starts.push(i as u32 + 1);
}
}
LineIndex {
text: text.to_owned(),
starts,
}
}
pub fn len(&self) -> u32 {
self.text.len() as u32
}
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
pub fn position(&self, offset: u32) -> (u32, u32) {
let offset = offset.min(self.len());
let line = match self.starts.binary_search(&offset) {
Ok(line) => line,
Err(next) => next - 1,
};
let start = self.starts[line] as usize;
let mut column = 0u32;
for (i, c) in self.text[start..].char_indices() {
if start + i >= offset as usize {
break;
}
column += c.len_utf16() as u32;
}
(line as u32, column)
}
pub fn offset(&self, line: u32, column: u32) -> u32 {
let Some(&start) = self.starts.get(line as usize) else {
return self.len();
};
let end = self.line_end(line as usize);
let mut units = 0u32;
for (i, c) in self.text[start as usize..end].char_indices() {
let next = units + c.len_utf16() as u32;
if next > column {
return start + i as u32;
}
units = next;
}
end as u32
}
fn line_end(&self, line: usize) -> usize {
let start = self.starts[line] as usize;
let mut end = self
.starts
.get(line + 1)
.map_or(self.text.len(), |&s| s as usize);
let bytes = self.text.as_bytes();
if end > start && bytes[end - 1] == b'\n' {
end -= 1;
}
if end > start && bytes[end - 1] == b'\r' {
end -= 1;
}
end
}
}