use proc_macro2::Span;
use crate::error::{Error, SpanError};
#[derive(Debug, PartialEq, Default)]
pub struct Context<'a>(Vec<ColorTag<'a>>);
impl<'a> Context<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn ansi_apply_tags(&mut self, tag_group: Vec<ColorTag<'a>>) -> Result<String, SpanError> {
let state_diff = self.apply_tags_and_get_diff(tag_group)?;
Ok(state_diff.ansi_string())
}
pub fn apply_tags(&mut self, tag_group: Vec<ColorTag<'a>>) -> Result<(), SpanError> {
self.apply_tags_and_get_diff(tag_group).map(|_| ())
}
pub fn state(&self) -> State {
let mut state = State::default();
for tag in &self.0 {
if let Some(ref color) = tag.change_set.foreground {
state.foreground = ExtColor::Color(color.clone());
}
if let Some(ref color) = tag.change_set.background {
state.background = ExtColor::Color(color.clone());
}
state.bold |= tag.change_set.bold;
state.dim |= tag.change_set.dim;
state.underline |= tag.change_set.underline;
state.italics |= tag.change_set.italics;
state.blink |= tag.change_set.blink;
state.strike |= tag.change_set.strike;
state.reverse |= tag.change_set.reverse;
state.conceal |= tag.change_set.conceal;
if let Some(ref url) = tag.change_set.link {
state.link = Some(url.clone());
}
}
state
}
fn apply_tags_and_get_diff(&mut self, tags: Vec<ColorTag<'a>>) -> Result<StateDiff, SpanError> {
let old_state = self.state();
for tag in tags {
if tag.is_close {
let last_tag = self
.0
.last()
.ok_or_else(|| SpanError::new(Error::NoTagToClose, tag.span))?;
if !tag.change_set.is_void() && last_tag.change_set != tag.change_set {
let (last_src, src) = (
last_tag.source.unwrap(),
tag.source.unwrap(),
);
return Err(SpanError::new(
Error::MismatchCloseTag(last_src.to_owned(), src.to_owned()),
tag.span,
));
}
self.0.pop().unwrap();
} else {
self.0.push(tag);
}
}
let new_state = self.state();
Ok(StateDiff::from_diff(&old_state, &new_state))
}
}
#[derive(Debug, PartialEq, Default)]
pub struct State {
foreground: ExtColor,
background: ExtColor,
bold: bool,
dim: bool,
underline: bool,
italics: bool,
blink: bool,
strike: bool,
reverse: bool,
conceal: bool,
link: Option<String>,
}
#[derive(Debug)]
pub struct StateDiff {
foreground: Action<ExtColor>,
background: Action<ExtColor>,
bold: Action<bool>,
dim: Action<bool>,
underline: Action<bool>,
italics: Action<bool>,
blink: Action<bool>,
strike: Action<bool>,
reverse: Action<bool>,
conceal: Action<bool>,
link: Action<Option<String>>,
}
impl StateDiff {
pub fn from_diff(old: &State, new: &State) -> Self {
StateDiff {
foreground: Action::from_diff(
Some(old.foreground.clone()),
Some(new.foreground.clone()),
),
background: Action::from_diff(
Some(old.background.clone()),
Some(new.background.clone()),
),
bold: Action::from_diff(Some(old.bold), Some(new.bold)),
dim: Action::from_diff(Some(old.dim), Some(new.dim)),
underline: Action::from_diff(Some(old.underline), Some(new.underline)),
italics: Action::from_diff(Some(old.italics), Some(new.italics)),
blink: Action::from_diff(Some(old.blink), Some(new.blink)),
strike: Action::from_diff(Some(old.strike), Some(new.strike)),
reverse: Action::from_diff(Some(old.reverse), Some(new.reverse)),
conceal: Action::from_diff(Some(old.conceal), Some(new.conceal)),
link: Action::from_diff(Some(old.link.clone()), Some(new.link.clone())),
}
}
pub fn ansi_string(&self) -> String {
use crate::ansi_constants::*;
let mut output = String::new();
macro_rules! push_code {
($($codes:expr),*) => { output.push_str(&generate_ansi_code(&[$($codes),*])) };
}
if let Action::Change(ref ext_color) = self.foreground {
match ext_color {
ExtColor::Normal => push_code!(DEFAULT_FOREGROUND),
ExtColor::Color(Color::Color16(color)) => match color.intensity {
Intensity::Normal => {
push_code!(SET_FOREGROUND_BASE + color.base_color.index())
},
Intensity::Bright => {
push_code!(SET_BRIGHT_FOREGROUND_BASE + color.base_color.index())
},
},
ExtColor::Color(Color::Color256(color)) => {
push_code!(SET_FOREGROUND, 5, color.0);
},
ExtColor::Color(Color::ColorRgb(color)) => {
push_code!(SET_FOREGROUND, 2, color.r, color.g, color.b);
},
}
}
if let Action::Change(ref ext_color) = self.background {
match ext_color {
ExtColor::Normal => push_code!(DEFAULT_BACKGROUND),
ExtColor::Color(Color::Color16(color)) => match color.intensity {
Intensity::Normal => {
push_code!(SET_BACKGROUND_BASE + color.base_color.index())
},
Intensity::Bright => {
push_code!(SET_BRIGHT_BACKGROUND_BASE + color.base_color.index())
},
},
ExtColor::Color(Color::Color256(color)) => {
push_code!(SET_BACKGROUND, 5, color.0);
},
ExtColor::Color(Color::ColorRgb(color)) => {
push_code!(SET_BACKGROUND, 2, color.r, color.g, color.b);
},
}
}
macro_rules! handle_attr {
($attr:expr, $true_val:expr, $false_val:expr) => {
match $attr {
Action::Change(true) => push_code!($true_val),
Action::Change(false) => push_code!($false_val),
_ => (),
}
};
}
handle_attr!(self.bold, BOLD, NO_BOLD);
handle_attr!(self.dim, DIM, NO_BOLD);
handle_attr!(self.underline, UNDERLINE, NO_UNDERLINE);
handle_attr!(self.italics, ITALIC, NO_ITALIC);
handle_attr!(self.blink, BLINK, NO_BLINK);
handle_attr!(self.strike, STRIKE, NO_STRIKE);
handle_attr!(self.reverse, REVERSE, NO_REVERSE);
handle_attr!(self.conceal, CONCEAL, NO_CONCEAL);
if let Action::Change(ref link) = self.link {
let url = link.as_deref().unwrap_or("");
output.push_str(&generate_osc8_link(url));
}
output
}
}
#[derive(Debug, PartialEq)]
pub enum Action<T> {
None,
Keep(T),
Change(T),
}
impl<T> Action<T>
where
T: PartialEq,
{
pub fn from_diff(old: Option<T>, new: Option<T>) -> Self {
let eq = old == new;
match (old, new, eq) {
(Some(old_val), Some(_), true) | (Some(old_val), None, _) => Action::Keep(old_val),
(_, Some(new_val), _) => Action::Change(new_val),
_ => Action::None,
}
}
}
#[derive(Debug, Default)]
pub struct ColorTag<'a> {
pub source: Option<&'a str>,
pub span: Option<Span>,
pub is_close: bool,
pub change_set: ChangeSet,
}
impl PartialEq for ColorTag<'_> {
fn eq(&self, other: &Self) -> bool {
and!(
self.source == other.source,
self.is_close == other.is_close,
self.change_set == other.change_set,
)
}
}
impl<'a> ColorTag<'a> {
pub fn new_close() -> Self {
ColorTag {
source: None,
span: None,
is_close: true,
change_set: ChangeSet::default(),
}
}
pub fn set_span(&mut self, span: Span) {
self.span = Some(span);
}
}
#[derive(Debug, PartialEq, Default)]
pub struct ChangeSet {
pub foreground: Option<Color>,
pub background: Option<Color>,
pub bold: bool,
pub dim: bool,
pub underline: bool,
pub italics: bool,
pub blink: bool,
pub strike: bool,
pub reverse: bool,
pub conceal: bool,
pub link: Option<String>,
}
impl ChangeSet {
pub fn is_void(&self) -> bool {
and!(
self.foreground.is_none(),
self.background.is_none(),
!self.bold,
!self.dim,
!self.underline,
!self.italics,
!self.blink,
!self.strike,
!self.reverse,
!self.conceal,
self.link.is_none(),
)
}
}
impl From<&[Change]> for ChangeSet {
fn from(changes: &[Change]) -> ChangeSet {
let mut change_set = ChangeSet::default();
for change in changes {
match change {
Change::Foreground(color) => change_set.foreground = Some(color.clone()),
Change::Background(color) => change_set.background = Some(color.clone()),
Change::Bold => change_set.bold = true,
Change::Dim => change_set.dim = true,
Change::Underline => change_set.underline = true,
Change::Italics => change_set.italics = true,
Change::Blink => change_set.blink = true,
Change::Strike => change_set.strike = true,
Change::Reverse => change_set.reverse = true,
Change::Conceal => change_set.conceal = true,
Change::Link(url) => change_set.link = Some(url.clone()),
}
}
change_set
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Change {
Foreground(Color),
Background(Color),
Bold,
Dim,
Underline,
Italics,
Blink,
Strike,
Reverse,
Conceal,
Link(String),
}
impl TryFrom<&str> for Change {
type Error = ();
#[rustfmt::skip]
fn try_from(input: &str) -> Result<Self, Self::Error> {
macro_rules! color16 {
($kind:ident $intensity:ident $base_color:ident) => {
Change::$kind(Color::Color16(Color16::new(
BaseColor::$base_color,
Intensity::$intensity,
)))
};
}
let change = match input {
"s" | "strong" | "bold" | "em" => Change::Bold,
"dim" => Change::Dim,
"u" | "underline" => Change::Underline,
"i" | "italic" | "italics" => Change::Italics,
"blink" => Change::Blink,
"strike" => Change::Strike,
"reverse" | "rev" => Change::Reverse,
"conceal" | "hide" => Change::Conceal,
"k" | "black" => color16!(Foreground Normal Black),
"r" | "red" => color16!(Foreground Normal Red),
"g" | "green" => color16!(Foreground Normal Green),
"y" | "yellow" => color16!(Foreground Normal Yellow),
"b" | "blue" => color16!(Foreground Normal Blue),
"m" | "magenta" => color16!(Foreground Normal Magenta),
"c" | "cyan" => color16!(Foreground Normal Cyan),
"w" | "white" => color16!(Foreground Normal White),
"k!" | "black!" | "bright-black" => color16!(Foreground Bright Black),
"r!" | "red!" | "bright-red" => color16!(Foreground Bright Red),
"g!" | "green!" | "bright-green" => color16!(Foreground Bright Green),
"y!" | "yellow!" | "bright-yellow" => color16!(Foreground Bright Yellow),
"b!" | "blue!" | "bright-blue" => color16!(Foreground Bright Blue),
"m!" | "magenta!" | "bright-magenta" => color16!(Foreground Bright Magenta),
"c!" | "cyan!" | "bright-cyan" => color16!(Foreground Bright Cyan),
"w!" | "white!" | "bright-white" => color16!(Foreground Bright White),
"K" | "bg-black" => color16!(Background Normal Black),
"R" | "bg-red" => color16!(Background Normal Red),
"G" | "bg-green" => color16!(Background Normal Green),
"Y" | "bg-yellow" => color16!(Background Normal Yellow),
"B" | "bg-blue" => color16!(Background Normal Blue),
"M" | "bg-magenta" => color16!(Background Normal Magenta),
"C" | "bg-cyan" => color16!(Background Normal Cyan),
"W" | "bg-white" => color16!(Background Normal White),
"K!" | "bg-black!" | "bg-bright-black" => color16!(Background Bright Black),
"R!" | "bg-red!" | "bg-bright-red" => color16!(Background Bright Red),
"G!" | "bg-green!" | "bg-bright-green" => color16!(Background Bright Green),
"Y!" | "bg-yellow!" | "bg-bright-yellow" => color16!(Background Bright Yellow),
"B!" | "bg-blue!" | "bg-bright-blue" => color16!(Background Bright Blue),
"M!" | "bg-magenta!" | "bg-bright-magenta" => color16!(Background Bright Magenta),
"C!" | "bg-cyan!" | "bg-bright-cyan" => color16!(Background Bright Cyan),
"W!" | "bg-white!" | "bg-bright-white" => color16!(Background Bright White),
_ => return Err(()),
};
Ok(change)
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum ColorKind {
Background,
Foreground,
}
impl ColorKind {
pub fn to_change(&self, color: Color) -> Change {
match self {
Self::Foreground => Change::Foreground(color),
Self::Background => Change::Background(color),
}
}
}
#[derive(Debug, Default, PartialEq, Clone)]
pub enum ExtColor {
#[default]
Normal,
Color(Color),
}
#[derive(Debug, PartialEq, Clone)]
#[allow(clippy::enum_variant_names)]
pub enum Color {
Color16(Color16),
Color256(Color256),
ColorRgb(ColorRgb),
}
#[derive(Debug, PartialEq, Clone)]
pub struct Color16 {
base_color: BaseColor,
intensity: Intensity,
}
impl Color16 {
pub fn new(base_color: BaseColor, intensity: Intensity) -> Self {
Self {
base_color,
intensity,
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Intensity {
Normal,
Bright,
}
impl Intensity {
pub fn new(is_bright: bool) -> Self {
if is_bright {
Self::Bright
} else {
Self::Normal
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum BaseColor {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
impl BaseColor {
pub fn index(&self) -> u8 {
match self {
Self::Black => 0,
Self::Red => 1,
Self::Green => 2,
Self::Yellow => 3,
Self::Blue => 4,
Self::Magenta => 5,
Self::Cyan => 6,
Self::White => 7,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Color256(pub u8);
#[derive(Debug, PartialEq, Clone)]
pub struct ColorRgb {
pub r: u8,
pub g: u8,
pub b: u8,
}
#[cfg(test)]
mod tests {}