use std::{
io::{self, Write},
sync::{LazyLock, OnceLock},
};
use syntect::dumps::from_binary;
use syntect::easy::HighlightLines;
use syntect::highlighting::ThemeSet;
use syntect::parsing::SyntaxSet;
use syntect::util::LinesWithEndings;
use termcolor::WriteColor;
use crate::{buffer::Buffer, cli::Theme};
pub(crate) mod headers;
pub(crate) mod palette;
pub fn get_json_formatter(indent_level: usize) -> jsonxf::Formatter {
let mut fmt = jsonxf::Formatter::pretty_printer();
fmt.indent = " ".repeat(indent_level);
fmt.record_separator = String::from("\n\n");
fmt.eager_record_separators = true;
fmt
}
pub fn serde_json_format(indent_level: usize, text: &str, write: impl Write) -> io::Result<()> {
let indent = " ".repeat(indent_level);
let formatter = serde_json::ser::PrettyFormatter::with_indent(indent.as_bytes());
let mut serializer = serde_json::Serializer::with_formatter(write, formatter);
let mut deserializer = serde_json::Deserializer::from_str(text);
serde_transcode::transcode(&mut deserializer, &mut serializer)?;
Ok(())
}
pub(crate) static THEMES: LazyLock<ThemeSet> = LazyLock::new(|| {
from_binary(include_bytes!(concat!(
env!("OUT_DIR"),
"/themepack.themedump"
)))
});
static PS_BASIC: LazyLock<SyntaxSet> =
LazyLock::new(|| from_binary(include_bytes!(concat!(env!("OUT_DIR"), "/basic.packdump"))));
static PS_LARGE: LazyLock<SyntaxSet> =
LazyLock::new(|| from_binary(include_bytes!(concat!(env!("OUT_DIR"), "/large.packdump"))));
pub struct Highlighter<'a> {
highlighter: HighlightLines<'static>,
syntax_set: &'static SyntaxSet,
out: &'a mut Buffer,
}
impl<'a> Highlighter<'a> {
pub fn new(syntax: &'static str, theme: Theme, out: &'a mut Buffer) -> Self {
let syntax_set: &SyntaxSet = match syntax {
"json" => &PS_BASIC,
_ => &PS_LARGE,
};
let syntax = syntax_set
.find_syntax_by_extension(syntax)
.expect("syntax not found");
Self {
highlighter: HighlightLines::new(syntax, theme.as_syntect_theme()),
syntax_set,
out,
}
}
pub fn highlight(&mut self, text: &str) -> io::Result<()> {
for line in LinesWithEndings::from(text) {
for (style, component) in self
.highlighter
.highlight_line(line, self.syntax_set)
.map_err(io::Error::other)?
{
self.out.set_color(&convert_style(style))?;
write!(self.out, "{component}")?;
}
}
Ok(())
}
pub fn highlight_bytes(&mut self, line: &[u8]) -> io::Result<()> {
self.highlight(&String::from_utf8_lossy(line))
}
pub fn flush(&mut self) -> io::Result<()> {
self.out.flush()
}
}
impl Drop for Highlighter<'_> {
fn drop(&mut self) {
let _ = self.out.reset();
}
}
fn convert_style(style: syntect::highlighting::Style) -> termcolor::ColorSpec {
use syntect::highlighting::FontStyle;
let mut spec = termcolor::ColorSpec::new();
spec.set_fg(convert_color(style.foreground))
.set_underline(style.font_style.contains(FontStyle::UNDERLINE))
.set_bold(style.font_style.contains(FontStyle::BOLD))
.set_italic(style.font_style.contains(FontStyle::ITALIC));
spec
}
fn convert_color(color: syntect::highlighting::Color) -> Option<termcolor::Color> {
use termcolor::Color;
if color.a == 0 {
match color.r {
0x00 => Some(Color::Black),
0x01 => Some(Color::Red),
0x02 => Some(Color::Green),
0x03 => Some(Color::Yellow),
0x04 => Some(Color::Blue),
0x05 => Some(Color::Magenta),
0x06 => Some(Color::Cyan),
0x07 => None,
n => Some(Color::Ansi256(n)),
}
} else {
Some(Color::Rgb(color.r, color.g, color.b))
}
}
pub(crate) fn supports_hyperlinks() -> bool {
static SUPPORTS_HYPERLINKS: OnceLock<bool> = OnceLock::new();
*SUPPORTS_HYPERLINKS.get_or_init(supports_hyperlinks::supports_hyperlinks)
}
pub(crate) fn create_hyperlink(text: &str, url: &str) -> String {
format!("\x1B]8;;{url}\x1B\\{text}\x1B]8;;\x1B\\")
}