feat: perform highlighting

This commit is contained in:
Jan-Bulthuis 2026-07-19 15:38:52 +02:00
parent c1057be18e
commit cac6d0ae31

View File

@ -5,15 +5,22 @@ use std::{
fs, fs,
path::Path, path::Path,
sync::OnceLock, sync::OnceLock,
time::Duration,
}; };
use ropey::Rope;
use tracing::{info, warn}; use tracing::{info, warn};
use tree_house::{ use tree_house::{
InjectionLanguageMarker, Language, LanguageConfig, LanguageLoader, highlighter::Highlight, InjectionLanguageMarker, Language, LanguageConfig, LanguageLoader, Syntax,
highlighter::{Highlight, HighlightEvent, Highlighter},
read_query, read_query,
}; };
use tree_house_bindings::Grammar; use tree_house_bindings::Grammar;
use typst::foundations::{Func, NativeFunc, func}; use typst::{
foundations::{Content, Func, NativeElement, NativeFunc, func},
text::TextElem,
};
use typst_html::{HtmlAttr, HtmlElem, attr, tag};
use crate::AppConfig; use crate::AppConfig;
@ -183,7 +190,76 @@ fn highlight(
#[named] #[named]
#[default(None)] #[default(None)]
lang: Option<String>, lang: Option<String>,
) -> String { ) -> Content {
let rev = text.chars().rev(); let highlighted = highlight_code(&text, lang.as_deref()).unwrap_or(TextElem::packed(text));
rev.collect()
if block {
HtmlElem::new(tag::pre)
.with_body(Some(highlighted))
.with_attr(HtmlAttr::constant("class"), "code-block")
.pack()
} else {
HtmlElem::new(tag::code)
.with_body(Some(highlighted))
.with_attr(HtmlAttr::constant("class"), "code-inline")
.pack()
}
}
/// Highlights `source` as `lang`.
fn highlight_code(source: &str, lang: Option<&str>) -> Option<Content> {
let registry = REGISTRY.get()?;
let language = *registry.by_name.get(lang?)?;
let rope = Rope::from_str(source);
let slice = rope.slice(..);
let syntax = Syntax::new(slice, language, Duration::from_millis(500), registry).ok()?;
let mut hl = Highlighter::new(&syntax, slice, registry, 0..source.len() as u32);
let src = source.as_bytes();
let src_len = source.len() as u32;
let mut pos: u32 = 0;
let mut classes = String::new();
let mut nodes = Vec::<Content>::new();
loop {
let next = hl.next_event_offset().min(src_len);
if next > pos {
let text = std::str::from_utf8(&src[pos as usize..next as usize]).ok()?;
let node = TextElem::packed(text);
nodes.push(if classes.is_empty() {
node
} else {
HtmlElem::new(tag::span)
.with_attr(attr::class, classes.clone())
.with_body(Some(node))
.pack()
});
pos = next;
}
if pos >= src_len {
break;
}
let (event, highlights) = hl.advance();
let base = match event {
// Replace the active highlights
HighlightEvent::Refresh => String::new(),
// Extend the active highlights
HighlightEvent::Push => classes.clone(),
};
classes = highlights.fold(base, |mut acc, h| {
let name = HIGHLIGHT_NAMES[h.idx()].replace('.', "-");
if !acc.is_empty() {
acc.push(' ');
}
acc.push_str("hl-");
acc.push_str(&name);
acc
});
}
Some(Content::sequence(nodes))
} }