use crate::Span;
use std::fmt;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::slice;
#[derive(Default, Clone)]
pub struct Source {
name: String,
source: String,
path: Option<PathBuf>,
line_starts: Vec<usize>,
}
impl Source {
pub fn new<N, S>(name: N, source: S) -> Self
where
N: AsRef<str>,
S: AsRef<str>,
{
let source = source.as_ref();
let line_starts = line_starts(source).collect::<Vec<_>>();
Self {
name: name.as_ref().to_owned(),
source: source.to_owned(),
path: None,
line_starts,
}
}
pub fn line_starts(&self) -> &[usize] {
&self.line_starts
}
pub fn from_path(path: &Path) -> io::Result<Self> {
let name = path.display().to_string();
let path = &path.canonicalize()?;
let source = fs::read_to_string(path)?;
let line_starts = line_starts(&source).collect::<Vec<_>>();
Ok(Self {
name,
source,
path: Some(path.to_owned()),
line_starts,
})
}
pub fn is_empty(&self) -> bool {
self.source.is_empty()
}
pub fn len(&self) -> usize {
self.source.len()
}
pub fn name(&self) -> &str {
&self.name
}
pub fn source(&self, span: Span) -> Option<&'_ str> {
self.get(span.range())
}
pub fn get<I>(&self, i: I) -> Option<&I::Output>
where
I: slice::SliceIndex<str>,
{
self.source.get(i)
}
pub fn end(&self) -> usize {
self.source.len()
}
pub fn as_str(&self) -> &str {
&self.source
}
pub fn path(&self) -> Option<&Path> {
self.path.as_deref()
}
pub fn path_mut(&mut self) -> &mut Option<PathBuf> {
&mut self.path
}
pub fn position_to_utf16cu_line_char(&self, offset: usize) -> Option<(usize, usize)> {
if offset == 0 {
return Some((0, 0));
}
let line = match self.line_starts.binary_search(&offset) {
Ok(exact) => exact,
Err(0) => return None,
Err(n) => n - 1,
};
let line_start = self.line_starts[line];
let rest = &self.source[line_start..];
let offset = offset - line_start;
let mut line_count = 0;
for (n, c) in rest.char_indices() {
if n == offset {
return Some((line, line_count));
}
if n > offset {
break;
}
line_count += c.encode_utf16(&mut [0u16; 2]).len();
}
Some((line, line_count))
}
pub fn position_to_unicode_line_char(&self, offset: usize) -> (usize, usize) {
if offset == 0 {
return (0, 0);
}
let line = match self.line_starts.binary_search(&offset) {
Ok(exact) => exact,
Err(0) => return (0, 0),
Err(n) => n - 1,
};
let line_start = self.line_starts[line];
let rest = &self.source[line_start..];
let offset = offset - line_start;
let mut line_count = 0;
for (n, _) in rest.char_indices() {
if n == offset {
return (line, line_count);
}
if n > offset {
break;
}
line_count += 1;
}
(line, line_count)
}
}
impl fmt::Debug for Source {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Source")
.field("name", &self.name)
.field("path", &self.path)
.finish()
}
}
fn line_starts(source: &str) -> impl Iterator<Item = usize> + '_ {
std::iter::once(0).chain(source.match_indices('\n').map(|(i, _)| i + 1))
}