use super::theme::*;
use {
owo_colors::*,
std::{fmt, io},
};
pub const MARKUP_DELIMITER: char = '|';
pub const MARKUP_ESCAPE: char = '\\';
pub const MARKUP_ESCAPED_DELIMITER: &str = "\\|";
pub fn depict_markup<ThingT>(thing: ThingT, theme: Option<&Theme>) -> String
where
ThingT: fmt::Display,
{
DepictionMarkupParser::new(thing.to_string().chars().collect()).depict(theme)
}
pub fn escape_depiction_markup<ThingT>(thing: ThingT) -> String
where
ThingT: fmt::Display,
{
thing.to_string().replace(MARKUP_DELIMITER, MARKUP_ESCAPED_DELIMITER)
}
pub fn write_depiction_markup<WriteT, ThingT>(
writer: &mut WriteT,
thing: ThingT,
theme: Option<&Theme>,
) -> io::Result<()>
where
WriteT: io::Write,
ThingT: fmt::Display,
{
write!(writer, "{}", depict_markup(thing, theme))
}
pub fn print_depiction_markup<ThingT>(thing: ThingT, theme: Option<&Theme>)
where
ThingT: fmt::Display,
{
write_depiction_markup(&mut anstream::stdout(), thing, theme).expect("writing to stdout");
}
pub fn eprint_depiction_markup<ThingT>(thing: ThingT, theme: Option<&Theme>)
where
ThingT: fmt::Display,
{
write_depiction_markup(&mut anstream::stderr(), thing, theme).expect("writing to stderr");
}
impl Theme {
pub fn depict_markup<ThingT>(&self, thing: ThingT) -> String
where
ThingT: fmt::Display,
{
depict_markup(thing, Some(self))
}
pub fn write_depiction_markup<WriteT, ThingT>(&self, writer: &mut WriteT, thing: ThingT) -> io::Result<()>
where
WriteT: io::Write,
ThingT: fmt::Display,
{
write_depiction_markup(writer, thing, Some(self))
}
pub fn print_depiction_markup<ThingT>(&self, thing: ThingT)
where
ThingT: fmt::Display,
{
print_depiction_markup(thing, Some(self));
}
pub fn eprint_depiction_markup<ThingT>(&self, thing: ThingT)
where
ThingT: fmt::Display,
{
eprint_depiction_markup(thing, Some(self));
}
}
struct DepictionMarkupParser {
characters: Vec<char>,
index: usize,
start: usize,
mode: Mode,
result: String,
}
impl DepictionMarkupParser {
fn new(characters: Vec<char>) -> Self {
Self { characters, index: 0, start: 0, mode: Mode::Unmarked, result: Default::default() }
}
fn depict(mut self, theme: Option<&Theme>) -> String {
let length = self.characters.len();
while self.index < length {
let c = self.characters[self.index];
match c {
MARKUP_DELIMITER => {
match theme {
Some(theme) => match self.mode {
Mode::Unmarked => self.mode = Mode::Style,
Mode::Style => self.mode = (&self.characters[self.start..self.index]).into(),
Mode::Symbol => self.append_styled(&theme.symbol_style),
Mode::Number => self.append_styled(&theme.number_style),
Mode::String => self.append_styled(&theme.string_style),
Mode::Name => self.append_styled(&theme.name_style),
Mode::Meta => self.append_styled(&theme.meta_style),
Mode::Error => self.append_styled(&theme.error_style),
Mode::Heading => self.append_styled(&theme.heading_style),
Mode::Delimiter => self.append_styled(&theme.delimiter_style),
_ => self.append(),
},
None => match self.mode {
Mode::Unmarked => self.mode = Mode::Style,
Mode::Style => self.mode = Mode::Plain,
_ => self.append(),
},
}
self.start = self.index + 1;
self.index += 1;
}
MARKUP_ESCAPE => {
if self.index < length - 1 {
let next = self.characters[self.index + 1];
if next == MARKUP_DELIMITER {
self.result.push(MARKUP_DELIMITER);
self.index += 2;
} else {
self.result.push(MARKUP_ESCAPE);
self.index += 1;
}
} else {
self.result.push(MARKUP_ESCAPE);
self.index += 1;
}
}
_ => {
if matches!(self.mode, Mode::Unmarked) {
self.result.push(c);
}
self.index += 1;
}
}
}
self.result
}
fn append(&mut self) {
let start = self.start;
let end = self.index;
if end > start {
self.result.reserve(end - start + 1);
for c in &self.characters[start..end] {
self.result.push(*c);
}
}
self.mode = Mode::Unmarked;
}
fn append_styled(&mut self, style: &Style) {
let start = self.start;
let end = self.index;
if end > start {
let styled: String = self.characters[start..end].iter().collect();
self.result.push_str(&style.style(styled).to_string());
}
self.mode = Mode::Unmarked;
}
}
#[derive(Clone, Copy, Debug, Default)]
enum Mode {
#[default]
Unmarked,
Style,
Symbol,
Number,
String,
Name,
Meta,
Error,
Heading,
Delimiter,
Plain, }
impl From<&[char]> for Mode {
fn from(value: &[char]) -> Self {
match value {
['s', 'y', 'm', 'b', 'o', 'l'] => Mode::Symbol,
['n', 'u', 'm', 'b', 'e', 'r'] => Mode::Number,
['s', 't', 'r', 'i', 'n', 'g'] => Mode::String,
['n', 'a', 'm', 'e'] => Mode::Name,
['m', 'e', 't', 'a'] => Mode::Meta,
['e', 'r', 'r', 'o', 'r'] => Mode::Error,
['h', 'e', 'a', 'd', 'i', 'n', 'g'] => Mode::Heading,
['d', 'e', 'l', 'i', 'm', 'i', 't', 'e', 'r'] => Mode::Delimiter,
_ => Mode::Plain,
}
}
}