Syntax highlighting (#691)

* Syntax highlighting mostly... Exists.

* Add dependency to dockerfile

* Handle non-existent languages better

* Make the default a bit nicer

* Improve highlighting. Clean up function

* Add dark theme, add the comment scope to the allowed classes

* update build env

* Address review comments

* Use find_syntax_by_token which produces the desired behavior

* Change flat_map into flatten
(commit cargo.lock)
This commit is contained in:
Violet White
2019-12-30 08:35:27 -05:00
committed by Ana Gelez
parent 597778fd2f
commit 458baf5f78
11 changed files with 392 additions and 21 deletions
+1
View File
@@ -20,6 +20,7 @@ extern crate shrinkwraprs;
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
extern crate syntect;
extern crate tokio;
pub mod activity_pub;
+53 -2
View File
@@ -7,6 +7,8 @@ use rocket::{
};
use std::borrow::Cow;
use std::collections::HashSet;
use syntect::html::ClassedHTMLGenerator;
use syntect::parsing::SyntaxSet;
/// Generates an hexadecimal representation of 32 bytes of random data
pub fn random_hex() -> String {
@@ -55,7 +57,54 @@ fn to_inline(tag: Tag) -> Tag {
t => t,
}
}
struct HighlighterContext {
content: Vec<String>,
}
fn highlight_code<'a>(
context: &mut Option<HighlighterContext>,
evt: Event<'a>,
) -> Option<Vec<Event<'a>>> {
match evt {
Event::Start(Tag::CodeBlock(lang)) => {
if lang.is_empty() {
Some(vec![Event::Start(Tag::CodeBlock(lang))])
} else {
*context = Some(HighlighterContext { content: vec![] });
Some(vec![Event::Start(Tag::CodeBlock(lang))])
}
}
Event::End(Tag::CodeBlock(x)) => {
let mut result = vec![];
if let Some(ctx) = context.take() {
let syntax_set = SyntaxSet::load_defaults_newlines();
let syntax = syntax_set.find_syntax_by_token(&x).unwrap_or_else(|| {
syntax_set
.find_syntax_by_name(&x)
.unwrap_or_else(|| syntax_set.find_syntax_plain_text())
});
let mut html = ClassedHTMLGenerator::new(&syntax, &syntax_set);
for line in ctx.content {
html.parse_html_for_line(&line);
}
let q = html.finalize();
result.push(Event::Html(q.into()));
}
result.push(Event::End(Tag::CodeBlock(x)));
*context = None;
Some(result)
}
Event::Text(t) => {
if let Some(mut c) = context.take() {
c.content.push(t.to_string());
*context = Some(c);
Some(vec![])
} else {
Some(vec![Event::Text(t)])
}
}
_ => Some(vec![evt]),
}
}
fn flatten_text<'a>(state: &mut Option<String>, evt: Event<'a>) -> Option<Vec<Event<'a>>> {
let (s, res) = match evt {
Event::Text(txt) => match state.take() {
@@ -168,7 +217,9 @@ pub fn md_to_html<'a>(
let (parser, mentions, hashtags): (Vec<Event>, Vec<String>, Vec<String>) = parser
// Flatten text because pulldown_cmark break #hashtag in two individual text elements
.scan(None, flatten_text)
.flat_map(IntoIterator::into_iter)
.flatten()
.scan(None, highlight_code)
.flatten()
.map(|evt| process_image(evt, inline, &media_processor))
// Ignore headings, images, and tables if inline = true
.scan((vec![], inline), inline_tags)