Skip to content

Commit bec75b6

Browse files
jwieslerdjc
authored andcommitted
fix: Store set of attributes more compact
1 parent c7002e3 commit bec75b6

File tree

1 file changed

+132
-27
lines changed

1 file changed

+132
-27
lines changed

src/utils.rs

Lines changed: 132 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::borrow::Cow;
2-
use std::collections::BTreeSet;
32
use std::env;
43
use std::fmt;
4+
use std::fmt::{Debug, Formatter};
55
use std::sync::atomic::{AtomicBool, Ordering};
66

77
use once_cell::sync::Lazy;
@@ -116,32 +116,85 @@ impl Color {
116116

117117
/// A terminal style attribute.
118118
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
119+
#[repr(u16)]
119120
pub enum Attribute {
120-
Bold,
121-
Dim,
122-
Italic,
123-
Underlined,
124-
Blink,
125-
BlinkFast,
126-
Reverse,
127-
Hidden,
128-
StrikeThrough,
121+
// This mapping is important, it exactly matches ansi_num = (x as u16 + 1)
122+
// See `ATTRIBUTES_LOOKUP` as well
123+
Bold = 0,
124+
Dim = 1,
125+
Italic = 2,
126+
Underlined = 3,
127+
Blink = 4,
128+
BlinkFast = 5,
129+
Reverse = 6,
130+
Hidden = 7,
131+
StrikeThrough = 8,
129132
}
130133

131-
impl Attribute {
134+
#[derive(Clone, Copy, PartialEq, Eq)]
135+
struct Attributes(u16);
136+
137+
impl Attributes {
138+
const ATTRIBUTES_LOOKUP: [Attribute; 9] = [
139+
Attribute::Bold,
140+
Attribute::Dim,
141+
Attribute::Italic,
142+
Attribute::Underlined,
143+
Attribute::Blink,
144+
Attribute::BlinkFast,
145+
Attribute::Reverse,
146+
Attribute::Hidden,
147+
Attribute::StrikeThrough,
148+
];
149+
132150
#[inline]
133-
fn ansi_num(self) -> usize {
134-
match self {
135-
Attribute::Bold => 1,
136-
Attribute::Dim => 2,
137-
Attribute::Italic => 3,
138-
Attribute::Underlined => 4,
139-
Attribute::Blink => 5,
140-
Attribute::BlinkFast => 6,
141-
Attribute::Reverse => 7,
142-
Attribute::Hidden => 8,
143-
Attribute::StrikeThrough => 9,
151+
const fn new() -> Self {
152+
Attributes(0)
153+
}
154+
155+
#[inline]
156+
#[must_use]
157+
const fn insert(mut self, attr: Attribute) -> Self {
158+
let bit = attr as u16;
159+
self.0 |= 1 << bit;
160+
self
161+
}
162+
163+
#[inline]
164+
const fn bits(self) -> BitsIter {
165+
BitsIter(self.0)
166+
}
167+
168+
#[inline]
169+
fn ansi_nums(self) -> impl Iterator<Item = u16> {
170+
// Per construction of the enum
171+
self.bits().map(|bit| bit + 1)
172+
}
173+
174+
#[inline]
175+
fn attrs(self) -> impl Iterator<Item = Attribute> {
176+
self.bits().map(|bit| Self::ATTRIBUTES_LOOKUP[bit as usize])
177+
}
178+
}
179+
180+
struct BitsIter(u16);
181+
182+
impl Iterator for BitsIter {
183+
type Item = u16;
184+
185+
fn next(&mut self) -> Option<Self::Item> {
186+
if self.0 == 0 {
187+
return None;
144188
}
189+
let bit = self.0.trailing_zeros();
190+
self.0 ^= (1 << bit) as u16;
191+
Some(bit as u16)
192+
}
193+
}
194+
195+
impl Debug for Attributes {
196+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
197+
f.debug_set().entries(self.attrs()).finish()
145198
}
146199
}
147200

@@ -160,7 +213,7 @@ pub struct Style {
160213
bg: Option<Color>,
161214
fg_bright: bool,
162215
bg_bright: bool,
163-
attrs: BTreeSet<Attribute>,
216+
attrs: Attributes,
164217
force: Option<bool>,
165218
for_stderr: bool,
166219
}
@@ -179,7 +232,7 @@ impl Style {
179232
bg: None,
180233
fg_bright: false,
181234
bg_bright: false,
182-
attrs: BTreeSet::new(),
235+
attrs: Attributes::new(),
183236
force: None,
184237
for_stderr: false,
185238
}
@@ -291,7 +344,7 @@ impl Style {
291344
/// Adds a attr.
292345
#[inline]
293346
pub fn attr(mut self, attr: Attribute) -> Self {
294-
self.attrs.insert(attr);
347+
self.attrs = self.attrs.insert(attr);
295348
self
296349
}
297350

@@ -650,8 +703,8 @@ macro_rules! impl_fmt {
650703
}
651704
reset = true;
652705
}
653-
for attr in &self.style.attrs {
654-
write!(f, "\x1b[{}m", attr.ansi_num())?;
706+
for ansi_num in self.style.attrs.ansi_nums() {
707+
write!(f, "\x1b[{}m", ansi_num)?;
655708
reset = true;
656709
}
657710
}
@@ -965,3 +1018,55 @@ fn test_pad_str_with() {
9651018
"foo..."
9661019
);
9671020
}
1021+
1022+
#[test]
1023+
fn test_attributes_single() {
1024+
for attr in Attributes::ATTRIBUTES_LOOKUP {
1025+
let attrs = Attributes::new().insert(attr);
1026+
assert_eq!(attrs.bits().collect::<Vec<_>>(), [attr as u16]);
1027+
assert_eq!(attrs.ansi_nums().collect::<Vec<_>>(), [attr as u16 + 1]);
1028+
assert_eq!(attrs.attrs().collect::<Vec<_>>(), [attr]);
1029+
assert_eq!(format!("{:?}", attrs), format!("{{{:?}}}", attr));
1030+
}
1031+
}
1032+
1033+
#[test]
1034+
fn test_attributes_many() {
1035+
let tests: [&[Attribute]; 3] = [
1036+
&[
1037+
Attribute::Bold,
1038+
Attribute::Underlined,
1039+
Attribute::BlinkFast,
1040+
Attribute::Hidden,
1041+
],
1042+
&[
1043+
Attribute::Dim,
1044+
Attribute::Italic,
1045+
Attribute::Blink,
1046+
Attribute::Reverse,
1047+
Attribute::StrikeThrough,
1048+
],
1049+
&Attributes::ATTRIBUTES_LOOKUP,
1050+
];
1051+
for test_attrs in tests {
1052+
let mut attrs = Attributes::new();
1053+
for attr in test_attrs {
1054+
attrs = attrs.insert(*attr);
1055+
}
1056+
assert_eq!(
1057+
attrs.bits().collect::<Vec<_>>(),
1058+
test_attrs
1059+
.iter()
1060+
.map(|attr| *attr as u16)
1061+
.collect::<Vec<_>>()
1062+
);
1063+
assert_eq!(
1064+
attrs.ansi_nums().collect::<Vec<_>>(),
1065+
test_attrs
1066+
.iter()
1067+
.map(|attr| *attr as u16 + 1)
1068+
.collect::<Vec<_>>()
1069+
);
1070+
assert_eq!(&attrs.attrs().collect::<Vec<_>>(), test_attrs);
1071+
}
1072+
}

0 commit comments

Comments
 (0)