Skip to main content

spectrum/emit/
style.rs

1//! Wrap console::Attribute and console::Style to tweak their behavior (especially debug output)
2
3use derive_new::new;
4use std::{fmt::Debug, hash::Hash};
5
6use indexmap::IndexSet;
7
8#[derive(Copy, Clone)]
9pub struct Attribute {
10    attr: console::Attribute,
11}
12
13impl Debug for Attribute {
14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        write!(
16            f,
17            "{}",
18            match self.attr {
19                console::Attribute::Bold => "bold",
20                console::Attribute::Dim => "dim",
21                console::Attribute::Italic => "italic",
22                console::Attribute::Underlined => "underlined",
23                console::Attribute::Blink => "blink",
24                console::Attribute::Reverse => "reverse",
25                console::Attribute::Hidden => "hidden",
26            }
27        )
28    }
29}
30
31impl PartialOrd for Attribute {
32    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
33        self.attr.partial_cmp(&other.attr)
34    }
35}
36
37impl Ord for Attribute {
38    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
39        self.attr.cmp(&other.attr)
40    }
41}
42
43impl PartialEq for Attribute {
44    fn eq(&self, other: &Self) -> bool {
45        self.attr == other.attr
46    }
47}
48
49impl Eq for Attribute {}
50
51impl Hash for Attribute {
52    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
53        match self.attr {
54            console::Attribute::Bold => 0.hash(state),
55            console::Attribute::Dim => 1.hash(state),
56            console::Attribute::Italic => 2.hash(state),
57            console::Attribute::Underlined => 3.hash(state),
58            console::Attribute::Blink => 4.hash(state),
59            console::Attribute::Reverse => 5.hash(state),
60            console::Attribute::Hidden => 6.hash(state),
61        }
62    }
63}
64
65impl Into<Attribute> for console::Attribute {
66    fn into(self) -> Attribute {
67        Attribute { attr: self }
68    }
69}
70
71#[derive(Clone, new)]
72pub struct Style {
73    #[new(value = "None")]
74    fg: Option<console::Color>,
75    #[new(value = "None")]
76    bg: Option<console::Color>,
77    #[new(default)]
78    attrs: IndexSet<Attribute>,
79}
80
81impl Default for Style {
82    fn default() -> Self {
83        Style::new()
84    }
85}
86
87impl Style {
88    pub fn apply_to<D>(&self, fragment: D) -> console::StyledObject<D> {
89        let style: console::Style = self.into();
90        style.apply_to(fragment)
91    }
92
93    pub fn fg(mut self, color: impl Into<console::Color>) -> Style {
94        self.fg = Some(color.into());
95        self
96    }
97
98    pub fn bg(mut self, color: impl Into<console::Color>) -> Style {
99        self.bg = Some(color.into());
100        self
101    }
102
103    pub fn attr(mut self, attr: impl Into<Attribute>) -> Style {
104        self.attrs.insert(attr.into());
105        self
106    }
107}
108
109impl<'a> From<&'a Style> for console::Style {
110    fn from(style: &'a Style) -> Self {
111        let style = style.clone();
112        style.into()
113    }
114}
115
116impl From<Style> for console::Style {
117    fn from(style: Style) -> Self {
118        let mut console_style = console::Style::new();
119
120        if let Some(fg) = style.fg {
121            console_style = console_style.fg(fg);
122        }
123
124        if let Some(bg) = style.bg {
125            console_style = console_style.bg(bg);
126        }
127
128        for attr in style.attrs {
129            console_style = console_style.attr(attr.attr);
130        }
131
132        console_style
133    }
134}
135
136impl Debug for Style {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        let Self { fg, bg, attrs } = self;
139
140        let mut desc = String::new();
141        let mut short = true;
142
143        match (fg, bg) {
144            (Some(fg), None) => {
145                desc.push_str(&format!("{:?}", fg));
146            }
147            (None, Some(bg)) => {
148                desc.push_str(&format!("normal on {:?}", bg));
149                short = false;
150            }
151            (None, None) => {
152                desc.push_str("normal");
153            }
154            (Some(fg), Some(bg)) => {
155                desc.push_str(&format!("{:?} on {:?}", fg, bg));
156                short = false;
157            }
158        }
159
160        let mut debug_attrs = String::new();
161
162        for attr in itertools::sorted(attrs.iter()) {
163            debug_attrs.push_str(", ");
164
165            debug_attrs.push_str(&format!("{:?}", attr));
166        }
167
168        if !debug_attrs.is_empty() {
169            short = false;
170        }
171
172        if short {
173            write!(f, "{}", desc)
174        } else {
175            write!(f, "[{}{}]", desc, debug_attrs)
176        }
177    }
178}