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 len(self) -> usize {
184 let qty = self.0.count_ones();
185 match usize::try_from(qty) {
186 Ok(sz) => sz,
187 Err(_) => unreachable!("The number of bits in a u16 should fit in a usize"),
188 }
189 }
190
191 pub fn is_all(self) -> bool {
193 self == Self::ALL
194 }
195
196 pub fn contains(self, attr: Attribute) -> bool {
198 self.0 & (attr as u16) != 0
199 }
200
201 pub fn insert(&mut self, attr: Attribute) -> bool {
218 let attr = attr as u16;
219 let adding = (self.0 & attr) == 0;
220 self.0 |= attr;
221 adding
222 }
223
224 pub fn remove(&mut self, attr: Attribute) -> bool {
241 let attr = attr as u16;
242 let present = (self.0 & attr) != 0;
243 self.0 &= !attr;
244 present
245 }
246
247 pub fn clear(&mut self) {
249 *self = Self::default();
250 }
251
252 pub fn is_disjoint(self, other: AttributeSet) -> bool {
279 self.0 & other.0 == 0
280 }
281
282 pub fn is_subset(self, other: AttributeSet) -> bool {
307 self.0 & other.0 == self.0
308 }
309
310 pub fn is_superset(self, other: AttributeSet) -> bool {
335 self.0 & other.0 == other.0
336 }
337}
338
339impl From<Attribute> for AttributeSet {
340 fn from(value: Attribute) -> AttributeSet {
341 AttributeSet(value as u16)
342 }
343}
344
345impl<const N: usize> From<[Attribute; N]> for AttributeSet {
346 fn from(value: [Attribute; N]) -> AttributeSet {
347 AttributeSet::from_iter(value)
348 }
349}
350
351impl IntoIterator for AttributeSet {
352 type Item = Attribute;
353 type IntoIter = AttributeSetIter;
354
355 fn into_iter(self) -> AttributeSetIter {
356 AttributeSetIter::new(self)
357 }
358}
359
360impl FromIterator<Attribute> for AttributeSet {
361 fn from_iter<I: IntoIterator<Item = Attribute>>(iter: I) -> Self {
362 iter.into_iter()
363 .fold(AttributeSet::new(), |set, attr| set | attr)
364 }
365}
366
367impl Extend<Attribute> for AttributeSet {
368 fn extend<I: IntoIterator<Item = Attribute>>(&mut self, iter: I) {
369 for attr in iter {
370 *self |= attr;
371 }
372 }
373}
374
375#[cfg(feature = "anstyle")]
376#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
377impl From<AttributeSet> for anstyle::Effects {
378 fn from(value: AttributeSet) -> anstyle::Effects {
390 let mut efs = anstyle::Effects::new();
391 for attr in value {
392 match attr {
393 Attribute::Bold => efs |= anstyle::Effects::BOLD,
394 Attribute::Dim => efs |= anstyle::Effects::DIMMED,
395 Attribute::Italic => efs |= anstyle::Effects::ITALIC,
396 Attribute::Underline => efs |= anstyle::Effects::UNDERLINE,
397 Attribute::Blink => efs |= anstyle::Effects::BLINK,
398 Attribute::Blink2 => (),
399 Attribute::Reverse => efs |= anstyle::Effects::INVERT,
400 Attribute::Conceal => efs |= anstyle::Effects::HIDDEN,
401 Attribute::Strike => efs |= anstyle::Effects::STRIKETHROUGH,
402 Attribute::Underline2 => efs |= anstyle::Effects::DOUBLE_UNDERLINE,
403 Attribute::Frame => (),
404 Attribute::Encircle => (),
405 Attribute::Overline => (),
406 }
407 }
408 efs
409 }
410}
411
412#[cfg(feature = "anstyle")]
413#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
414impl From<anstyle::Effects> for AttributeSet {
415 fn from(value: anstyle::Effects) -> AttributeSet {
427 let mut set = AttributeSet::new();
428 for eff in value.iter() {
429 match eff {
430 anstyle::Effects::BOLD => set |= Attribute::Bold,
431 anstyle::Effects::DIMMED => set |= Attribute::Dim,
432 anstyle::Effects::ITALIC => set |= Attribute::Italic,
433 anstyle::Effects::UNDERLINE => set |= Attribute::Underline,
434 anstyle::Effects::DOUBLE_UNDERLINE => set |= Attribute::Underline2,
435 anstyle::Effects::CURLY_UNDERLINE => (),
436 anstyle::Effects::DOTTED_UNDERLINE => (),
437 anstyle::Effects::DASHED_UNDERLINE => (),
438 anstyle::Effects::BLINK => set |= Attribute::Blink,
439 anstyle::Effects::INVERT => set |= Attribute::Reverse,
440 anstyle::Effects::HIDDEN => set |= Attribute::Conceal,
441 anstyle::Effects::STRIKETHROUGH => set |= Attribute::Strike,
442 _ => (),
446 }
447 }
448 set
449 }
450}
451
452#[cfg(feature = "crossterm")]
453#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
454impl From<AttributeSet> for crossterm::style::Attributes {
455 fn from(value: AttributeSet) -> crossterm::style::Attributes {
458 use crossterm::style::Attribute as CrossAttrib;
459 let mut attributes = crossterm::style::Attributes::none();
460 for attr in value {
461 let ca = match attr {
462 Attribute::Bold => CrossAttrib::Bold,
463 Attribute::Dim => CrossAttrib::Dim,
464 Attribute::Italic => CrossAttrib::Italic,
465 Attribute::Underline => CrossAttrib::Underlined,
466 Attribute::Blink => CrossAttrib::SlowBlink,
467 Attribute::Blink2 => CrossAttrib::RapidBlink,
468 Attribute::Reverse => CrossAttrib::Reverse,
469 Attribute::Conceal => CrossAttrib::Hidden,
470 Attribute::Strike => CrossAttrib::CrossedOut,
471 Attribute::Underline2 => CrossAttrib::DoubleUnderlined,
472 Attribute::Frame => CrossAttrib::Framed,
473 Attribute::Encircle => CrossAttrib::Encircled,
474 Attribute::Overline => CrossAttrib::OverLined,
475 };
476 attributes.set(ca);
477 }
478 attributes
479 }
480}
481
482#[cfg(feature = "ratatui")]
483#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
484impl From<AttributeSet> for ratatui_core::style::Modifier {
485 fn from(value: AttributeSet) -> ratatui_core::style::Modifier {
497 let mut mods = ratatui_core::style::Modifier::empty();
498 for attr in value {
499 match attr {
500 Attribute::Bold => mods |= ratatui_core::style::Modifier::BOLD,
501 Attribute::Dim => mods |= ratatui_core::style::Modifier::DIM,
502 Attribute::Italic => mods |= ratatui_core::style::Modifier::ITALIC,
503 Attribute::Underline => mods |= ratatui_core::style::Modifier::UNDERLINED,
504 Attribute::Blink => mods |= ratatui_core::style::Modifier::SLOW_BLINK,
505 Attribute::Blink2 => mods |= ratatui_core::style::Modifier::RAPID_BLINK,
506 Attribute::Reverse => mods |= ratatui_core::style::Modifier::REVERSED,
507 Attribute::Conceal => mods |= ratatui_core::style::Modifier::HIDDEN,
508 Attribute::Strike => mods |= ratatui_core::style::Modifier::CROSSED_OUT,
509 Attribute::Underline2 => (),
510 Attribute::Frame => (),
511 Attribute::Encircle => (),
512 Attribute::Overline => (),
513 }
514 }
515 mods
516 }
517}
518
519#[cfg(feature = "ratatui")]
520#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
521impl From<ratatui_core::style::Modifier> for AttributeSet {
522 fn from(value: ratatui_core::style::Modifier) -> AttributeSet {
524 let mut set = AttributeSet::new();
525 for m in value.iter() {
526 match m {
527 ratatui_core::style::Modifier::BOLD => set |= Attribute::Bold,
528 ratatui_core::style::Modifier::DIM => set |= Attribute::Dim,
529 ratatui_core::style::Modifier::ITALIC => set |= Attribute::Italic,
530 ratatui_core::style::Modifier::UNDERLINED => set |= Attribute::Underline,
531 ratatui_core::style::Modifier::SLOW_BLINK => set |= Attribute::Blink,
532 ratatui_core::style::Modifier::RAPID_BLINK => set |= Attribute::Blink,
533 ratatui_core::style::Modifier::REVERSED => set |= Attribute::Reverse,
534 ratatui_core::style::Modifier::HIDDEN => set |= Attribute::Conceal,
535 ratatui_core::style::Modifier::CROSSED_OUT => set |= Attribute::Strike,
536 _ => (),
540 }
541 }
542 set
543 }
544}
545
546impl<A: Into<AttributeSet>> std::ops::BitAnd<A> for AttributeSet {
547 type Output = AttributeSet;
548
549 fn bitand(self, rhs: A) -> AttributeSet {
550 AttributeSet(self.0 & rhs.into().0)
551 }
552}
553
554impl<A: Into<AttributeSet>> std::ops::BitAndAssign<A> for AttributeSet {
555 fn bitand_assign(&mut self, rhs: A) {
556 self.0 &= rhs.into().0;
557 }
558}
559
560impl<A: Into<AttributeSet>> std::ops::BitOr<A> for AttributeSet {
561 type Output = AttributeSet;
562
563 fn bitor(self, rhs: A) -> AttributeSet {
564 AttributeSet(self.0 | rhs.into().0)
565 }
566}
567
568impl<A: Into<AttributeSet>> std::ops::BitOrAssign<A> for AttributeSet {
569 fn bitor_assign(&mut self, rhs: A) {
570 self.0 |= rhs.into().0;
571 }
572}
573
574impl<A: Into<AttributeSet>> std::ops::BitXor<A> for AttributeSet {
575 type Output = AttributeSet;
576
577 fn bitxor(self, rhs: A) -> AttributeSet {
578 AttributeSet(self.0 ^ rhs.into().0)
579 }
580}
581
582impl<A: Into<AttributeSet>> std::ops::BitXorAssign<A> for AttributeSet {
583 fn bitxor_assign(&mut self, rhs: A) {
584 self.0 ^= rhs.into().0;
585 }
586}
587
588impl<A: Into<AttributeSet>> std::ops::Sub<A> for AttributeSet {
589 type Output = AttributeSet;
590
591 fn sub(self, rhs: A) -> AttributeSet {
592 AttributeSet(self.0 & !rhs.into().0)
593 }
594}
595
596impl<A: Into<AttributeSet>> std::ops::SubAssign<A> for AttributeSet {
597 fn sub_assign(&mut self, rhs: A) {
598 self.0 &= !rhs.into().0;
599 }
600}
601
602impl std::ops::Not for AttributeSet {
603 type Output = AttributeSet;
604
605 fn not(self) -> AttributeSet {
606 AttributeSet(!self.0 & ((1 << Attribute::COUNT) - 1))
607 }
608}
609
610#[derive(Clone, Debug)]
612pub struct AttributeSetIter {
613 inner: AttributeIter,
614 set: AttributeSet,
615}
616
617impl AttributeSetIter {
618 fn new(set: AttributeSet) -> AttributeSetIter {
619 AttributeSetIter {
620 inner: Attribute::iter(),
621 set,
622 }
623 }
624}
625
626impl Iterator for AttributeSetIter {
627 type Item = Attribute;
628
629 fn next(&mut self) -> Option<Attribute> {
630 self.inner.by_ref().find(|&attr| self.set.contains(attr))
631 }
632
633 fn size_hint(&self) -> (usize, Option<usize>) {
634 (0, self.inner.size_hint().1)
635 }
636}
637
638impl DoubleEndedIterator for AttributeSetIter {
639 fn next_back(&mut self) -> Option<Attribute> {
640 self.inner.by_ref().rfind(|&attr| self.set.contains(attr))
641 }
642}
643
644impl std::iter::FusedIterator for AttributeSetIter {}
645
646#[derive(Clone, Debug, Eq, Error, PartialEq)]
648#[error("invalid attribute name: {0:?}")]
649pub struct ParseAttributeError(
650 pub String,
652);
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657
658 #[test]
659 fn double_ended_iteration() {
660 let attrs = Attribute::Bold | Attribute::Frame | Attribute::Reverse | Attribute::Strike;
661 let mut iter = attrs.into_iter();
662 assert_eq!(iter.next(), Some(Attribute::Bold));
663 assert_eq!(iter.next_back(), Some(Attribute::Frame));
664 assert_eq!(iter.next(), Some(Attribute::Reverse));
665 assert_eq!(iter.next_back(), Some(Attribute::Strike));
666 assert_eq!(iter.next(), None);
667 assert_eq!(iter.next_back(), None);
668 assert_eq!(iter.next(), None);
669 assert_eq!(iter.next_back(), None);
670 }
671}