Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to use Comrak for syntax highlighting #438

Merged
merged 2 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Expand Up @@ -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::{
Expand All @@ -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<_>>();
Expand Down Expand Up @@ -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);

Expand All @@ -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>) {
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Up @@ -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>,
Expand Down Expand Up @@ -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();
}
}
Loading