use camino::Utf8Path;
use crate::error::LineColumn;
use crate::primitives::{SourceId, Span};
#[derive(Debug, Clone, Copy)]
pub struct SourceText<'src> {
pub id: SourceId,
pub text: &'src str,
pub filename: Option<&'src Utf8Path>,
}
impl<'src> SourceText<'src> {
pub fn new(id: SourceId, text: &'src str, filename: Option<&'src Utf8Path>) -> Self {
Self { id, text, filename }
}
pub fn len(self) -> usize {
self.text.len()
}
pub fn is_empty(self) -> bool {
self.text.is_empty()
}
pub fn span_all(self) -> Span {
Span::from_offsets(0, self.text.len()).unwrap_or(Span::EMPTY)
}
pub fn slice(self, span: Span) -> Option<&'src str> {
self.text.get(span.start.as_usize()..span.end.as_usize())
}
pub fn utf16_offset(self, offset: usize) -> usize {
let bounded = offset.min(self.text.len());
self.text[..bounded]
.chars()
.filter(|&ch| ch != '\r')
.map(char::len_utf16)
.sum()
}
pub fn line_column_at_offset(self, offset: usize) -> (usize, usize) {
let mut line = 1usize;
let mut column = 0usize;
let limit = offset.min(self.text.len());
for ch in self.text[..limit].chars() {
match ch {
'\n' => {
line += 1;
column = 0;
}
'\r' => {}
_ => {
column += ch.len_utf16();
}
}
}
(line, column)
}
pub fn location_at_offset(self, offset: usize) -> LineColumn {
let (line, column) = self.line_column_at_offset(offset);
LineColumn {
line,
column,
character: self.utf16_offset(offset),
}
}
}
#[cfg(test)]
mod tests {
use camino::Utf8Path;
use super::{SourceId, SourceText};
#[test]
fn source_text_reports_utf16_locations() {
let source = SourceText::new(
SourceId::new(1),
"a\n😀b",
Some(Utf8Path::new("input.svelte")),
);
assert_eq!(source.utf16_offset(0), 0);
assert_eq!(source.utf16_offset(2), 2);
assert_eq!(source.utf16_offset("a\n😀".len()), 4);
let location = source.location_at_offset("a\n😀".len());
assert_eq!(location.line, 2);
assert_eq!(location.column, 2);
assert_eq!(location.character, 4);
}
#[test]
fn source_text_normalizes_crlf_offsets() {
let source = SourceText::new(
SourceId::new(2),
"a\r\nb\r\n😀c",
Some(Utf8Path::new("input.svelte")),
);
let offset = "a\r\nb\r\n😀".len();
assert_eq!(source.utf16_offset(offset), 6);
let location = source.location_at_offset(offset);
assert_eq!(location.line, 3);
assert_eq!(location.column, 2);
assert_eq!(location.character, 6);
}
}