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:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user