use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::ops::Range;
use std::sync::Arc;
use typst_utils::LazyHash;
use crate::lines::Lines;
use crate::reparser::reparse;
use crate::{FileId, LinkedNode, Span, SyntaxNode, VirtualPath, parse};
#[derive(Clone)]
pub struct Source(Arc<Repr>);
#[derive(Clone)]
struct Repr {
id: FileId,
root: LazyHash<SyntaxNode>,
lines: LazyHash<Lines<String>>,
}
impl Source {
pub fn new(id: FileId, text: String) -> Self {
let _scope = typst_timing::TimingScope::new("create source");
let mut root = parse(&text);
root.numberize(id, Span::FULL).unwrap();
Self(Arc::new(Repr {
id,
lines: LazyHash::new(Lines::new(text)),
root: LazyHash::new(root),
}))
}
pub fn detached(text: impl Into<String>) -> Self {
Self::new(FileId::new(None, VirtualPath::new("main.typ")), text.into())
}
pub fn root(&self) -> &SyntaxNode {
&self.0.root
}
pub fn id(&self) -> FileId {
self.0.id
}
pub fn text(&self) -> &str {
self.0.lines.text()
}
pub fn lines(&self) -> &Lines<String> {
&self.0.lines
}
pub fn replace(&mut self, new: &str) -> Range<usize> {
let _scope = typst_timing::TimingScope::new("replace source");
let Some((prefix, suffix)) = self.0.lines.replacement_range(new) else {
return 0..0;
};
let old = self.text();
let replace = prefix..old.len() - suffix;
let with = &new[prefix..new.len() - suffix];
self.edit(replace, with)
}
#[track_caller]
pub fn edit(&mut self, replace: Range<usize>, with: &str) -> Range<usize> {
let inner = Arc::make_mut(&mut self.0);
inner.lines.edit(replace.clone(), with);
reparse(&mut inner.root, inner.lines.text(), replace, with.len())
}
pub fn find(&self, span: Span) -> Option<LinkedNode<'_>> {
LinkedNode::new(self.root()).find(span)
}
pub fn range(&self, span: Span) -> Option<Range<usize>> {
Some(self.find(span)?.range())
}
}
impl Debug for Source {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Source({:?})", self.id().vpath())
}
}
impl Hash for Source {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.id.hash(state);
self.0.lines.hash(state);
self.0.root.hash(state);
}
}
impl AsRef<str> for Source {
fn as_ref(&self) -> &str {
self.text()
}
}