use crate::codespan::{LineColumn, Range, Span};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LineNumbers {
pub line_starts: Vec<u32>,
pub length: u32,
pub mapping: HashMap<usize, CharLen>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CharLen {
pub length_utf8: u8,
pub length_utf16: u8,
}
impl LineNumbers {
pub fn new(src: &str) -> Self {
Self {
length: src.len() as u32,
line_starts: std::iter::once(0)
.chain(src.match_indices('\n').map(|(i, _)| i as u32 + 1))
.collect(),
mapping: Self::mapping(src),
}
}
fn mapping(src: &str) -> HashMap<usize, CharLen> {
let mut map = HashMap::new();
for (i, char) in src.char_indices() {
let length = char.len_utf8();
if length != 1 {
_ = map.insert(
i,
CharLen {
length_utf8: length as u8,
length_utf16: char.len_utf16() as u8,
},
);
}
}
map
}
pub fn line_number(&self, byte_index: u32) -> u32 {
self.line_starts
.binary_search(&byte_index)
.unwrap_or_else(|next_line| next_line - 1) as u32
}
pub fn line_and_column_number(&self, byte_index: u32) -> LineColumn {
let line = self.line_number(byte_index);
let line_start = self
.line_starts
.get(line as usize)
.copied()
.unwrap_or_default();
let mut u8_offset = line_start;
let mut u16_offset = 0;
loop {
if u8_offset >= byte_index {
break;
}
if let Some(length) = self.mapping.get(&(u8_offset as usize)) {
u8_offset += length.length_utf8 as u32;
u16_offset += length.length_utf16 as u32;
} else {
u16_offset += 1;
u8_offset += 1;
}
}
LineColumn {
line,
column: u16_offset,
}
}
pub fn byte_index_of_line_col(&self, position: LineColumn) -> u32 {
let line_start = match self.line_starts.get(position.line as usize) {
Some(&line_start) => line_start,
None => return self.length,
};
let mut u8_offset = line_start;
let mut u16_offset = 0;
loop {
if u16_offset >= position.column {
break;
}
if let Some(length) = self.mapping.get(&(u8_offset as usize)) {
u8_offset += length.length_utf8 as u32;
u16_offset += length.length_utf16 as u32;
} else {
u16_offset += 1;
u8_offset += 1;
}
}
u8_offset
}
pub fn spans_entire_line(&self, span: &Span) -> bool {
self.line_starts.iter().any(|&line_start| {
line_start == span.start && self.line_starts.contains(&(span.end() + 1))
})
}
pub fn span_of_range(&self, range: &Range, source_id: u16) -> Span {
let start = self.byte_index_of_line_col(range.start);
let end = self.byte_index_of_line_col(range.end);
let len = end.saturating_sub(start) as u16;
Span {
start,
len,
source_id,
}
}
pub fn range_of_span(&self, span: Span) -> Range {
let start = self.line_and_column_number(span.start);
let end = self.line_and_column_number(span.end());
Range { start, end }
}
}