2017-03-07 12:34:31 +00:00
|
|
|
use std::borrow::Cow::Owned;
|
|
|
|
|
|
|
|
use pulldown_cmark as cmark;
|
|
|
|
use self::cmark::{Parser, Event, Tag};
|
|
|
|
|
2017-03-20 08:30:50 +00:00
|
|
|
use syntect::dumps::from_binary;
|
2017-03-07 12:34:31 +00:00
|
|
|
use syntect::easy::HighlightLines;
|
|
|
|
use syntect::parsing::SyntaxSet;
|
|
|
|
use syntect::highlighting::ThemeSet;
|
|
|
|
use syntect::html::{start_coloured_html_snippet, styles_to_coloured_html, IncludeBackground};
|
|
|
|
|
|
|
|
|
|
|
|
// We need to put those in a struct to impl Send and sync
|
2017-03-20 08:30:50 +00:00
|
|
|
pub struct Setup {
|
2017-03-07 12:34:31 +00:00
|
|
|
syntax_set: SyntaxSet,
|
2017-03-20 08:30:50 +00:00
|
|
|
pub theme_set: ThemeSet,
|
2017-03-07 12:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl Send for Setup {}
|
|
|
|
unsafe impl Sync for Setup {}
|
|
|
|
|
|
|
|
lazy_static!{
|
2017-03-20 08:30:50 +00:00
|
|
|
pub static ref SETUP: Setup = Setup {
|
2017-03-07 12:34:31 +00:00
|
|
|
syntax_set: SyntaxSet::load_defaults_newlines(),
|
2017-03-20 08:30:50 +00:00
|
|
|
theme_set: from_binary(include_bytes!("../sublime_themes/all.themedump"))
|
2017-03-07 12:34:31 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct CodeHighlightingParser<'a> {
|
|
|
|
// The block we're currently highlighting
|
|
|
|
highlighter: Option<HighlightLines<'a>>,
|
|
|
|
parser: Parser<'a>,
|
2017-03-20 08:30:50 +00:00
|
|
|
theme: &'a str,
|
2017-03-07 12:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> CodeHighlightingParser<'a> {
|
2017-03-20 08:30:50 +00:00
|
|
|
pub fn new(parser: Parser<'a>, theme: &'a str) -> CodeHighlightingParser<'a> {
|
2017-03-07 12:34:31 +00:00
|
|
|
CodeHighlightingParser {
|
|
|
|
highlighter: None,
|
|
|
|
parser: parser,
|
2017-03-20 08:30:50 +00:00
|
|
|
theme: theme,
|
2017-03-07 12:34:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Iterator for CodeHighlightingParser<'a> {
|
|
|
|
type Item = Event<'a>;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Event<'a>> {
|
|
|
|
// Not using pattern matching to reduce indentation levels
|
|
|
|
let next_opt = self.parser.next();
|
|
|
|
if next_opt.is_none() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
let item = next_opt.unwrap();
|
|
|
|
// Below we just look for the start of a code block and highlight everything
|
|
|
|
// until we see the end of a code block.
|
|
|
|
// Everything else happens as normal in pulldown_cmark
|
|
|
|
match item {
|
|
|
|
Event::Text(text) => {
|
|
|
|
// if we are in the middle of a code block
|
|
|
|
if let Some(ref mut highlighter) = self.highlighter {
|
|
|
|
let highlighted = &highlighter.highlight(&text);
|
|
|
|
let html = styles_to_coloured_html(highlighted, IncludeBackground::Yes);
|
|
|
|
Some(Event::Html(Owned(html)))
|
|
|
|
} else {
|
|
|
|
Some(Event::Text(text))
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Event::Start(Tag::CodeBlock(ref info)) => {
|
2017-03-20 08:30:50 +00:00
|
|
|
let theme = &SETUP.theme_set.themes[self.theme];
|
2017-03-07 12:34:31 +00:00
|
|
|
let syntax = info
|
|
|
|
.split(' ')
|
|
|
|
.next()
|
|
|
|
.and_then(|lang| SETUP.syntax_set.find_syntax_by_token(lang))
|
|
|
|
.unwrap_or_else(|| SETUP.syntax_set.find_syntax_plain_text());
|
|
|
|
self.highlighter = Some(
|
2017-03-20 08:30:50 +00:00
|
|
|
HighlightLines::new(syntax, &theme)
|
2017-03-07 12:34:31 +00:00
|
|
|
);
|
2017-03-20 08:30:50 +00:00
|
|
|
let snippet = start_coloured_html_snippet(&theme);
|
2017-03-07 12:34:31 +00:00
|
|
|
Some(Event::Html(Owned(snippet)))
|
|
|
|
},
|
|
|
|
Event::End(Tag::CodeBlock(_)) => {
|
|
|
|
// reset highlight and close the code block
|
|
|
|
self.highlighter = None;
|
|
|
|
Some(Event::Html(Owned("</pre>".to_owned())))
|
|
|
|
},
|
|
|
|
_ => Some(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-20 08:30:50 +00:00
|
|
|
pub fn markdown_to_html(content: &str, highlight_code: bool, highlight_theme: &str) -> String {
|
2017-03-07 12:34:31 +00:00
|
|
|
let mut html = String::new();
|
|
|
|
if highlight_code {
|
2017-03-20 08:30:50 +00:00
|
|
|
let parser = CodeHighlightingParser::new(Parser::new(content), highlight_theme);
|
2017-03-07 12:34:31 +00:00
|
|
|
cmark::html::push_html(&mut html, parser);
|
|
|
|
} else {
|
|
|
|
let parser = Parser::new(content);
|
|
|
|
cmark::html::push_html(&mut html, parser);
|
|
|
|
};
|
|
|
|
html
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::{markdown_to_html};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_markdown_to_html_simple() {
|
2017-03-20 08:30:50 +00:00
|
|
|
let res = markdown_to_html("# hello", true, "base16-ocean-dark");
|
2017-03-07 12:34:31 +00:00
|
|
|
assert_eq!(res, "<h1>hello</h1>\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_markdown_to_html_code_block_highlighting_off() {
|
2017-03-20 08:30:50 +00:00
|
|
|
let res = markdown_to_html("```\n$ gutenberg server\n```", false, "base16-ocean-dark");
|
2017-03-07 12:34:31 +00:00
|
|
|
assert_eq!(
|
|
|
|
res,
|
|
|
|
"<pre><code>$ gutenberg server\n</code></pre>\n"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_markdown_to_html_code_block_no_lang() {
|
2017-03-20 08:30:50 +00:00
|
|
|
let res = markdown_to_html("```\n$ gutenberg server\n$ ping\n```", true, "base16-ocean-dark");
|
2017-03-07 12:34:31 +00:00
|
|
|
assert_eq!(
|
|
|
|
res,
|
|
|
|
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">$ gutenberg server\n</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">$ ping\n</span></pre>"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_markdown_to_html_code_block_with_lang() {
|
2017-03-20 08:30:50 +00:00
|
|
|
let res = markdown_to_html("```python\nlist.append(1)\n```", true, "base16-ocean-dark");
|
2017-03-07 12:34:31 +00:00
|
|
|
assert_eq!(
|
|
|
|
res,
|
|
|
|
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">.</span><span style=\"background-color:#2b303b;color:#bf616a;\">append</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">(</span><span style=\"background-color:#2b303b;color:#d08770;\">1</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">)</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">\n</span></pre>"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_markdown_to_html_code_block_with_unknown_lang() {
|
2017-03-20 08:30:50 +00:00
|
|
|
let res = markdown_to_html("```yolo\nlist.append(1)\n```", true, "base16-ocean-dark");
|
2017-03-07 12:34:31 +00:00
|
|
|
// defaults to plain text
|
|
|
|
assert_eq!(
|
|
|
|
res,
|
|
|
|
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list.append(1)\n</span></pre>"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|