1use std::fmt;
2use thiserror::Error;
3
4#[derive(Clone, Copy, Debug, strum::EnumIter, Eq, Hash, Ord, PartialEq, PartialOrd)]
31#[repr(u16)]
32pub enum Attribute {
33 Bold = 1 << 0,
34 Dim = 1 << 1,
35 Italic = 1 << 2,
36 Underline = 1 << 3,
37 Blink = 1 << 4,
38 Blink2 = 1 << 5,
40 Reverse = 1 << 6,
42 Conceal = 1 << 7,
44 Strike = 1 << 8,
46 Underline2 = 1 << 9,
48 Frame = 1 << 10,
49 Encircle = 1 << 11,
50 Overline = 1 << 12,
51}
52
53impl Attribute {
54 const COUNT: u16 = 13;
55
56 pub fn iter() -> AttributeIter {
58 <Attribute as strum::IntoEnumIterator>::iter()
60 }
61
62 pub fn as_str(self) -> &'static str {
72 match self {
73 Attribute::Bold => "bold",
74 Attribute::Dim => "dim",
75 Attribute::Italic => "italic",
76 Attribute::Underline => "underline",
77 Attribute::Blink => "blink",
78 Attribute::Blink2 => "blink2",
79 Attribute::Reverse => "reverse",
80 Attribute::Conceal => "conceal",
81 Attribute::Strike => "strike",
82 Attribute::Underline2 => "underline2",
83 Attribute::Frame => "frame",
84 Attribute::Encircle => "encircle",
85 Attribute::Overline => "overline",
86 }
87 }
88
89 pub fn as_short_str(self) -> &'static str {
101 match self {
102 Attribute::Bold => "b",
103 Attribute::Dim => "d",
104 Attribute::Italic => "i",
105 Attribute::Underline => "u",
106 Attribute::Blink => "blink",
107 Attribute::Blink2 => "blink2",
108 Attribute::Reverse => "r",
109 Attribute::Conceal => "c",
110 Attribute::Strike => "s",
111 Attribute::Underline2 => "uu",
112 Attribute::Frame => "frame",
113 Attribute::Encircle => "encircle",
114 Attribute::Overline => "overline",
115 }
116 }
117}
118
119impl fmt::Display for Attribute {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 if f.alternate() {
122 f.write_str(self.as_short_str())
123 } else {
124 f.write_str(self.as_str())
125 }
126 }
127}
128
129impl std::str::FromStr for Attribute {
130 type Err = ParseAttributeError;
131
132 fn from_str(s: &str) -> Result<Attribute, ParseAttributeError> {
133 match s.to_ascii_lowercase().as_str() {
134 "bold" | "b" => Ok(Attribute::Bold),
135 "dim" | "d" => Ok(Attribute::Dim),
136 "italic" | "i" => Ok(Attribute::Italic),
137 "underline" | "u" => Ok(Attribute::Underline),
138 "blink" => Ok(Attribute::Blink),
139 "blink2" => Ok(Attribute::Blink2),
140 "reverse" | "r" => Ok(Attribute::Reverse),
141 "conceal" | "c" => Ok(Attribute::Conceal),
142 "strike" | "s" => Ok(Attribute::Strike),
143 "underline2" | "uu" => Ok(Attribute::Underline2),
144 "frame" => Ok(Attribute::Frame),
145 "encircle" => Ok(Attribute::Encircle),
146 "overline" => Ok(Attribute::Overline),
147 _ => Err(ParseAttributeError(s.to_owned())),
148 }
149 }
150}
151
152impl<A: Into<AttributeSet>> std::ops::BitAnd<A> for Attribute {
153 type Output = AttributeSet;
154
155 fn bitand(self, rhs: A) -> AttributeSet {
156 AttributeSet((self as u16) & rhs.into().0)
157 }
158}
159
160impl<A: Into<AttributeSet>> std::ops::BitOr<A> for Attribute {
161 type Output = AttributeSet;
162
163 fn bitor(self, rhs: A) -> AttributeSet {
164 AttributeSet((self as u16) | rhs.into().0)
165 }
166}
167
168impl<A: Into<AttributeSet>> std::ops::BitXor<A> for Attribute {
169 type Output = AttributeSet;
170
171 fn bitxor(self, rhs: A) -> AttributeSet {
172 AttributeSet((self as u16) ^ rhs.into().0)
173 }
174}
175
176impl<A: Into<AttributeSet>> std::ops::Sub<A> for Attribute {
177 type Output = AttributeSet;
178
179 fn sub(self, rhs: A) -> AttributeSet {
180 AttributeSet((self as u16) & !rhs.into().0)
181 }
182}
183
184impl std::ops::Not for Attribute {
185 type Output = AttributeSet;
186
187 fn not(self) -> AttributeSet {
188 AttributeSet::ALL - self
189 }
190}
191
192#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
197pub struct AttributeSet(u16);
198
199impl AttributeSet {
200 pub const EMPTY: AttributeSet = AttributeSet(0);
202
203 pub const ALL: AttributeSet = AttributeSet((1 << Attribute::COUNT) - 1);
205
206 pub fn new() -> AttributeSet {
208 AttributeSet(0)
209 }
210
211 pub fn is_empty(self) -> bool {
213 self.0 == 0
214 }
215
216 pub fn len(self) -> usize {
218 let qty = self.0.count_ones();
219 match usize::try_from(qty) {
220 Ok(sz) => sz,
221 Err(_) => unreachable!("The number of bits in a u16 should fit in a usize"),
222 }
223 }
224
225 pub fn is_all(self) -> bool {
227 self == Self::ALL
228 }
229
230 pub fn contains(self, attr: Attribute) -> bool {
232 self.0 & (attr as u16) != 0
233 }
234
235 pub fn insert(&mut self, attr: Attribute) -> bool {
252 let attr = attr as u16;
253 let adding = (self.0 & attr) == 0;
254 self.0 |= attr;
255 adding
256 }
257
258 pub fn remove(&mut self, attr: Attribute) -> bool {
275 let attr = attr as u16;
276 let present = (self.0 & attr) != 0;
277 self.0 &= !attr;
278 present
279 }
280
281 pub fn clear(&mut self) {
283 *self = Self::default();
284 }
285
286 pub fn is_disjoint(self, other: AttributeSet) -> bool {
313 self.0 & other.0 == 0
314 }
315
316 pub fn is_subset(self, other: AttributeSet) -> bool {
341 self.0 & other.0 == self.0
342 }
343
344 pub fn is_superset(self, other: AttributeSet) -> bool {
369 self.0 & other.0 == other.0
370 }
371}
372
373impl From<Attribute> for AttributeSet {
374 fn from(value: Attribute) -> AttributeSet {
375 AttributeSet(value as u16)
376 }
377}
378
379impl<const N: usize> From<[Attribute; N]> for AttributeSet {
380 fn from(value: [Attribute; N]) -> AttributeSet {
381 AttributeSet::from_iter(value)
382 }
383}
384
385impl IntoIterator for AttributeSet {
386 type Item = Attribute;
387 type IntoIter = AttributeSetIter;
388
389 fn into_iter(self) -> AttributeSetIter {
390 AttributeSetIter::new(self)
391 }
392}
393
394impl FromIterator<Attribute> for AttributeSet {
395 fn from_iter<I: IntoIterator<Item = Attribute>>(iter: I) -> Self {
396 iter.into_iter()
397 .fold(AttributeSet::new(), |set, attr| set | attr)
398 }
399}
400
401impl Extend<Attribute> for AttributeSet {
402 fn extend<I: IntoIterator<Item = Attribute>>(&mut self, iter: I) {
403 for attr in iter {
404 *self |= attr;
405 }
406 }
407}
408
409#[cfg(feature = "anstyle")]
410#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
411impl From<AttributeSet> for anstyle::Effects {
412 fn from(value: AttributeSet) -> anstyle::Effects {
424 let mut efs = anstyle::Effects::new();
425 for attr in value {
426 match attr {
427 Attribute::Bold => efs |= anstyle::Effects::BOLD,
428 Attribute::Dim => efs |= anstyle::Effects::DIMMED,
429 Attribute::Italic => efs |= anstyle::Effects::ITALIC,
430 Attribute::Underline => efs |= anstyle::Effects::UNDERLINE,
431 Attribute::Blink => efs |= anstyle::Effects::BLINK,
432 Attribute::Blink2 => (),
433 Attribute::Reverse => efs |= anstyle::Effects::INVERT,
434 Attribute::Conceal => efs |= anstyle::Effects::HIDDEN,
435 Attribute::Strike => efs |= anstyle::Effects::STRIKETHROUGH,
436 Attribute::Underline2 => efs |= anstyle::Effects::DOUBLE_UNDERLINE,
437 Attribute::Frame => (),
438 Attribute::Encircle => (),
439 Attribute::Overline => (),
440 }
441 }
442 efs
443 }
444}
445
446#[cfg(feature = "anstyle")]
447#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
448impl From<anstyle::Effects> for AttributeSet {
449 fn from(value: anstyle::Effects) -> AttributeSet {
461 let mut set = AttributeSet::new();
462 for eff in value.iter() {
463 match eff {
464 anstyle::Effects::BOLD => set |= Attribute::Bold,
465 anstyle::Effects::DIMMED => set |= Attribute::Dim,
466 anstyle::Effects::ITALIC => set |= Attribute::Italic,
467 anstyle::Effects::UNDERLINE => set |= Attribute::Underline,
468 anstyle::Effects::DOUBLE_UNDERLINE => set |= Attribute::Underline2,
469 anstyle::Effects::CURLY_UNDERLINE => (),
470 anstyle::Effects::DOTTED_UNDERLINE => (),
471 anstyle::Effects::DASHED_UNDERLINE => (),
472 anstyle::Effects::BLINK => set |= Attribute::Blink,
473 anstyle::Effects::INVERT => set |= Attribute::Reverse,
474 anstyle::Effects::HIDDEN => set |= Attribute::Conceal,
475 anstyle::Effects::STRIKETHROUGH => set |= Attribute::Strike,
476 _ => (),
480 }
481 }
482 set
483 }
484}
485
486#[cfg(feature = "crossterm")]
487#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
488impl From<AttributeSet> for crossterm::style::Attributes {
489 fn from(value: AttributeSet) -> crossterm::style::Attributes {
492 use crossterm::style::Attribute as CrossAttrib;
493 let mut attributes = crossterm::style::Attributes::none();
494 for attr in value {
495 let ca = match attr {
496 Attribute::Bold => CrossAttrib::Bold,
497 Attribute::Dim => CrossAttrib::Dim,
498 Attribute::Italic => CrossAttrib::Italic,
499 Attribute::Underline => CrossAttrib::Underlined,
500 Attribute::Blink => CrossAttrib::SlowBlink,
501 Attribute::Blink2 => CrossAttrib::RapidBlink,
502 Attribute::Reverse => CrossAttrib::Reverse,
503 Attribute::Conceal => CrossAttrib::Hidden,
504 Attribute::Strike => CrossAttrib::CrossedOut,
505 Attribute::Underline2 => CrossAttrib::DoubleUnderlined,
506 Attribute::Frame => CrossAttrib::Framed,
507 Attribute::Encircle => CrossAttrib::Encircled,
508 Attribute::Overline => CrossAttrib::OverLined,
509 };
510 attributes.set(ca);
511 }
512 attributes
513 }
514}
515
516#[cfg(feature = "ratatui")]
517#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
518impl From<AttributeSet> for ratatui_core::style::Modifier {
519 fn from(value: AttributeSet) -> ratatui_core::style::Modifier {
531 let mut mods = ratatui_core::style::Modifier::empty();
532 for attr in value {
533 match attr {
534 Attribute::Bold => mods |= ratatui_core::style::Modifier::BOLD,
535 Attribute::Dim => mods |= ratatui_core::style::Modifier::DIM,
536 Attribute::Italic => mods |= ratatui_core::style::Modifier::ITALIC,
537 Attribute::Underline => mods |= ratatui_core::style::Modifier::UNDERLINED,
538 Attribute::Blink => mods |= ratatui_core::style::Modifier::SLOW_BLINK,
539 Attribute::Blink2 => mods |= ratatui_core::style::Modifier::RAPID_BLINK,
540 Attribute::Reverse => mods |= ratatui_core::style::Modifier::REVERSED,
541 Attribute::Conceal => mods |= ratatui_core::style::Modifier::HIDDEN,
542 Attribute::Strike => mods |= ratatui_core::style::Modifier::CROSSED_OUT,
543 Attribute::Underline2 => (),
544 Attribute::Frame => (),
545 Attribute::Encircle => (),
546 Attribute::Overline => (),
547 }
548 }
549 mods
550 }
551}
552
553#[cfg(feature = "ratatui")]
554#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
555impl From<ratatui_core::style::Modifier> for AttributeSet {
556 fn from(value: ratatui_core::style::Modifier) -> AttributeSet {
558 let mut set = AttributeSet::new();
559 for m in value.iter() {
560 match m {
561 ratatui_core::style::Modifier::BOLD => set |= Attribute::Bold,
562 ratatui_core::style::Modifier::DIM => set |= Attribute::Dim,
563 ratatui_core::style::Modifier::ITALIC => set |= Attribute::Italic,
564 ratatui_core::style::Modifier::UNDERLINED => set |= Attribute::Underline,
565 ratatui_core::style::Modifier::SLOW_BLINK => set |= Attribute::Blink,
566 ratatui_core::style::Modifier::RAPID_BLINK => set |= Attribute::Blink,
567 ratatui_core::style::Modifier::REVERSED => set |= Attribute::Reverse,
568 ratatui_core::style::Modifier::HIDDEN => set |= Attribute::Conceal,
569 ratatui_core::style::Modifier::CROSSED_OUT => set |= Attribute::Strike,
570 _ => (),
574 }
575 }
576 set
577 }
578}
579
580impl<A: Into<AttributeSet>> std::ops::BitAnd<A> for AttributeSet {
581 type Output = AttributeSet;
582
583 fn bitand(self, rhs: A) -> AttributeSet {
584 AttributeSet(self.0 & rhs.into().0)
585 }
586}
587
588impl<A: Into<AttributeSet>> std::ops::BitAndAssign<A> for AttributeSet {
589 fn bitand_assign(&mut self, rhs: A) {
590 self.0 &= rhs.into().0;
591 }
592}
593
594impl<A: Into<AttributeSet>> std::ops::BitOr<A> for AttributeSet {
595 type Output = AttributeSet;
596
597 fn bitor(self, rhs: A) -> AttributeSet {
598 AttributeSet(self.0 | rhs.into().0)
599 }
600}
601
602impl<A: Into<AttributeSet>> std::ops::BitOrAssign<A> for AttributeSet {
603 fn bitor_assign(&mut self, rhs: A) {
604 self.0 |= rhs.into().0;
605 }
606}
607
608impl<A: Into<AttributeSet>> std::ops::BitXor<A> for AttributeSet {
609 type Output = AttributeSet;
610
611 fn bitxor(self, rhs: A) -> AttributeSet {
612 AttributeSet(self.0 ^ rhs.into().0)
613 }
614}
615
616impl<A: Into<AttributeSet>> std::ops::BitXorAssign<A> for AttributeSet {
617 fn bitxor_assign(&mut self, rhs: A) {
618 self.0 ^= rhs.into().0;
619 }
620}
621
622impl<A: Into<AttributeSet>> std::ops::Sub<A> for AttributeSet {
623 type Output = AttributeSet;
624
625 fn sub(self, rhs: A) -> AttributeSet {
626 AttributeSet(self.0 & !rhs.into().0)
627 }
628}
629
630impl<A: Into<AttributeSet>> std::ops::SubAssign<A> for AttributeSet {
631 fn sub_assign(&mut self, rhs: A) {
632 self.0 &= !rhs.into().0;
633 }
634}
635
636impl std::ops::Not for AttributeSet {
637 type Output = AttributeSet;
638
639 fn not(self) -> AttributeSet {
640 AttributeSet(!self.0 & ((1 << Attribute::COUNT) - 1))
641 }
642}
643
644#[derive(Clone, Debug)]
646pub struct AttributeSetIter {
647 inner: AttributeIter,
648 set: AttributeSet,
649}
650
651impl AttributeSetIter {
652 fn new(set: AttributeSet) -> AttributeSetIter {
653 AttributeSetIter {
654 inner: Attribute::iter(),
655 set,
656 }
657 }
658}
659
660impl Iterator for AttributeSetIter {
661 type Item = Attribute;
662
663 fn next(&mut self) -> Option<Attribute> {
664 self.inner.by_ref().find(|&attr| self.set.contains(attr))
665 }
666
667 fn size_hint(&self) -> (usize, Option<usize>) {
668 (0, self.inner.size_hint().1)
669 }
670}
671
672impl DoubleEndedIterator for AttributeSetIter {
673 fn next_back(&mut self) -> Option<Attribute> {
674 self.inner.by_ref().rfind(|&attr| self.set.contains(attr))
675 }
676}
677
678impl std::iter::FusedIterator for AttributeSetIter {}
679
680#[derive(Clone, Debug, Eq, Error, PartialEq)]
682#[error("invalid attribute name: {0:?}")]
683pub struct ParseAttributeError(
684 pub String,
686);
687
688#[cfg(test)]
689mod tests {
690 use super::*;
691
692 mod attribute {
693 use super::*;
694 use rstest::rstest;
695
696 #[rstest]
697 #[case(Attribute::Bold, "bold")]
698 #[case(Attribute::Dim, "dim")]
699 #[case(Attribute::Italic, "italic")]
700 #[case(Attribute::Underline, "underline")]
701 #[case(Attribute::Blink, "blink")]
702 #[case(Attribute::Blink2, "blink2")]
703 #[case(Attribute::Reverse, "reverse")]
704 #[case(Attribute::Conceal, "conceal")]
705 #[case(Attribute::Strike, "strike")]
706 #[case(Attribute::Underline2, "underline2")]
707 #[case(Attribute::Frame, "frame")]
708 #[case(Attribute::Encircle, "encircle")]
709 #[case(Attribute::Overline, "overline")]
710 fn display(#[case] attr: Attribute, #[case] s: &str) {
711 assert_eq!(attr.to_string(), s);
712 }
713
714 #[rstest]
715 #[case(Attribute::Bold, "b")]
716 #[case(Attribute::Dim, "d")]
717 #[case(Attribute::Italic, "i")]
718 #[case(Attribute::Underline, "u")]
719 #[case(Attribute::Blink, "blink")]
720 #[case(Attribute::Blink2, "blink2")]
721 #[case(Attribute::Reverse, "r")]
722 #[case(Attribute::Conceal, "c")]
723 #[case(Attribute::Strike, "s")]
724 #[case(Attribute::Underline2, "uu")]
725 #[case(Attribute::Frame, "frame")]
726 #[case(Attribute::Encircle, "encircle")]
727 #[case(Attribute::Overline, "overline")]
728 fn alt_display(#[case] attr: Attribute, #[case] s: &str) {
729 assert_eq!(format!("{attr:#}"), s);
730 }
731 }
732
733 mod attribute_set {
734 use super::*;
735
736 #[test]
737 fn double_ended_iteration() {
738 let attrs = Attribute::Bold | Attribute::Frame | Attribute::Reverse | Attribute::Strike;
739 let mut iter = attrs.into_iter();
740 assert_eq!(iter.next(), Some(Attribute::Bold));
741 assert_eq!(iter.next_back(), Some(Attribute::Frame));
742 assert_eq!(iter.next(), Some(Attribute::Reverse));
743 assert_eq!(iter.next_back(), Some(Attribute::Strike));
744 assert_eq!(iter.next(), None);
745 assert_eq!(iter.next_back(), None);
746 assert_eq!(iter.next(), None);
747 assert_eq!(iter.next_back(), None);
748 }
749 }
750}