feat: perform highlighting
This commit is contained in:
parent
c1057be18e
commit
cac6d0ae31
@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user