use std::fmt::{self, Debug, Formatter};
use std::ops::Range;
use std::sync::Arc;
use typst_utils::LazyHash;
use crate::lines::Lines;
use crate::reparser::reparse;
use crate::{
FileId, LinkedNode, RootedPath, Span, SpanNumber, SubRange, SyntaxNode, VirtualPath,
VirtualRoot, parse,
};
#[derive(Clone, Hash)]
pub struct Source(Arc<LazyHash<SourceInner>>);
#[derive(Clone, Hash)]
struct SourceInner {
id: FileId,
root: SyntaxNode,
lines: 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(LazyHash::new(SourceInner { id, lines: Lines::new(text), root })))
}
pub fn detached(text: impl Into<String>) -> Self {
Self::new(
RootedPath::new(VirtualRoot::Project, VirtualPath::new("main.typ").unwrap())
.intern(),
text.into(),
)
}
pub fn with_root(id: FileId, text: String, root: SyntaxNode) -> Self {
Self(Arc::new(LazyHash::new(SourceInner { id, lines: Lines::new(text), root })))
}
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 = &mut **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<'_>> {
if span.id() != Some(self.id()) {
return None;
}
LinkedNode::new(self.root()).find(span)
}
pub fn range(
&self,
num: SpanNumber,
sub_range: Option<SubRange>,
) -> Option<Range<usize>> {
let overall = LinkedNode::new(self.root()).find_number(num)?.range();
if let Some(sub_range) = sub_range {
let range = sub_range.to_absolute(overall.start);
assert!(range.end <= overall.end);
Some(range)
} else {
Some(overall)
}
}
}
impl Debug for Source {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Source({:?})", self.id().vpath())
}
}
impl AsRef<str> for Source {
fn as_ref(&self) -> &str {
self.text()
}
}
#[cfg(test)]
mod test {
use super::Source;
use crate::{LinkedNode, Side, Span, SubRange};
#[test]
fn test_source_sub_ranges() {
let text = "= head <label>";
let source = Source::detached(text);
let get = |span: Span, sub_range| {
let num = crate::SpanNumber(span.number());
&text[source.range(num, sub_range).unwrap()]
};
let head = LinkedNode::new(source.root()).leaf_at(2, Side::After).unwrap().span();
assert_eq!(get(head, None), "head");
assert_eq!(get(head, SubRange::new(1, 3)), "ea");
assert_eq!(get(head, SubRange::new(0, 1)), "h");
assert_eq!(get(head, SubRange::new(0, 4)), "head");
assert_eq!(get(head, SubRange::new(3, 4)), "d");
let root = source.root().span();
assert_eq!(get(root, None), text);
assert_eq!(get(root, SubRange::new(3, 10)), "ead <la");
assert_eq!(get(root, SubRange::new(0, 10)), "= head <la");
assert_eq!(get(root, SubRange::new(3, 14)), "ead <label>");
}
}