Skip to content

Commit

Permalink
Merge pull request #347 from kivikakk/use-css-if-you-like
Browse files Browse the repository at this point in the history
Allow for Syntect to simply generate CSS classes
  • Loading branch information
kivikakk authored Nov 29, 2023
2 parents 0679810 + 4b9d838 commit 769b4e5
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 40 deletions.
9 changes: 7 additions & 2 deletions examples/syntect.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
//! This example shows how to use the bundled syntect plugin.

use comrak::plugins::syntect::SyntectAdapter;
use comrak::plugins::syntect::SyntectAdapterBuilder;
use comrak::{markdown_to_html_with_plugins, Options, Plugins};

fn main() {
let adapter = SyntectAdapter::new("base16-ocean.dark");
run_with(SyntectAdapterBuilder::new().theme("base16-ocean.dark"));
run_with(SyntectAdapterBuilder::new().css());
}

fn run_with(builder: SyntectAdapterBuilder) {
let adapter = builder.build();
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
113 changes: 79 additions & 34 deletions src/plugins/syntect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,65 @@ 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 {
/// If None is specified, apply CSS classes instead.
pub fn new(theme: Option<&str>) -> Self {
SyntectAdapter {
theme: theme.into(),
theme: theme.map(String::from),
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);

let mut output = String::new();
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,
)?;
}
Ok(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)?;
}
Ok(html_generator.finalize())
}
}
Ok(output)
}
}

Expand Down Expand Up @@ -82,16 +102,25 @@ 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
);

let mut pre_attributes = SyntectPreAttributes::new(attributes, &style);
html::write_opening_tag(output, "pre", pre_attributes.iter_mut())
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<&str, &str> = HashMap::new();
attributes.insert("class", "syntax-highlighting");
html::write_opening_tag(output, "pre", attributes)
}
}
}

fn write_code_tag(
Expand Down Expand Up @@ -151,7 +180,7 @@ impl<'a> Iterator for SyntectPreAttributesIter<'a> {
}
}

#[derive(Debug, Default)]
#[derive(Debug)]
/// A builder for [`SyntectAdapter`].
///
/// Allows customization of `Theme`, [`ThemeSet`], and [`SyntaxSet`].
Expand All @@ -161,25 +190,41 @@ pub struct SyntectAdapterBuilder {
theme_set: Option<ThemeSet>,
}

impl Default for SyntectAdapterBuilder {
fn default() -> Self {
SyntectAdapterBuilder {
theme: Some("InspiredGitHub".into()),
syntax_set: None,
theme_set: None,
}
}
}

impl SyntectAdapterBuilder {
/// Creates a new empty [`SyntectAdapterBuilder`]
/// Create a new empty [`SyntectAdapterBuilder`].
pub fn new() -> Self {
Default::default()
}

/// Sets the theme
/// Set the theme.
pub fn theme(mut self, s: &str) -> Self {
self.theme.replace(s.into());
self
}

/// Sets the syntax set
/// Uses CSS classes instead of a Syntect theme.
pub fn css(mut self) -> Self {
self.theme = None;
self
}

/// Set the syntax set.
pub fn syntax_set(mut self, s: SyntaxSet) -> Self {
self.syntax_set.replace(s);
self
}

/// Sets the theme set
/// Set the theme set.
pub fn theme_set(mut self, s: ThemeSet) -> Self {
self.theme_set.replace(s);
self
Expand All @@ -191,7 +236,7 @@ impl SyntectAdapterBuilder {
/// - `theme_set`: [`ThemeSet::load_defaults()`]
pub fn build(self) -> SyntectAdapter {
SyntectAdapter {
theme: self.theme.unwrap_or_else(|| "InspiredGitHub".into()),
theme: self.theme,
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);
}
1 change: 0 additions & 1 deletion src/tests/regressions.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use super::*;
use ntest::timeout;

#[test]
fn pointy_brace() {
Expand Down

0 comments on commit 769b4e5

Please sign in to comment.