use std::fmt::{Display, Formatter, Write};
use super::{AnsiColor, codes, codes::bitmasks};
pub struct AnsiStyle {
pub foreground: Option<AnsiColor>,
pub background: Option<AnsiColor>,
pub flags: u8,
}
impl AnsiStyle {
fn set_flags(&mut self, mask: u8, enabled: bool) -> &mut Self {
if enabled {
self.flags |= mask;
} else {
self.flags &= !mask;
}
self
}
fn chain_set_flags(mut self, mask: u8, enabled: bool) -> Self {
self.set_flags(mask, enabled);
self
}
pub const fn plain() -> Self {
Self {
foreground: None,
background: None,
flags: 0,
}
}
pub const fn with_background(background: AnsiColor) -> Self {
Self {
foreground: None,
background: Some(background),
flags: 0,
}
}
pub const fn with_foreground(foreground: AnsiColor) -> Self {
Self {
foreground: Some(foreground),
background: None,
flags: 0,
}
}
pub fn is_bold(&self) -> bool {
self.flags & bitmasks::BOLD == bitmasks::BOLD
}
pub fn is_italic(&self) -> bool {
self.flags & bitmasks::ITALIC == bitmasks::ITALIC
}
pub fn is_underline(&self) -> bool {
self.flags & bitmasks::UNDERLINE == bitmasks::UNDERLINE
}
pub fn is_strikethrough(&self) -> bool {
self.flags & bitmasks::STRIKETHROUGH == bitmasks::STRIKETHROUGH
}
pub fn is_dim(&self) -> bool {
self.flags & bitmasks::DIM == bitmasks::DIM
}
pub fn is_blinking(&self) -> bool {
self.flags & bitmasks::BLINKING == bitmasks::BLINKING
}
pub fn is_reverse(&self) -> bool {
self.flags & bitmasks::REVERSE == bitmasks::REVERSE
}
pub fn is_hidden(&self) -> bool {
self.flags & bitmasks::HIDDEN == bitmasks::HIDDEN
}
pub fn is_plain(&self) -> bool {
!((self.flags != 0) | self.foreground.is_some() | self.background.is_some())
}
pub fn set_bold(&mut self, enabled: bool) -> &mut Self {
self.set_flags(bitmasks::BOLD, enabled)
}
pub fn set_italic(&mut self, enabled: bool) -> &mut Self {
self.set_flags(bitmasks::ITALIC, enabled)
}
pub fn set_underline(&mut self, enabled: bool) -> &mut Self {
self.set_flags(bitmasks::UNDERLINE, enabled)
}
pub fn set_strikethrough(&mut self, enabled: bool) -> &mut Self {
self.set_flags(bitmasks::STRIKETHROUGH, enabled)
}
pub fn set_dim(&mut self, enabled: bool) -> &mut Self {
self.set_flags(bitmasks::DIM, enabled)
}
pub fn set_blinking(&mut self, enabled: bool) -> &mut Self {
self.set_flags(bitmasks::BLINKING, enabled)
}
pub fn set_reverse(&mut self, enabled: bool) -> &mut Self {
self.set_flags(bitmasks::REVERSE, enabled)
}
pub fn set_hidden(&mut self, enabled: bool) -> &mut Self {
self.set_flags(bitmasks::HIDDEN, enabled)
}
pub fn set_fg(&mut self, foreground: Option<AnsiColor>) -> &mut Self {
self.foreground = foreground;
self
}
pub fn set_bg(&mut self, background: Option<AnsiColor>) -> &mut Self {
self.background = background;
self
}
pub fn chain_bold(self, enabled: bool) -> Self {
self.chain_set_flags(bitmasks::BOLD, enabled)
}
pub fn chain_italic(self, enabled: bool) -> Self {
self.chain_set_flags(bitmasks::ITALIC, enabled)
}
pub fn chain_underline(self, enabled: bool) -> Self {
self.chain_set_flags(bitmasks::UNDERLINE, enabled)
}
pub fn chain_strikethrough(self, enabled: bool) -> Self {
self.chain_set_flags(bitmasks::STRIKETHROUGH, enabled)
}
pub fn chain_dim(self, enabled: bool) -> Self {
self.chain_set_flags(bitmasks::DIM, enabled)
}
pub fn chain_blinking(self, enabled: bool) -> Self {
self.chain_set_flags(bitmasks::BLINKING, enabled)
}
pub fn chain_reverse(self, enabled: bool) -> Self {
self.chain_set_flags(bitmasks::REVERSE, enabled)
}
pub fn chain_hidden(self, enabled: bool) -> Self {
self.chain_set_flags(bitmasks::HIDDEN, enabled)
}
pub fn chain_fg(mut self, foreground: Option<AnsiColor>) -> Self {
self.foreground = foreground;
self
}
pub fn chain_bg(mut self, background: Option<AnsiColor>) -> Self {
self.background = background;
self
}
pub fn mutate_apply(&self, text: &mut String) {
text.insert_str(0, &self.to_string());
}
pub fn mutate_apply_with_reset(&self, text: &mut String) {
if self.is_plain() {
return;
}
self.mutate_apply(text);
text.push_str(codes::RESET);
}
pub fn apply(&self, text: &str) -> String {
let mut new_text = text.to_owned();
self.mutate_apply(&mut new_text);
new_text
}
pub fn apply_with_reset(&self, text: &str) -> String {
let mut new_text = text.to_owned();
self.mutate_apply_with_reset(&mut new_text);
new_text
}
pub fn write_to_stream<W: Write>(&self, stream_out: &mut W) -> std::fmt::Result {
if self.is_plain() {
return Ok(());
}
stream_out.write_str("\x1b[")?;
let mut wrote = false;
let mut append = |arg: &str| -> std::fmt::Result {
if wrote {
stream_out.write_char(';')?;
}
stream_out.write_str(arg)?;
wrote = true;
Ok(())
};
if self.is_bold() {
append("1")?
}
if self.is_dim() {
append("2")?
}
if self.is_italic() {
append("3")?
}
if self.is_underline() {
append("4")?
}
if self.is_blinking() {
append("5")?
}
if self.is_reverse() {
append("7")?
}
if self.is_hidden() {
append("8")?
}
if self.is_strikethrough() {
append("9")?
}
if let Some(foreground) = &self.foreground {
append(foreground.gen_sequence(false).as_str())?
}
if let Some(background) = &self.background {
append(background.gen_sequence(true).as_str())?
}
stream_out.write_char('m')?;
Ok(())
}
}
impl Display for AnsiStyle {
fn fmt(&self, handle: &mut Formatter) -> std::fmt::Result {
self.write_to_stream(handle)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_style_is_plain() {
let style = AnsiStyle::plain();
assert!(style.is_plain(), "Not plain")
}
#[test]
fn flag_positions() {
let style = AnsiStyle {
flags: 0b101,
background: None,
foreground: None,
};
assert!(style.is_bold(), "Style should be bold");
assert!(!style.is_italic(), "Style shouldn't be italic");
assert!(style.is_underline(), "Style should be underlined");
assert!(
!style.is_strikethrough(),
"Style shouldn't be strikethrough"
);
}
#[test]
fn mutate_flags() {
let mut style = AnsiStyle::plain();
assert_eq!(style.flags, 0, "flags should be 0");
style.set_bold(true);
assert_eq!(style.flags, 1, "flags should be 1");
style.set_italic(true);
assert_eq!(style.flags, 3, "flags should be 3");
style.set_underline(true);
assert_eq!(style.flags, 7, "flags should be 7");
style.set_strikethrough(true);
assert_eq!(style.flags, 15, "flags should be 15");
}
#[test]
fn plain_mutate_apply_should_not_modify() {
let plain_style = AnsiStyle::plain();
let mut text = String::from("Hello world!");
plain_style.mutate_apply(&mut text);
assert_eq!(
&text[..],
"Hello world!",
"String was modified by a plain style, got {text}"
);
plain_style.mutate_apply_with_reset(&mut text);
assert_eq!(
&text[..],
"Hello world!",
"String was modified by a plain style, got {text}"
)
}
#[test]
fn mutate_apply_modify() {
let style = AnsiStyle {
flags: 0b1,
background: None,
foreground: None,
};
let mut text = String::from("Hello world!");
let expected = "\x1b[1mHello world!";
assert_eq!(
&text[..],
"Hello world!" );
style.mutate_apply(&mut text);
assert_eq!(
&text[..],
expected,
"Expected: {:3?}\nGot: {:3?}",
expected.as_bytes(),
text.as_bytes()
);
}
#[test]
fn plain_apply_should_not_differ() {
let style = AnsiStyle::plain();
let input_text = String::from("Hello world!");
assert_eq!(
&input_text,
"Hello world!" );
let output_text = style.apply(&input_text);
assert_eq!(
output_text,
input_text )
}
}