v6/src/generator/highlight/tree_sitter.rs

79 lines
2.5 KiB
Rust

use super::Highlighter;
use pulldown_cmark::escape::escape_html;
use std::fmt::Write;
use tree_sitter_highlight::{Highlight as TSHighlight, HighlightConfiguration, HighlightEvent};
pub struct TreeSitterHighlighter {
config: &'static HighlightConfiguration,
}
// SAFETY: the only thing we store is a HighlightConfiguration, which is thread safe
// unsafe impl Sync for TreeSitterHighlighter {}
impl TreeSitterHighlighter {
pub fn new(config: &'static HighlightConfiguration) -> Self {
Self { config }
}
}
impl Highlighter for TreeSitterHighlighter {
fn highlight(&self, code: &str) -> String {
let mut highlighter = tree_sitter_highlight::Highlighter::new();
let highlights = highlighter
.highlight(self.config, code.as_bytes(), None, |_| None)
.unwrap();
let mut buf = String::new();
for highlight in highlights {
let event = highlight.unwrap();
match event {
HighlightEvent::Source { start, end } => {
escape_html(
&mut buf,
std::str::from_utf8(&code.as_bytes()[start..end]).unwrap(),
)
.unwrap();
}
HighlightEvent::HighlightStart(TSHighlight(idx)) => {
write!(
&mut buf,
"<span class=\"hl-{}\">",
HIGHLIGHT_CLASS_NAMES[idx]
)
.unwrap();
}
HighlightEvent::HighlightEnd => {
buf.push_str("</span>");
}
}
}
buf
}
}
pub const HIGHLIGHT_NAMES: &[&str] = &[
"attribute",
"comment",
"constant",
"embedded", // something inside a string, e.g., the foo in `${foo}` in js
"function",
"function.builtin",
"keyword",
"module", // e.g., atoms in elixir
"number",
"operator",
"property",
"punctuation.special", // e.g., in js the dollar sign and curly braces in `${foo}`
"string",
"string.special.key", // e.g., json keys
"tag", // e.g., html tags, css class/id selectors
"type",
"type.builtin",
"variable",
"variable.builtin",
];
const HIGHLIGHT_CLASS_NAMES: &[&str] = &[
"attr", "cmt", "const", "emb", "fn", "builtin", "kw", "mod", "num", "op", "prop", "punct-sp",
"str", "key", "tag", "type", "builtin", "var", "builtin",
];