Skip to content

Commit

Permalink
Allow for Syntect to simply generate CSS classes
Browse files Browse the repository at this point in the history
  • Loading branch information
gjtorikian authored and kivikakk committed Nov 29, 2023
1 parent a21d42c commit 985e49a
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 30 deletions.
2 changes: 1 addition & 1 deletion examples/syntect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use comrak::plugins::syntect::SyntectAdapter;
use comrak::{markdown_to_html_with_plugins, Options, Plugins};

fn main() {
let adapter = SyntectAdapter::new("base16-ocean.dark");
let adapter = SyntectAdapter::new(Some("base16-ocean.dark"));
let options = Options::default();
let mut plugins = Plugins::default();

Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ fn main() -> Result<(), Box<dyn Error>> {
if theme.is_empty() || theme == "none" {
syntax_highlighter = None;
} else {
adapter = SyntectAdapter::new(&theme);
adapter = SyntectAdapter::new(Some(&theme));
syntax_highlighter = Some(&adapter);
}

Expand Down
87 changes: 61 additions & 26 deletions src/plugins/syntect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,68 @@ use std::collections::{hash_map, HashMap};
use std::io::{self, Write};
use syntect::easy::HighlightLines;
use syntect::highlighting::{Color, ThemeSet};
use syntect::html::{append_highlighted_html_for_styled_line, IncludeBackground};
use syntect::html::{
append_highlighted_html_for_styled_line, ClassStyle, ClassedHTMLGenerator, IncludeBackground,
};
use syntect::parsing::{SyntaxReference, SyntaxSet};
use syntect::util::LinesWithEndings;
use syntect::Error;

#[derive(Debug)]
/// Syntect syntax highlighter plugin.
pub struct SyntectAdapter {
theme: String,
theme: Option<String>,
syntax_set: SyntaxSet,
theme_set: ThemeSet,
}

impl SyntectAdapter {
/// Construct a new `SyntectAdapter` object and set the syntax highlighting theme.
pub fn new(theme: &str) -> Self {
pub fn new(theme: Option<&str>) -> Self {
SyntectAdapter {
theme: theme.into(),
theme: match theme {
Some(t) => Some(t.into()),
None => None,
},
syntax_set: SyntaxSet::load_defaults_newlines(),
theme_set: ThemeSet::load_defaults(),
}
}

fn highlight_html(&self, code: &str, syntax: &SyntaxReference) -> Result<String, Error> {
// syntect::html::highlighted_html_for_string, without the opening/closing <pre>.
let theme = &self.theme_set.themes[&self.theme];
let mut highlighter = HighlightLines::new(syntax, theme);
let mut output = String::new();
let bg = theme.settings.background.unwrap_or(Color::WHITE);

for line in LinesWithEndings::from(code) {
let regions = highlighter.highlight_line(line, &self.syntax_set)?;
append_highlighted_html_for_styled_line(
&regions[..],
IncludeBackground::IfDifferent(bg),
&mut output,
)?;

match &self.theme {
Some(theme) => {
// syntect::html::highlighted_html_for_string, without the opening/closing <pre>.
let theme = &self.theme_set.themes[theme];
let mut highlighter = HighlightLines::new(syntax, theme);

let bg = theme.settings.background.unwrap_or(Color::WHITE);

for line in LinesWithEndings::from(code) {
let regions = highlighter.highlight_line(line, &self.syntax_set)?;
append_highlighted_html_for_styled_line(
&regions[..],
IncludeBackground::IfDifferent(bg),
&mut output,
)?;
}
}
None => {
// fall back to HTML classes
let mut html_generator = ClassedHTMLGenerator::new_with_class_style(
syntax,
&self.syntax_set,
ClassStyle::Spaced,
);
for line in LinesWithEndings::from(code) {
html_generator
.parse_html_for_line_which_includes_newline(line)
.unwrap();
}
output = html_generator.finalize();
}
}
Ok(output)
}
Expand Down Expand Up @@ -82,16 +107,26 @@ impl SyntaxHighlighterAdapter for SyntectAdapter {
output: &mut dyn Write,
attributes: HashMap<String, String>,
) -> io::Result<()> {
let theme = &self.theme_set.themes[&self.theme];
let colour = theme.settings.background.unwrap_or(Color::WHITE);

let style = format!(
"background-color:#{:02x}{:02x}{:02x};",
colour.r, colour.g, colour.b
);
match &self.theme {
Some(theme) => {
let theme = &self.theme_set.themes[theme];
let colour = theme.settings.background.unwrap_or(Color::WHITE);

let style = format!(
"background-color:#{:02x}{:02x}{:02x};",
colour.r, colour.g, colour.b
);

let mut pre_attributes = SyntectPreAttributes::new(attributes, &style);
html::write_opening_tag(output, "pre", pre_attributes.iter_mut())
}
None => {
let mut attributes: HashMap<String, String> = HashMap::new();
attributes.insert(String::from("class"), String::from("syntax-highlighting"));

let mut pre_attributes = SyntectPreAttributes::new(attributes, &style);
html::write_opening_tag(output, "pre", pre_attributes.iter_mut())
html::write_opening_tag(output, "pre", attributes)
}
}
}

fn write_code_tag(
Expand Down Expand Up @@ -191,7 +226,7 @@ impl SyntectAdapterBuilder {
/// - `theme_set`: [`ThemeSet::load_defaults()`]
pub fn build(self) -> SyntectAdapter {
SyntectAdapter {
theme: self.theme.unwrap_or_else(|| "InspiredGitHub".into()),
theme: Some(self.theme.unwrap_or_else(|| "InspiredGitHub".into())),
syntax_set: self
.syntax_set
.unwrap_or_else(SyntaxSet::load_defaults_newlines),
Expand Down
24 changes: 22 additions & 2 deletions src/tests/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ fn heading_adapter_plugin() {

#[test]
#[cfg(feature = "syntect")]
fn syntect_plugin() {
let adapter = crate::plugins::syntect::SyntectAdapter::new("base16-ocean.dark");
fn syntect_plugin_with_base16_ocean_dark_theme() {
let adapter = crate::plugins::syntect::SyntectAdapter::new(Some("base16-ocean.dark"));

let input = concat!("```rust\n", "fn main<'a>();\n", "```\n");
let expected = concat!(
Expand All @@ -104,3 +104,23 @@ fn syntect_plugin() {

html_plugins(input, expected, &plugins);
}

#[test]
#[cfg(feature = "syntect")]
fn syntect_plugin_with_css_classes() {
let adapter = crate::plugins::syntect::SyntectAdapter::new(None);

let input = concat!("```rust\n", "fn main<'a>();\n", "```\n");
let expected = concat!(
"<pre class=\"syntax-highlighting\"><code class=\"language-rust\">",
"<span class=\"source rust\"><span class=\"meta function rust\"><span class=\"meta function rust\"><span class=\"storage type function rust\">fn</span> </span><span class=\"entity name function rust\">main</span></span><span class=\"meta generic rust\"><span class=\"punctuation definition generic begin rust\">&lt;</span>",
"<span class=\"storage modifier lifetime rust\">&#39;a</span><span class=\"punctuation definition generic end rust\">&gt;</span></span><span class=\"meta function rust\"><span class=\"meta function parameters rust\"><span class=\"punctuation section parameters begin rust\">(</span></span><span class=\"meta function rust\">",
"<span class=\"meta function parameters rust\"><span class=\"punctuation section parameters end rust\">)</span></span></span></span><span class=\"punctuation terminator rust\">;</span>\n</span>",
"</code></pre>\n",
);

let mut plugins = Plugins::default();
plugins.render.codefence_syntax_highlighter = Some(&adapter);

html_plugins(input, expected, &plugins);
}

0 comments on commit 985e49a

Please sign in to comment.