use crate::{SourceID, SourcePath};
use std::{
borrow::Cow,
fmt::{Debug, Display, Formatter},
ops::Range,
path::Path,
};
use url::Url;
mod display;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct SourceText {
path: SourcePath,
raw: String,
lines: Vec<SourceLine>,
length: u32,
dirty: bool,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct SourceLine {
pub offset: u32,
pub length: u32,
pub text: String,
}
#[derive(Copy, Clone, Default, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SourceSpan {
pub start: u32,
pub end: u32,
pub file: SourceID,
}
impl SourceText {
pub fn snippet<S, N>(text: S, name: N) -> Self
where
S: Into<String>,
N: Into<Cow<'static, str>>,
{
let mut src = Self::from(text);
src.path = SourcePath::Snippet(name.into());
src
}
pub fn source_id(&self) -> SourceID {
self.path.source_id()
}
pub fn get_length(&self) -> usize {
self.length as usize
}
pub fn get_line(&self, idx: usize) -> Option<&SourceLine> {
self.lines.get(idx)
}
pub fn get_source(&self) -> &SourcePath {
&self.path
}
pub fn set_source(&mut self, path: SourcePath) {
self.path = path;
}
pub fn set_path(&mut self, path: &Path) {
self.path = SourcePath::Local(path.to_path_buf());
}
pub fn with_path(self, path: &Path) -> Self {
Self { path: SourcePath::Local(path.to_path_buf()), ..self }
}
pub fn set_remote(&mut self, url: Url) -> bool {
self.path = SourcePath::Remote(url);
true
}
pub fn with_remote(self, url: Url) -> Self {
Self { path: SourcePath::Remote(url), ..self }
}
pub fn text(&self) -> &str {
self.raw.as_str()
}
pub fn lines(&self) -> &[SourceLine] {
self.lines.as_slice()
}
pub fn clear(&mut self) {
self.raw.clear();
self.lines.clear();
self.dirty = true;
}
}
impl SourceText {
pub fn get_offset_line(&self, offset: u32) -> Option<(&SourceLine, usize, u32)> {
if offset <= self.length {
let idx = self.lines.binary_search_by_key(&offset, |line| line.offset).unwrap_or_else(|idx| idx.saturating_sub(1));
let line = &self.lines[idx];
assert!(offset >= line.offset, "offset = {}, line.offset = {}", offset, line.offset);
Some((line, idx, offset - line.offset))
}
else {
None
}
}
pub fn get_line_range(&self, span: &Range<u32>) -> Range<usize> {
let start = self.get_offset_line(span.start).map_or(0, |(_, l, _)| l);
let end = self.get_offset_line(span.end.saturating_sub(1).max(span.start)).map_or(self.lines.len(), |(_, l, _)| l + 1);
start..end
}
}
impl SourceSpan {
pub fn new(file: SourceID, start: u32, end: u32) -> Self {
Self { start, end, file }
}
pub fn get_range(&self) -> Range<u32> {
self.start..self.end
}
pub fn get_start(&self) -> u32 {
self.start
}
pub fn get_end(&self) -> u32 {
self.end
}
pub fn set_range(&mut self, range: Range<u32>) {
self.start = range.start;
self.end = range.end;
}
pub fn with_range(self, range: Range<u32>) -> Self {
Self { start: range.start, end: range.end, ..self }
}
pub fn get_file(&self) -> SourceID {
self.file
}
pub fn set_file(&mut self, file: SourceID) {
self.file = file;
}
pub fn with_file(self, file: SourceID) -> Self {
Self { file, ..self }
}
pub fn length(&self) -> u32 {
self.end.saturating_sub(self.start)
}
pub fn contains(&self, offset: u32) -> bool {
self.get_range().contains(&offset)
}
}
impl SourceLine {
pub fn range(&self) -> Range<u32> {
self.offset..self.offset + self.length
}
pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
self.text.chars()
}
}