2.2 KB
raw
use std::sync::OnceLock;
use syntect::highlighting::{Theme, ThemeSet};
use syntect::html::{styled_line_to_highlighted_html, IncludeBackground};
use syntect::parsing::SyntaxSet;
use syntect::util::LinesWithEndings;
fn syntax_set() -> &'static SyntaxSet {
static CELL: OnceLock<SyntaxSet> = OnceLock::new();
CELL.get_or_init(SyntaxSet::load_defaults_newlines)
}
fn theme() -> &'static Theme {
static CELL: OnceLock<Theme> = OnceLock::new();
CELL.get_or_init(|| {
let mut ts = ThemeSet::load_defaults();
// base16-eighties.dark reads well on the heartwood palette and ships
// with syntect's default themes, so no theme files to vendor.
ts.themes
.remove("base16-eighties.dark")
.or_else(|| ts.themes.remove("Solarized (dark)"))
.unwrap_or_else(|| ts.themes.into_values().next().expect("at least one theme"))
})
}
/// `filename` is used only to pick the syntax; pass the blob basename.
pub fn highlight(source: &str, filename: &str) -> String {
let ss = syntax_set();
let theme = theme();
let syntax = ss
.find_syntax_for_file(filename)
.ok()
.flatten()
.or_else(|| {
let ext = std::path::Path::new(filename)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("");
ss.find_syntax_by_extension(ext)
})
.unwrap_or_else(|| ss.find_syntax_plain_text());
let mut highlighter = syntect::easy::HighlightLines::new(syntax, theme);
let mut out = String::from("<pre class=\"hl\"><code>");
for (i, line) in LinesWithEndings::from(source).enumerate() {
let ranges = highlighter
.highlight_line(line, ss)
.unwrap_or_else(|_| vec![(Default::default(), line)]);
out.push_str(&format!(
"<span class=\"ln\" data-n=\"{}\"></span>",
i + 1
));
out.push_str("<span class=\"l\">");
let html = styled_line_to_highlighted_html(&ranges, IncludeBackground::No)
.unwrap_or_else(|_| line.to_string());
out.push_str(&html);
out.push_str("</span>");
}
out.push_str("</code></pre>");
out
}