Skip to content

Commit

Permalink
Switch to use Comrak for syntax highlighting (#438)
Browse files Browse the repository at this point in the history
lambda-fairy authored Aug 22, 2024
1 parent f0d11ce commit a3c8ea3
Showing 6 changed files with 127 additions and 91 deletions.
27 changes: 27 additions & 0 deletions docs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ description = "Documentation for Maud."
edition = "2021"

[dependencies]
comrak = { version = "*", default-features = false }
comrak = { version = "*", default-features = false, features = ["syntect"] }
maud = { path = "../maud" }
serde_json = "*"
syntect = "*"
54 changes: 4 additions & 50 deletions docs/src/bin/build_page.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use comrak::{
nodes::{AstNode, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeLink, NodeValue},
nodes::{AstNode, NodeCodeBlock, NodeHeading, NodeLink, NodeValue},
Arena,
};
use docs::{
@@ -14,11 +14,6 @@ use std::{
path::Path,
str,
};
use syntect::{
highlighting::{Color, ThemeSet},
html::highlighted_html_for_string,
parsing::SyntaxSet,
};

fn main() -> Result<(), Box<dyn Error>> {
let args = env::args().collect::<Vec<_>>();
@@ -55,7 +50,7 @@ fn build_page(
.collect::<Vec<_>>();

let page = Page::load(&arena, input_path)?;
postprocess(page.content)?;
postprocess(page.content);

let markup = views::main(slug, page, &nav, version, hash);

@@ -65,12 +60,10 @@ fn build_page(
Ok(())
}

fn postprocess<'a>(content: &'a AstNode<'a>) -> Result<(), Box<dyn Error>> {
fn postprocess<'a>(content: &'a AstNode<'a>) {
lower_headings(content);
rewrite_md_links(content);
strip_hidden_code(content);
highlight_code(content)?;
Ok(())
}

fn lower_headings<'a>(root: &'a AstNode<'a>) {
@@ -98,8 +91,7 @@ fn strip_hidden_code<'a>(root: &'a AstNode<'a>) {
for node in root.descendants() {
let mut data = node.data.borrow_mut();
if let NodeValue::CodeBlock(NodeCodeBlock { info, literal, .. }) = &mut data.value {
let info = parse_code_block_info(info);
if !info.contains(&"rust") {
if info.split(',').map(str::trim).all(|lang| lang != "rust") {
continue;
}
*literal = strip_hidden_code_inner(literal);
@@ -117,41 +109,3 @@ fn strip_hidden_code_inner(literal: &str) -> String {
.collect::<Vec<_>>();
lines.join("\n")
}

fn highlight_code<'a>(root: &'a AstNode<'a>) -> Result<(), Box<dyn Error>> {
let ss = SyntaxSet::load_defaults_newlines();
let ts = ThemeSet::load_defaults();
let mut theme = ts.themes["InspiredGitHub"].clone();
theme.settings.background = Some(Color {
r: 0xff,
g: 0xee,
b: 0xff,
a: 0xff,
});
for node in root.descendants() {
let mut data = node.data.borrow_mut();
if let NodeValue::CodeBlock(NodeCodeBlock { info, literal, .. }) = &mut data.value {
let info = parse_code_block_info(info);
let syntax = info
.into_iter()
.filter_map(|token| ss.find_syntax_by_token(token))
.next()
.unwrap_or_else(|| ss.find_syntax_plain_text());
let mut literal = std::mem::take(literal);
if !literal.ends_with('\n') {
// Syntect expects a trailing newline
literal.push('\n');
}
let html = highlighted_html_for_string(&literal, &ss, syntax, &theme)?;
data.value = NodeValue::HtmlBlock(NodeHtmlBlock {
literal: html,
..Default::default()
});
}
}
Ok(())
}

fn parse_code_block_info(info: &str) -> Vec<&str> {
info.split(',').map(str::trim).collect()
}
44 changes: 44 additions & 0 deletions docs/src/highlight.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use comrak::{
plugins::syntect::{SyntectAdapter, SyntectAdapterBuilder},
Plugins,
};
use std::rc::Rc;
use syntect::highlighting::{Color, ThemeSet};

pub struct Highlighter {
adapter: Rc<SyntectAdapter>,
}

impl Highlighter {
pub fn get() -> Self {
Self {
adapter: SYNTECT_ADAPTER.with(Rc::clone),
}
}

pub fn as_plugins(&self) -> Plugins<'_> {
let mut plugins = Plugins::default();
plugins.render.codefence_syntax_highlighter = Some(&*self.adapter);
plugins
}
}

thread_local! {
static SYNTECT_ADAPTER: Rc<SyntectAdapter> = Rc::new({
SyntectAdapterBuilder::new()
.theme_set({
let mut ts = ThemeSet::load_defaults();
let mut theme = ts.themes["InspiredGitHub"].clone();
theme.settings.background = Some(Color {
r: 0xff,
g: 0xee,
b: 0xff,
a: 0xff,
});
ts.themes.insert("InspiredGitHub2".to_string(), theme);
ts
})
.theme("InspiredGitHub2")
.build()
});
}
1 change: 1 addition & 0 deletions docs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod highlight;
pub mod page;
pub mod string_writer;
pub mod views;
90 changes: 50 additions & 40 deletions docs/src/views.rs
Original file line number Diff line number Diff line change
@@ -3,50 +3,11 @@ use maud::{html, Markup, PreEscaped, Render, DOCTYPE};
use std::str;

use crate::{
highlight::Highlighter,
page::{default_comrak_options, Page},
string_writer::StringWriter,
};

struct Comrak<'a>(&'a AstNode<'a>);

impl<'a> Render for Comrak<'a> {
fn render_to(&self, buffer: &mut String) {
comrak::format_html(self.0, &default_comrak_options(), &mut StringWriter(buffer)).unwrap();
}
}

/// Hack! The page title is wrapped in a `Paragraph` node, which introduces an
/// extra `<p>` tag that we don't want most of the time.
struct ComrakRemovePTags<'a>(&'a AstNode<'a>);

impl<'a> Render for ComrakRemovePTags<'a> {
fn render(&self) -> Markup {
let mut buffer = String::new();
comrak::format_html(
self.0,
&default_comrak_options(),
&mut StringWriter(&mut buffer),
)
.unwrap();
assert!(buffer.starts_with("<p>") && buffer.ends_with("</p>\n"));
PreEscaped(
buffer
.trim_start_matches("<p>")
.trim_end_matches("</p>\n")
.to_string(),
)
}
}

struct ComrakText<'a>(&'a AstNode<'a>);

impl<'a> Render for ComrakText<'a> {
fn render_to(&self, buffer: &mut String) {
comrak::format_commonmark(self.0, &default_comrak_options(), &mut StringWriter(buffer))
.unwrap();
}
}

pub fn main<'a>(
slug: &str,
page: Page<'a>,
@@ -124,3 +85,52 @@ pub fn main<'a>(
}
}
}

struct Comrak<'a>(&'a AstNode<'a>);

impl<'a> Render for Comrak<'a> {
fn render_to(&self, buffer: &mut String) {
let highlighter = Highlighter::get();
comrak::format_html_with_plugins(
self.0,
&default_comrak_options(),
&mut StringWriter(buffer),
&highlighter.as_plugins(),
)
.unwrap();
}
}

/// Hack! The page title is wrapped in a `Paragraph` node, which introduces an
/// extra `<p>` tag that we don't want most of the time.
struct ComrakRemovePTags<'a>(&'a AstNode<'a>);

impl<'a> Render for ComrakRemovePTags<'a> {
fn render(&self) -> Markup {
let mut buffer = String::new();
let highlighter = Highlighter::get();
comrak::format_html_with_plugins(
self.0,
&default_comrak_options(),
&mut StringWriter(&mut buffer),
&highlighter.as_plugins(),
)
.unwrap();
assert!(buffer.starts_with("<p>") && buffer.ends_with("</p>\n"));
PreEscaped(
buffer
.trim_start_matches("<p>")
.trim_end_matches("</p>\n")
.to_string(),
)
}
}

struct ComrakText<'a>(&'a AstNode<'a>);

impl<'a> Render for ComrakText<'a> {
fn render_to(&self, buffer: &mut String) {
comrak::format_commonmark(self.0, &default_comrak_options(), &mut StringWriter(buffer))
.unwrap();
}
}

0 comments on commit a3c8ea3

Please sign in to comment.