diff --git a/src/utils.rs b/src/utils.rs index a6cf37d6..c3d13504 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; -use std::collections::BTreeSet; use std::env; use std::fmt; +use std::fmt::{Debug, Formatter}; use std::sync::atomic::{AtomicBool, Ordering}; use once_cell::sync::Lazy; @@ -116,32 +116,85 @@ impl Color { /// A terminal style attribute. #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)] +#[repr(u16)] pub enum Attribute { - Bold, - Dim, - Italic, - Underlined, - Blink, - BlinkFast, - Reverse, - Hidden, - StrikeThrough, + // This mapping is important, it exactly matches ansi_num = (x as u16 + 1) + // See `ATTRIBUTES_LOOKUP` as well + Bold = 0, + Dim = 1, + Italic = 2, + Underlined = 3, + Blink = 4, + BlinkFast = 5, + Reverse = 6, + Hidden = 7, + StrikeThrough = 8, } -impl Attribute { +#[derive(Clone, Copy, PartialEq, Eq)] +struct Attributes(u16); + +impl Attributes { + const ATTRIBUTES_LOOKUP: [Attribute; 9] = [ + Attribute::Bold, + Attribute::Dim, + Attribute::Italic, + Attribute::Underlined, + Attribute::Blink, + Attribute::BlinkFast, + Attribute::Reverse, + Attribute::Hidden, + Attribute::StrikeThrough, + ]; + #[inline] - fn ansi_num(self) -> usize { - match self { - Attribute::Bold => 1, - Attribute::Dim => 2, - Attribute::Italic => 3, - Attribute::Underlined => 4, - Attribute::Blink => 5, - Attribute::BlinkFast => 6, - Attribute::Reverse => 7, - Attribute::Hidden => 8, - Attribute::StrikeThrough => 9, + const fn new() -> Self { + Attributes(0) + } + + #[inline] + #[must_use] + const fn insert(mut self, attr: Attribute) -> Self { + let bit = attr as u16; + self.0 |= 1 << bit; + self + } + + #[inline] + const fn bits(self) -> BitsIter { + BitsIter(self.0) + } + + #[inline] + fn ansi_nums(self) -> impl Iterator { + // Per construction of the enum + self.bits().map(|bit| bit + 1) + } + + #[inline] + fn attrs(self) -> impl Iterator { + self.bits().map(|bit| Self::ATTRIBUTES_LOOKUP[bit as usize]) + } +} + +struct BitsIter(u16); + +impl Iterator for BitsIter { + type Item = u16; + + fn next(&mut self) -> Option { + if self.0 == 0 { + return None; } + let bit = self.0.trailing_zeros(); + self.0 ^= (1 << bit) as u16; + Some(bit as u16) + } +} + +impl Debug for Attributes { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_set().entries(self.attrs()).finish() } } @@ -160,7 +213,7 @@ pub struct Style { bg: Option, fg_bright: bool, bg_bright: bool, - attrs: BTreeSet, + attrs: Attributes, force: Option, for_stderr: bool, } @@ -179,7 +232,7 @@ impl Style { bg: None, fg_bright: false, bg_bright: false, - attrs: BTreeSet::new(), + attrs: Attributes::new(), force: None, for_stderr: false, } @@ -290,8 +343,8 @@ impl Style { /// Adds a attr. #[inline] - pub fn attr(mut self, attr: Attribute) -> Self { - self.attrs.insert(attr); + pub const fn attr(mut self, attr: Attribute) -> Self { + self.attrs = self.attrs.insert(attr); self } @@ -382,39 +435,39 @@ impl Style { } #[inline] - pub fn bold(self) -> Self { + pub const fn bold(self) -> Self { self.attr(Attribute::Bold) } #[inline] - pub fn dim(self) -> Self { + pub const fn dim(self) -> Self { self.attr(Attribute::Dim) } #[inline] - pub fn italic(self) -> Self { + pub const fn italic(self) -> Self { self.attr(Attribute::Italic) } #[inline] - pub fn underlined(self) -> Self { + pub const fn underlined(self) -> Self { self.attr(Attribute::Underlined) } #[inline] - pub fn blink(self) -> Self { + pub const fn blink(self) -> Self { self.attr(Attribute::Blink) } #[inline] - pub fn blink_fast(self) -> Self { + pub const fn blink_fast(self) -> Self { self.attr(Attribute::BlinkFast) } #[inline] - pub fn reverse(self) -> Self { + pub const fn reverse(self) -> Self { self.attr(Attribute::Reverse) } #[inline] - pub fn hidden(self) -> Self { + pub const fn hidden(self) -> Self { self.attr(Attribute::Hidden) } #[inline] - pub fn strikethrough(self) -> Self { + pub const fn strikethrough(self) -> Self { self.attr(Attribute::StrikeThrough) } } @@ -467,152 +520,152 @@ impl StyledObject { /// /// This is the default #[inline] - pub fn for_stdout(mut self) -> StyledObject { + pub const fn for_stdout(mut self) -> StyledObject { self.style = self.style.for_stdout(); self } /// Sets a foreground color. #[inline] - pub fn fg(mut self, color: Color) -> StyledObject { + pub const fn fg(mut self, color: Color) -> StyledObject { self.style = self.style.fg(color); self } /// Sets a background color. #[inline] - pub fn bg(mut self, color: Color) -> StyledObject { + pub const fn bg(mut self, color: Color) -> StyledObject { self.style = self.style.bg(color); self } /// Adds a attr. #[inline] - pub fn attr(mut self, attr: Attribute) -> StyledObject { + pub const fn attr(mut self, attr: Attribute) -> StyledObject { self.style = self.style.attr(attr); self } #[inline] - pub fn black(self) -> StyledObject { + pub const fn black(self) -> StyledObject { self.fg(Color::Black) } #[inline] - pub fn red(self) -> StyledObject { + pub const fn red(self) -> StyledObject { self.fg(Color::Red) } #[inline] - pub fn green(self) -> StyledObject { + pub const fn green(self) -> StyledObject { self.fg(Color::Green) } #[inline] - pub fn yellow(self) -> StyledObject { + pub const fn yellow(self) -> StyledObject { self.fg(Color::Yellow) } #[inline] - pub fn blue(self) -> StyledObject { + pub const fn blue(self) -> StyledObject { self.fg(Color::Blue) } #[inline] - pub fn magenta(self) -> StyledObject { + pub const fn magenta(self) -> StyledObject { self.fg(Color::Magenta) } #[inline] - pub fn cyan(self) -> StyledObject { + pub const fn cyan(self) -> StyledObject { self.fg(Color::Cyan) } #[inline] - pub fn white(self) -> StyledObject { + pub const fn white(self) -> StyledObject { self.fg(Color::White) } #[inline] - pub fn color256(self, color: u8) -> StyledObject { + pub const fn color256(self, color: u8) -> StyledObject { self.fg(Color::Color256(color)) } #[inline] - pub fn bright(mut self) -> StyledObject { + pub const fn bright(mut self) -> StyledObject { self.style = self.style.bright(); self } #[inline] - pub fn on_black(self) -> StyledObject { + pub const fn on_black(self) -> StyledObject { self.bg(Color::Black) } #[inline] - pub fn on_red(self) -> StyledObject { + pub const fn on_red(self) -> StyledObject { self.bg(Color::Red) } #[inline] - pub fn on_green(self) -> StyledObject { + pub const fn on_green(self) -> StyledObject { self.bg(Color::Green) } #[inline] - pub fn on_yellow(self) -> StyledObject { + pub const fn on_yellow(self) -> StyledObject { self.bg(Color::Yellow) } #[inline] - pub fn on_blue(self) -> StyledObject { + pub const fn on_blue(self) -> StyledObject { self.bg(Color::Blue) } #[inline] - pub fn on_magenta(self) -> StyledObject { + pub const fn on_magenta(self) -> StyledObject { self.bg(Color::Magenta) } #[inline] - pub fn on_cyan(self) -> StyledObject { + pub const fn on_cyan(self) -> StyledObject { self.bg(Color::Cyan) } #[inline] - pub fn on_white(self) -> StyledObject { + pub const fn on_white(self) -> StyledObject { self.bg(Color::White) } #[inline] - pub fn on_color256(self, color: u8) -> StyledObject { + pub const fn on_color256(self, color: u8) -> StyledObject { self.bg(Color::Color256(color)) } #[inline] - pub fn on_bright(mut self) -> StyledObject { + pub const fn on_bright(mut self) -> StyledObject { self.style = self.style.on_bright(); self } #[inline] - pub fn bold(self) -> StyledObject { + pub const fn bold(self) -> StyledObject { self.attr(Attribute::Bold) } #[inline] - pub fn dim(self) -> StyledObject { + pub const fn dim(self) -> StyledObject { self.attr(Attribute::Dim) } #[inline] - pub fn italic(self) -> StyledObject { + pub const fn italic(self) -> StyledObject { self.attr(Attribute::Italic) } #[inline] - pub fn underlined(self) -> StyledObject { + pub const fn underlined(self) -> StyledObject { self.attr(Attribute::Underlined) } #[inline] - pub fn blink(self) -> StyledObject { + pub const fn blink(self) -> StyledObject { self.attr(Attribute::Blink) } #[inline] - pub fn blink_fast(self) -> StyledObject { + pub const fn blink_fast(self) -> StyledObject { self.attr(Attribute::BlinkFast) } #[inline] - pub fn reverse(self) -> StyledObject { + pub const fn reverse(self) -> StyledObject { self.attr(Attribute::Reverse) } #[inline] - pub fn hidden(self) -> StyledObject { + pub const fn hidden(self) -> StyledObject { self.attr(Attribute::Hidden) } #[inline] - pub fn strikethrough(self) -> StyledObject { + pub const fn strikethrough(self) -> StyledObject { self.attr(Attribute::StrikeThrough) } } @@ -650,8 +703,8 @@ macro_rules! impl_fmt { } reset = true; } - for attr in &self.style.attrs { - write!(f, "\x1b[{}m", attr.ansi_num())?; + for ansi_num in self.style.attrs.ansi_nums() { + write!(f, "\x1b[{}m", ansi_num)?; reset = true; } } @@ -965,3 +1018,55 @@ fn test_pad_str_with() { "foo..." ); } + +#[test] +fn test_attributes_single() { + for attr in Attributes::ATTRIBUTES_LOOKUP { + let attrs = Attributes::new().insert(attr); + assert_eq!(attrs.bits().collect::>(), [attr as u16]); + assert_eq!(attrs.ansi_nums().collect::>(), [attr as u16 + 1]); + assert_eq!(attrs.attrs().collect::>(), [attr]); + assert_eq!(format!("{:?}", attrs), format!("{{{:?}}}", attr)); + } +} + +#[test] +fn test_attributes_many() { + let tests: [&[Attribute]; 3] = [ + &[ + Attribute::Bold, + Attribute::Underlined, + Attribute::BlinkFast, + Attribute::Hidden, + ], + &[ + Attribute::Dim, + Attribute::Italic, + Attribute::Blink, + Attribute::Reverse, + Attribute::StrikeThrough, + ], + &Attributes::ATTRIBUTES_LOOKUP, + ]; + for test_attrs in tests { + let mut attrs = Attributes::new(); + for attr in test_attrs { + attrs = attrs.insert(*attr); + } + assert_eq!( + attrs.bits().collect::>(), + test_attrs + .iter() + .map(|attr| *attr as u16) + .collect::>() + ); + assert_eq!( + attrs.ansi_nums().collect::>(), + test_attrs + .iter() + .map(|attr| *attr as u16 + 1) + .collect::>() + ); + assert_eq!(&attrs.attrs().collect::>(), test_attrs); + } +}