use std::fmt::Write;
use parsing::{ScopeStackOp, BasicScopeStackOp, Scope, ScopeStack, SyntaxDefinition, SyntaxSet, SCOPE_REPO};
use easy::{HighlightLines, HighlightFile};
use highlighting::{self, Style, Theme, Color};
use escape::Escape;
use std::io::{self, BufRead};
use std::path::Path;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ClassStyle {
Spaced,
}
fn scope_to_classes(s: &mut String, scope: Scope, style: ClassStyle) {
assert!(style == ClassStyle::Spaced); let repo = SCOPE_REPO.lock().unwrap();
for i in 0..(scope.len()) {
let atom = scope.atom_at(i as usize);
let atom_s = repo.atom_str(atom);
if i != 0 {
s.push_str(" ")
}
s.push_str(atom_s);
}
}
pub fn highlighted_snippet_for_string(s: &str, syntax: &SyntaxDefinition, theme: &Theme) -> String {
let mut output = String::new();
let mut highlighter = HighlightLines::new(syntax, theme);
let c = theme.settings.background.unwrap_or(highlighting::WHITE);
write!(output,
"<pre style=\"background-color:#{:02x}{:02x}{:02x};\">\n",
c.r,
c.g,
c.b)
.unwrap();
for line in s.lines() {
let regions = highlighter.highlight(line);
let html = styles_to_coloured_html(®ions[..], IncludeBackground::IfDifferent(c));
output.push_str(&html);
output.push('\n');
}
output.push_str("</pre>\n");
output
}
pub fn highlighted_snippet_for_file<P: AsRef<Path>>(path: P,
ss: &SyntaxSet,
theme: &Theme)
-> io::Result<String> {
let mut output = String::new();
let mut highlighter = try!(HighlightFile::new(path, ss, theme));
let c = theme.settings.background.unwrap_or(highlighting::WHITE);
write!(output,
"<pre style=\"background-color:#{:02x}{:02x}{:02x};\">\n",
c.r,
c.g,
c.b)
.unwrap();
for maybe_line in highlighter.reader.lines() {
let line = try!(maybe_line);
let regions = highlighter.highlight_lines.highlight(&line);
let html = styles_to_coloured_html(®ions[..], IncludeBackground::IfDifferent(c));
output.push_str(&html);
output.push('\n');
}
output.push_str("</pre>\n");
Ok(output)
}
pub fn tokens_to_classed_html(line: &str,
ops: &[(usize, ScopeStackOp)],
style: ClassStyle)
-> String {
let mut s = String::with_capacity(line.len() + ops.len() * 8); let mut cur_index = 0;
let mut stack = ScopeStack::new();
for &(i, ref op) in ops {
if i > cur_index {
write!(s, "{}", Escape(&line[cur_index..i])).unwrap();
cur_index = i
}
stack.apply_with_hook(op, |basic_op, _| {
match basic_op {
BasicScopeStackOp::Push(scope) => {
s.push_str("<span class=\"");
scope_to_classes(&mut s, scope, style);
s.push_str("\">");
}
BasicScopeStackOp::Pop => {
s.push_str("</span>");
}
}
});
}
s
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum IncludeBackground {
No,
Yes,
IfDifferent(Color),
}
fn write_css_color(s: &mut String, c: Color) {
if c.a != 0xFF {
write!(s,"#{:02x}{:02x}{:02x}{:02x}",c.r,c.g,c.b,c.a).unwrap();
} else {
write!(s,"#{:02x}{:02x}{:02x}",c.r,c.g,c.b).unwrap();
}
}
pub fn styles_to_coloured_html(v: &[(Style, &str)], bg: IncludeBackground) -> String {
let mut s: String = String::new();
for &(ref style, text) in v.iter() {
write!(s, "<span style=\"").unwrap();
let include_bg = match bg {
IncludeBackground::Yes => true,
IncludeBackground::No => false,
IncludeBackground::IfDifferent(c) => (style.background != c),
};
if include_bg {
write!(s, "background-color:").unwrap();
write_css_color(&mut s, style.background);
write!(s, ";").unwrap();
}
if style.font_style.contains(highlighting::FONT_STYLE_UNDERLINE) {
write!(s, "text-decoration:underline;").unwrap();
}
if style.font_style.contains(highlighting::FONT_STYLE_BOLD) {
write!(s, "font-weight:bold;").unwrap();
}
if style.font_style.contains(highlighting::FONT_STYLE_ITALIC) {
write!(s, "font-style:italic;").unwrap();
}
write!(s, "color:").unwrap();
write_css_color(&mut s, style.foreground);
write!(s, ";\">{}</span>", Escape(text)).unwrap();
}
s
}
pub fn start_coloured_html_snippet(t: &Theme) -> String {
let c = t.settings.background.unwrap_or(highlighting::WHITE);
format!("<pre style=\"background-color:#{:02x}{:02x}{:02x}\">\n",
c.r,
c.g,
c.b)
}
#[cfg(test)]
mod tests {
use super::*;
use parsing::{SyntaxSet, ParseState, ScopeStack};
use highlighting::{ThemeSet, Style, Highlighter, HighlightIterator, HighlightState};
#[test]
fn tokens() {
let ps = SyntaxSet::load_defaults_nonewlines();
let syntax = ps.find_syntax_by_name("Markdown").unwrap();
let mut state = ParseState::new(syntax);
let line = "[w](t.co) *hi* **five**";
let ops = state.parse_line(line);
let html = tokens_to_classed_html(line, &ops[..], ClassStyle::Spaced);
println!("{}", html);
assert_eq!(html, include_str!("../testdata/test2.html").trim_right());
let ts = ThemeSet::load_defaults();
let highlighter = Highlighter::new(&ts.themes["InspiredGitHub"]);
let mut highlight_state = HighlightState::new(&highlighter, ScopeStack::new());
let iter = HighlightIterator::new(&mut highlight_state, &ops[..], line, &highlighter);
let regions: Vec<(Style, &str)> = iter.collect();
let html2 = styles_to_coloured_html(®ions[..], IncludeBackground::Yes);
println!("{}", html2);
assert_eq!(html2, include_str!("../testdata/test1.html").trim_right());
}
#[test]
fn strings() {
let ss = SyntaxSet::load_defaults_nonewlines();
let ts = ThemeSet::load_defaults();
let s = include_str!("../testdata/highlight_test.erb");
let syntax = ss.find_syntax_by_extension("erb").unwrap();
let html = highlighted_snippet_for_string(s, syntax, &ts.themes["base16-ocean.dark"]);
assert_eq!(html, include_str!("../testdata/test3.html"));
let html2 = highlighted_snippet_for_file("testdata/highlight_test.erb",
&ss,
&ts.themes["base16-ocean.dark"])
.unwrap();
assert_eq!(html2, html);
let html3 = highlighted_snippet_for_file("testdata/Packages/Rust/Cargo.sublime-syntax",
&ss,
&ts.themes["InspiredGitHub"])
.unwrap();
println!("{}", html3);
assert_eq!(html3, include_str!("../testdata/test4.html"));
}
#[test]
fn tricky_test_syntax() {
let ss = SyntaxSet::load_from_folder("testdata").unwrap();
let ts = ThemeSet::load_defaults();
let html = highlighted_snippet_for_file("testdata/testing-syntax.testsyntax",
&ss,
&ts.themes["base16-ocean.dark"])
.unwrap();
println!("{}", html);
assert_eq!(html, include_str!("../testdata/test5.html"));
}
}