1use std::fmt;
2use thiserror::Error;
3
4#[derive(Clone, Copy, Debug, strum::EnumIter, Eq, Hash, Ord, PartialEq, PartialOrd)]
30#[repr(u16)]
31pub enum Attribute {
32 Bold = 1 << 0,
33 Dim = 1 << 1,
34 Italic = 1 << 2,
35 Underline = 1 << 3,
36 Blink = 1 << 4,
37 Blink2 = 1 << 5,
39 Reverse = 1 << 6,
41 Conceal = 1 << 7,
43 Strike = 1 << 8,
45 Underline2 = 1 << 9,
47 Frame = 1 << 10,
48 Encircle = 1 << 11,
49 Overline = 1 << 12,
50}
51
52impl Attribute {
53 const COUNT: u16 = 13;
54
55 pub fn iter() -> AttributeIter {
57 <Attribute as strum::IntoEnumIterator>::iter()
59 }
60
61 pub fn as_str(self) -> &'static str {
71 match self {
72 Attribute::Bold => "bold",
73 Attribute::Dim => "dim",
74 Attribute::Italic => "italic",
75 Attribute::Underline => "underline",
76 Attribute::Blink => "blink",
77 Attribute::Blink2 => "blink2",
78 Attribute::Reverse => "reverse",
79 Attribute::Conceal => "conceal",
80 Attribute::Strike => "strike",
81 Attribute::Underline2 => "underline2",
82 Attribute::Frame => "frame",
83 Attribute::Encircle => "encircle",
84 Attribute::Overline => "overline",
85 }
86 }
87}
88
89impl fmt::Display for Attribute {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 write!(f, "{}", self.as_str())
92 }
93}
94
95impl std::str::FromStr for Attribute {
96 type Err = ParseAttributeError;
97
98 fn from_str(s: &str) -> Result<Attribute, ParseAttributeError> {
99 match s.to_ascii_lowercase().as_str() {
100 "bold" | "b" => Ok(Attribute::Bold),
101 "dim" | "d" => Ok(Attribute::Dim),
102 "italic" | "i" => Ok(Attribute::Italic),
103 "underline" | "u" => Ok(Attribute::Underline),
104 "blink" => Ok(Attribute::Blink),
105 "blink2" => Ok(Attribute::Blink2),
106 "reverse" | "r" => Ok(Attribute::Reverse),
107 "conceal" | "c" => Ok(Attribute::Conceal),
108 "strike" | "s" => Ok(Attribute::Strike),
109 "underline2" | "uu" => Ok(Attribute::Underline2),
110 "frame" => Ok(Attribute::Frame),
111 "encircle" => Ok(Attribute::Encircle),
112 "overline" => Ok(Attribute::Overline),
113 _ => Err(ParseAttributeError(s.to_owned())),
114 }
115 }
116}
117
118impl<A: Into<AttributeSet>> std::ops::BitAnd<A> for Attribute {
119 type Output = AttributeSet;
120
121 fn bitand(self, rhs: A) -> AttributeSet {
122 AttributeSet((self as u16) & rhs.into().0)
123 }
124}
125
126impl<A: Into<AttributeSet>> std::ops::BitOr<A> for Attribute {
127 type Output = AttributeSet;
128
129 fn bitor(self, rhs: A) -> AttributeSet {
130 AttributeSet((self as u16) | rhs.into().0)
131 }
132}
133
134impl<A: Into<AttributeSet>> std::ops::BitXor<A> for Attribute {
135 type Output = AttributeSet;
136
137 fn bitxor(self, rhs: A) -> AttributeSet {
138 AttributeSet((self as u16) ^ rhs.into().0)
139 }
140}
141
142impl<A: Into<AttributeSet>> std::ops::Sub<A> for Attribute {
143 type Output = AttributeSet;
144
145 fn sub(self, rhs: A) -> AttributeSet {
146 AttributeSet((self as u16) & !rhs.into().0)
147 }
148}
149
150impl std::ops::Not for Attribute {
151 type Output = AttributeSet;
152
153 fn not(self) -> AttributeSet {
154 AttributeSet::ALL - self
155 }
156}
157
158#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
163pub struct AttributeSet(u16);
164
165impl AttributeSet {
166 pub const EMPTY: AttributeSet = AttributeSet(0);
168
169 pub const ALL: AttributeSet = AttributeSet((1 << Attribute::COUNT) - 1);
171
172 pub fn new() -> AttributeSet {
174 AttributeSet(0)
175 }
176
177 pub fn is_empty(self) -> bool {
179 self.0 == 0
180 }
181
182 pub fn is_all(self) -> bool {
184 self == Self::ALL
185 }
186
187 pub fn contains(self, attr: Attribute) -> bool {
189 self.0 & (attr as u16) != 0
190 }
191}
192
193impl From<Attribute> for AttributeSet {
194 fn from(value: Attribute) -> AttributeSet {
195 AttributeSet(value as u16)
196 }
197}
198
199impl IntoIterator for AttributeSet {
200 type Item = Attribute;
201 type IntoIter = AttributeSetIter;
202
203 fn into_iter(self) -> AttributeSetIter {
204 AttributeSetIter::new(self)
205 }
206}
207
208impl FromIterator<Attribute> for AttributeSet {
209 fn from_iter<I: IntoIterator<Item = Attribute>>(iter: I) -> Self {
210 iter.into_iter()
211 .fold(AttributeSet::new(), |set, attr| set | attr)
212 }
213}
214
215impl Extend<Attribute> for AttributeSet {
216 fn extend<I: IntoIterator<Item = Attribute>>(&mut self, iter: I) {
217 for attr in iter {
218 *self |= attr;
219 }
220 }
221}
222
223#[cfg(feature = "anstyle")]
224#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
225impl From<AttributeSet> for anstyle::Effects {
226 fn from(value: AttributeSet) -> anstyle::Effects {
238 let mut efs = anstyle::Effects::new();
239 for attr in value {
240 match attr {
241 Attribute::Bold => efs |= anstyle::Effects::BOLD,
242 Attribute::Dim => efs |= anstyle::Effects::DIMMED,
243 Attribute::Italic => efs |= anstyle::Effects::ITALIC,
244 Attribute::Underline => efs |= anstyle::Effects::UNDERLINE,
245 Attribute::Blink => efs |= anstyle::Effects::BLINK,
246 Attribute::Blink2 => (),
247 Attribute::Reverse => efs |= anstyle::Effects::INVERT,
248 Attribute::Conceal => efs |= anstyle::Effects::HIDDEN,
249 Attribute::Strike => efs |= anstyle::Effects::STRIKETHROUGH,
250 Attribute::Underline2 => efs |= anstyle::Effects::DOUBLE_UNDERLINE,
251 Attribute::Frame => (),
252 Attribute::Encircle => (),
253 Attribute::Overline => (),
254 }
255 }
256 efs
257 }
258}
259
260#[cfg(feature = "anstyle")]
261#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
262impl From<anstyle::Effects> for AttributeSet {
263 fn from(value: anstyle::Effects) -> AttributeSet {
275 let mut set = AttributeSet::new();
276 for eff in value.iter() {
277 match eff {
278 anstyle::Effects::BOLD => set |= Attribute::Bold,
279 anstyle::Effects::DIMMED => set |= Attribute::Dim,
280 anstyle::Effects::ITALIC => set |= Attribute::Italic,
281 anstyle::Effects::UNDERLINE => set |= Attribute::Underline,
282 anstyle::Effects::DOUBLE_UNDERLINE => set |= Attribute::Underline2,
283 anstyle::Effects::CURLY_UNDERLINE => (),
284 anstyle::Effects::DOTTED_UNDERLINE => (),
285 anstyle::Effects::DASHED_UNDERLINE => (),
286 anstyle::Effects::BLINK => set |= Attribute::Blink,
287 anstyle::Effects::INVERT => set |= Attribute::Reverse,
288 anstyle::Effects::HIDDEN => set |= Attribute::Conceal,
289 anstyle::Effects::STRIKETHROUGH => set |= Attribute::Strike,
290 _ => (),
294 }
295 }
296 set
297 }
298}
299
300#[cfg(feature = "crossterm")]
301#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
302impl From<AttributeSet> for crossterm::style::Attributes {
303 fn from(value: AttributeSet) -> crossterm::style::Attributes {
306 use crossterm::style::Attribute as CrossAttrib;
307 let mut attributes = crossterm::style::Attributes::none();
308 for attr in value {
309 let ca = match attr {
310 Attribute::Bold => CrossAttrib::Bold,
311 Attribute::Dim => CrossAttrib::Dim,
312 Attribute::Italic => CrossAttrib::Italic,
313 Attribute::Underline => CrossAttrib::Underlined,
314 Attribute::Blink => CrossAttrib::SlowBlink,
315 Attribute::Blink2 => CrossAttrib::RapidBlink,
316 Attribute::Reverse => CrossAttrib::Reverse,
317 Attribute::Conceal => CrossAttrib::Hidden,
318 Attribute::Strike => CrossAttrib::CrossedOut,
319 Attribute::Underline2 => CrossAttrib::DoubleUnderlined,
320 Attribute::Frame => CrossAttrib::Framed,
321 Attribute::Encircle => CrossAttrib::Encircled,
322 Attribute::Overline => CrossAttrib::OverLined,
323 };
324 attributes.set(ca);
325 }
326 attributes
327 }
328}
329
330#[cfg(feature = "ratatui")]
331#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
332impl From<AttributeSet> for ratatui::style::Modifier {
333 fn from(value: AttributeSet) -> ratatui::style::Modifier {
345 let mut mods = ratatui::style::Modifier::empty();
346 for attr in value {
347 match attr {
348 Attribute::Bold => mods |= ratatui::style::Modifier::BOLD,
349 Attribute::Dim => mods |= ratatui::style::Modifier::DIM,
350 Attribute::Italic => mods |= ratatui::style::Modifier::ITALIC,
351 Attribute::Underline => mods |= ratatui::style::Modifier::UNDERLINED,
352 Attribute::Blink => mods |= ratatui::style::Modifier::SLOW_BLINK,
353 Attribute::Blink2 => mods |= ratatui::style::Modifier::RAPID_BLINK,
354 Attribute::Reverse => mods |= ratatui::style::Modifier::REVERSED,
355 Attribute::Conceal => mods |= ratatui::style::Modifier::HIDDEN,
356 Attribute::Strike => mods |= ratatui::style::Modifier::CROSSED_OUT,
357 Attribute::Underline2 => (),
358 Attribute::Frame => (),
359 Attribute::Encircle => (),
360 Attribute::Overline => (),
361 }
362 }
363 mods
364 }
365}
366
367#[cfg(feature = "ratatui")]
368#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
369impl From<ratatui::style::Modifier> for AttributeSet {
370 fn from(value: ratatui::style::Modifier) -> AttributeSet {
372 let mut set = AttributeSet::new();
373 for m in value.iter() {
374 match m {
375 ratatui::style::Modifier::BOLD => set |= Attribute::Bold,
376 ratatui::style::Modifier::DIM => set |= Attribute::Dim,
377 ratatui::style::Modifier::ITALIC => set |= Attribute::Italic,
378 ratatui::style::Modifier::UNDERLINED => set |= Attribute::Underline,
379 ratatui::style::Modifier::SLOW_BLINK => set |= Attribute::Blink,
380 ratatui::style::Modifier::RAPID_BLINK => set |= Attribute::Blink,
381 ratatui::style::Modifier::REVERSED => set |= Attribute::Reverse,
382 ratatui::style::Modifier::HIDDEN => set |= Attribute::Conceal,
383 ratatui::style::Modifier::CROSSED_OUT => set |= Attribute::Strike,
384 _ => (),
388 }
389 }
390 set
391 }
392}
393
394impl<A: Into<AttributeSet>> std::ops::BitAnd<A> for AttributeSet {
395 type Output = AttributeSet;
396
397 fn bitand(self, rhs: A) -> AttributeSet {
398 AttributeSet(self.0 & rhs.into().0)
399 }
400}
401
402impl<A: Into<AttributeSet>> std::ops::BitAndAssign<A> for AttributeSet {
403 fn bitand_assign(&mut self, rhs: A) {
404 self.0 &= rhs.into().0;
405 }
406}
407
408impl<A: Into<AttributeSet>> std::ops::BitOr<A> for AttributeSet {
409 type Output = AttributeSet;
410
411 fn bitor(self, rhs: A) -> AttributeSet {
412 AttributeSet(self.0 | rhs.into().0)
413 }
414}
415
416impl<A: Into<AttributeSet>> std::ops::BitOrAssign<A> for AttributeSet {
417 fn bitor_assign(&mut self, rhs: A) {
418 self.0 |= rhs.into().0;
419 }
420}
421
422impl<A: Into<AttributeSet>> std::ops::BitXor<A> for AttributeSet {
423 type Output = AttributeSet;
424
425 fn bitxor(self, rhs: A) -> AttributeSet {
426 AttributeSet(self.0 ^ rhs.into().0)
427 }
428}
429
430impl<A: Into<AttributeSet>> std::ops::BitXorAssign<A> for AttributeSet {
431 fn bitxor_assign(&mut self, rhs: A) {
432 self.0 ^= rhs.into().0;
433 }
434}
435
436impl<A: Into<AttributeSet>> std::ops::Sub<A> for AttributeSet {
437 type Output = AttributeSet;
438
439 fn sub(self, rhs: A) -> AttributeSet {
440 AttributeSet(self.0 & !rhs.into().0)
441 }
442}
443
444impl<A: Into<AttributeSet>> std::ops::SubAssign<A> for AttributeSet {
445 fn sub_assign(&mut self, rhs: A) {
446 self.0 &= !rhs.into().0;
447 }
448}
449
450impl std::ops::Not for AttributeSet {
451 type Output = AttributeSet;
452
453 fn not(self) -> AttributeSet {
454 AttributeSet(!self.0 & ((1 << Attribute::COUNT) - 1))
455 }
456}
457
458#[derive(Clone, Debug)]
460pub struct AttributeSetIter {
461 inner: AttributeIter,
462 set: AttributeSet,
463}
464
465impl AttributeSetIter {
466 fn new(set: AttributeSet) -> AttributeSetIter {
467 AttributeSetIter {
468 inner: Attribute::iter(),
469 set,
470 }
471 }
472}
473
474impl Iterator for AttributeSetIter {
475 type Item = Attribute;
476
477 fn next(&mut self) -> Option<Attribute> {
478 self.inner.by_ref().find(|&attr| self.set.contains(attr))
479 }
480
481 fn size_hint(&self) -> (usize, Option<usize>) {
482 (0, self.inner.size_hint().1)
483 }
484}
485
486impl DoubleEndedIterator for AttributeSetIter {
487 fn next_back(&mut self) -> Option<Attribute> {
488 self.inner.by_ref().rfind(|&attr| self.set.contains(attr))
489 }
490}
491
492impl std::iter::FusedIterator for AttributeSetIter {}
493
494#[derive(Clone, Debug, Eq, Error, PartialEq)]
496#[error("invalid attribute name: {0:?}")]
497pub struct ParseAttributeError(
498 pub String,
500);
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505
506 #[test]
507 fn double_ended_iteration() {
508 let attrs = Attribute::Bold | Attribute::Frame | Attribute::Reverse | Attribute::Strike;
509 let mut iter = attrs.into_iter();
510 assert_eq!(iter.next(), Some(Attribute::Bold));
511 assert_eq!(iter.next_back(), Some(Attribute::Frame));
512 assert_eq!(iter.next(), Some(Attribute::Reverse));
513 assert_eq!(iter.next_back(), Some(Attribute::Strike));
514 assert_eq!(iter.next(), None);
515 assert_eq!(iter.next_back(), None);
516 assert_eq!(iter.next(), None);
517 assert_eq!(iter.next_back(), None);
518 }
519}