use crate::{
Error, HighlightConfig, HighlightResult, backend::Backend, highlight, language::Registry,
syntax::Syntax,
};
pub use crate::syntax::Edit;
pub struct Document<B: Backend> {
backend: B,
registry: Registry<B>,
language: String,
source: String,
syntax: Option<Syntax<B>>,
cached: Option<(bool, HighlightResult)>,
}
impl<B: Backend> Document<B> {
pub(crate) fn new(
registry: Registry<B>,
mut backend: B,
name: &str,
source: &str,
) -> Result<Self, Error> {
let language = registry.resolve(name)?.to_owned();
let syntax = Syntax::parse(&mut backend, ®istry, &language, source, None, None)?;
Ok(Self {
backend,
registry,
language,
source: source.to_owned(),
syntax: Some(syntax),
cached: None,
})
}
pub fn source(&self) -> &str {
&self.source
}
pub fn update(&mut self, source: &str) -> Result<(), Error> {
if self.source == source {
return Ok(());
}
let edit = Edit::between(&self.source, source);
let syntax = Syntax::parse(
&mut self.backend,
&self.registry,
&self.language,
source,
self.syntax.take(),
Some(&edit),
)?;
self.source.clear();
self.source.push_str(source);
self.syntax = Some(syntax);
self.cached = None;
Ok(())
}
pub fn highlight(&mut self, config: &HighlightConfig) -> Result<HighlightResult, Error> {
Ok(self.highlight_ref(config)?.clone())
}
pub fn highlight_ref(&mut self, config: &HighlightConfig) -> Result<&HighlightResult, Error> {
if self
.cached
.as_ref()
.is_none_or(|(rainbow, _)| *rainbow != config.rainbow)
{
if self.syntax.is_none() {
self.syntax = Some(Syntax::parse(
&mut self.backend,
&self.registry,
&self.language,
&self.source,
None,
None,
)?);
}
let highlighted = highlight::run(
&mut self.backend,
&self.registry,
&self.source,
self.syntax.as_ref().expect("parsed above"),
config,
)?;
self.cached = Some((config.rainbow, highlighted.to_owned()));
}
let result = &mut self.cached.as_mut().expect("highlighted above").1;
let mut selected = config.highlighted_lines.clone();
selected.sort_unstable();
let mut selected = selected.into_iter().peekable();
for line in &mut result.lines {
while selected.peek().is_some_and(|number| *number < line.number) {
selected.next();
}
line.highlighted = selected.peek() == Some(&line.number);
}
Ok(result)
}
}