use nu_ansi_term::{Color, Style};
use sdml_core::error::Error;
use std::io::Write;
use tree_sitter_highlight::HighlightConfiguration;
use tree_sitter_highlight::HighlightEvent;
use tree_sitter_highlight::Highlighter;
use tree_sitter_highlight::HtmlRenderer;
const HIGHLIGHT_NAMES: &[&str] = &[
"boolean",
"comment",
"constant",
"embedded",
"error",
"function.call",
"function.definition",
"keyword",
"operator",
"method.definition",
"module",
"module.definition",
"number",
"property",
"punctuation.bracket",
"punctuation.delimiter",
"punctuation.separator",
"string",
"string.special",
"type",
"type.builtin",
"type.definition",
"variable",
"variable.builtin",
"variable.field",
"variable.parameter",
];
const DEFAULT_CSS: &str = include_str!("sdml-highlight.css");
const HTML_HEADER: &str = r#"<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Formatted SDML</title>
<style type="text/css" media="screen">
body {
font-family: "Fira Sans",sans;
}
{css}
</style>
</head>
<body>
<header>
<h1>Formatted SDML</h1>
</header>
<main>
"#;
const HTML_FOOTER: &str = r#" </main>
<footer>
<p>Generated by sdml <a href="https://github.com/johnstonskj/rust-sdml">command-line tool</a>.</p>
</footer>
</body>
</html>
"#;
pub fn write_highlighted_as_ansi<S: AsRef<[u8]>, W: Write>(
source: S,
w: &mut W,
) -> Result<(), Error> {
let (mut highlighter, config) = highlighter_init();
let events = highlighter
.highlight(&config, source.as_ref(), None, |_| None)
.unwrap();
highlight_as_ansi(&source, w, events)
}
pub fn write_highlighted_as_html<S: AsRef<[u8]>, W: Write>(
source: S,
w: &mut W,
stand_alone: bool,
) -> Result<(), Error> {
let (mut highlighter, config) = highlighter_init();
let events = highlighter
.highlight(&config, source.as_ref(), None, |_| None)
.unwrap();
highlight_as_html(&source, w, events, stand_alone)
}
fn highlight_as_ansi<S: AsRef<[u8]>, W: Write>(
source: S,
w: &mut W,
events: impl Iterator<Item = Result<HighlightEvent, tree_sitter_highlight::Error>>,
) -> Result<(), Error> {
let styles: Vec<Style> = vec![
Style::new().fg(Color::Fixed(58)), Style::new().fg(Color::Fixed(248)).italic(), Style::new().fg(Color::Fixed(58)), Style::new().fg(Color::Fixed(248)).italic(), Style::new().fg(Color::Fixed(9)).underline(), Style::new().fg(Color::Fixed(26)), Style::new().fg(Color::Fixed(26)).bold(), Style::new().fg(Color::Fixed(100)), Style::new().fg(Color::Fixed(239)).bold(), Style::new().fg(Color::Fixed(26)).bold(), Style::new().fg(Color::Fixed(19)), Style::new().fg(Color::Fixed(19)).bold(), Style::new().fg(Color::Fixed(58)), Style::new().fg(Color::Fixed(160)), Style::new().fg(Color::Fixed(239)), Style::new().fg(Color::Fixed(239)), Style::new().fg(Color::Fixed(239)), Style::new().fg(Color::Fixed(70)), Style::new().fg(Color::Fixed(92)), Style::new().fg(Color::Fixed(27)), Style::new().fg(Color::Fixed(21)), Style::new().fg(Color::Fixed(27)).bold(), Style::new().fg(Color::Fixed(67)), Style::new().fg(Color::Fixed(67)), Style::new().fg(Color::Fixed(67)), Style::new().fg(Color::Fixed(67)), ];
highlight_as_ansi_inner(source, w, &styles, events)
}
fn highlight_as_ansi_inner<S: AsRef<[u8]>, W: Write>(
source: S,
w: &mut W,
styles: &[Style],
events: impl Iterator<Item = Result<HighlightEvent, tree_sitter_highlight::Error>>,
) -> Result<(), Error> {
let source = source.as_ref();
let mut style_stack = vec![Style::new()];
for event in events {
match event.unwrap() {
HighlightEvent::HighlightStart(highlight) => {
style_stack.push(styles[highlight.0]);
}
HighlightEvent::HighlightEnd => {
style_stack.pop();
}
HighlightEvent::Source { start, end } => {
style_stack
.last()
.unwrap()
.paint(&source[start..end])
.write_to(w)?;
}
}
}
Ok(())
}
fn highlight_as_html<S: AsRef<[u8]>, W: Write>(
source: S,
w: &mut W,
events: impl Iterator<Item = Result<HighlightEvent, tree_sitter_highlight::Error>>,
stand_alone: bool,
) -> Result<(), Error> {
let css_classes: Vec<String> = HIGHLIGHT_NAMES
.iter()
.map(|s| format!("class=\"{}\"", s.replace('.', "-")))
.collect();
if stand_alone {
write!(w, "{}", HTML_HEADER.replace("{css}", DEFAULT_CSS))?;
}
highlight_as_html_inner(
source,
w,
&css_classes,
events,
if stand_alone { " " } else { "" },
)?;
if stand_alone {
write!(w, "{}", HTML_FOOTER)?;
}
Ok(())
}
fn highlight_as_html_inner<S: AsRef<[u8]>, W: Write>(
source: S,
w: &mut W,
css_classes: &[String],
events: impl Iterator<Item = Result<HighlightEvent, tree_sitter_highlight::Error>>,
prefix: &str,
) -> Result<(), Error> {
let mut renderer = HtmlRenderer::new();
renderer
.render(events, source.as_ref(), &move |highlight| {
css_classes[highlight.0].as_bytes()
})
.unwrap();
writeln!(w, "{}<pre>", prefix)?;
writeln!(w, "{} <code class=\"sdml\">", prefix)?;
for line in renderer.lines() {
write!(w, "{} {}", prefix, line)?;
}
writeln!(w, "{} </code>", prefix)?;
writeln!(w, "{}</pre>", prefix)?;
Ok(())
}
fn highlighter_init() -> (Highlighter, HighlightConfiguration) {
let highlighter = Highlighter::new();
let mut config = HighlightConfiguration::new(
tree_sitter_sdml::language(),
"sdml",
tree_sitter_sdml::HIGHLIGHTS_QUERY,
tree_sitter_sdml::INJECTIONS_QUERY,
tree_sitter_sdml::LOCALS_QUERY,
)
.unwrap();
config.configure(HIGHLIGHT_NAMES);
(highlighter, config)
}