use std::io::Write;
use log::debug;
use pulldown_cmark::{CodeBlockKind, Event, Tag};
pub(crate) trait Highlighter {
fn hl_codeblock<'a>(&self, name: Option<&str>, block: &str) -> Vec<Event>;
fn write_header(&self, out: &mut dyn Write) -> std::io::Result<()>;
}
pub use js_hl::HighlightJsHighlighter;
#[cfg(feature = "syntect-hl")]
pub use syntect_hl::SyntectHighlighter;
#[cfg(feature = "syntect-hl")]
mod syntect_hl {
use std::io::Write;
use pulldown_cmark::Event;
use syntect::{
self, highlighting::ThemeSet, html::highlighted_html_for_string, parsing::SyntaxSet,
};
use super::{to_default_events, Highlighter};
pub struct SyntectHighlighter {
ss: SyntaxSet,
ts: ThemeSet,
}
impl SyntectHighlighter {
pub fn new() -> Self {
let ss = SyntaxSet::load_defaults_newlines();
let ts = ThemeSet::load_defaults();
SyntectHighlighter { ss, ts }
}
}
impl Highlighter for SyntectHighlighter {
fn hl_codeblock(&self, name: Option<&str>, block: &str) -> Vec<Event> {
let syntax = name
.and_then(|name| self.ss.find_syntax_by_token(&name))
.unwrap_or_else(|| self.ss.find_syntax_plain_text());
let theme = &self.ts.themes["InspiredGitHub"];
let highlighted = highlighted_html_for_string(&block, &self.ss, &syntax, theme);
match highlighted {
Ok(html) => vec![Event::Html(html.into())],
Err(_) => to_default_events(name, &block),
}
}
fn write_header(&self, _out: &mut dyn Write) -> std::io::Result<()> {
Ok(())
}
}
}
mod js_hl {
use std::io::Write;
use pulldown_cmark::Event;
use super::{to_default_events, Highlighter};
pub struct HighlightJsHighlighter;
impl Highlighter for HighlightJsHighlighter {
fn hl_codeblock(&self, name: Option<&str>, block: &str) -> Vec<Event> {
to_default_events(name, &block)
}
fn write_header(&self, out: &mut dyn Write) -> std::io::Result<()> {
const HLJS_VERSION: &str = "10.5.0";
write!(
out,
r#"
<link rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/{0}/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/{0}/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>"#,
HLJS_VERSION
)
}
}
}
fn to_default_events<'a, 'b>(name: Option<&'a str>, block: &'a str) -> Vec<Event<'b>> {
let kind = match name {
Some(name) => CodeBlockKind::Fenced(name.to_owned().into()),
None => CodeBlockKind::Indented,
};
vec![
Event::Start(Tag::CodeBlock(kind.clone())),
Event::Text(block.to_owned().into()),
Event::End(Tag::CodeBlock(kind)),
]
}
pub(crate) fn get_hilighter() -> &'static dyn Highlighter {
static GLOBAL_JS_HL: HighlightJsHighlighter = HighlightJsHighlighter;
#[cfg(feature = "syntect-hl")]
if std::env::var("DOCKET_FORCE_JS_HL").is_err() {
use once_cell::sync::Lazy;
static GLOBAL_SYNTECT_HL: Lazy<SyntectHighlighter> =
Lazy::new(|| SyntectHighlighter::new());
debug!("Using syntect for highlighting.");
return &*GLOBAL_SYNTECT_HL;
}
debug!("Using Javascript highlighter.");
&GLOBAL_JS_HL
}