Skip to main content

gitu/
style.rs

1use serde::{Deserialize, Deserializer, de};
2use std::str::FromStr;
3
4#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
5pub struct Style {
6    pub fg: Option<Color>,
7    pub bg: Option<Color>,
8    pub add_modifier: Modifier,
9    pub sub_modifier: Modifier,
10}
11
12impl Style {
13    pub const fn new() -> Self {
14        Style {
15            fg: None,
16            bg: None,
17            add_modifier: Modifier::empty(),
18            sub_modifier: Modifier::empty(),
19        }
20    }
21
22    /// Lays `other` over `self`, with what `other` leaves unset showing through.
23    pub fn patch(mut self, other: Style) -> Style {
24        self.fg = other.fg.or(self.fg);
25        self.bg = other.bg.or(self.bg);
26
27        self.add_modifier.remove(other.sub_modifier);
28        self.add_modifier.insert(other.add_modifier);
29        self.sub_modifier.remove(other.add_modifier);
30        self.sub_modifier.insert(other.sub_modifier);
31
32        self
33    }
34}
35
36#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum Color {
38    #[default]
39    Reset,
40    Black,
41    Red,
42    Green,
43    Yellow,
44    Blue,
45    Magenta,
46    Cyan,
47    Gray,
48    DarkGray,
49    LightRed,
50    LightGreen,
51    LightYellow,
52    LightBlue,
53    LightMagenta,
54    LightCyan,
55    White,
56    Rgb(u8, u8, u8),
57    Indexed(u8),
58}
59
60impl From<Color> for crossterm::style::Color {
61    fn from(color: Color) -> Self {
62        match color {
63            Color::Reset => Self::Reset,
64            Color::Black => Self::Black,
65            Color::Red => Self::DarkRed,
66            Color::Green => Self::DarkGreen,
67            Color::Yellow => Self::DarkYellow,
68            Color::Blue => Self::DarkBlue,
69            Color::Magenta => Self::DarkMagenta,
70            Color::Cyan => Self::DarkCyan,
71            Color::Gray => Self::Grey,
72            Color::DarkGray => Self::DarkGrey,
73            Color::LightRed => Self::Red,
74            Color::LightGreen => Self::Green,
75            Color::LightYellow => Self::Yellow,
76            Color::LightBlue => Self::Blue,
77            Color::LightMagenta => Self::Magenta,
78            Color::LightCyan => Self::Cyan,
79            Color::White => Self::White,
80            Color::Rgb(r, g, b) => Self::Rgb { r, g, b },
81            Color::Indexed(i) => Self::AnsiValue(i),
82        }
83    }
84}
85
86impl FromStr for Color {
87    type Err = ParseColorError;
88
89    fn from_str(s: &str) -> Result<Self, Self::Err> {
90        // Colors are written by hand in the config, so accept the spellings
91        // that are in the wild rather than one canonical form.
92        let name = s
93            .to_lowercase()
94            .replace([' ', '-', '_'], "")
95            .replace("bright", "light")
96            .replace("grey", "gray")
97            .replace("silver", "gray")
98            .replace("lightblack", "darkgray")
99            .replace("lightwhite", "white")
100            .replace("lightgray", "white");
101
102        Ok(match name.as_str() {
103            "reset" => Self::Reset,
104            "black" => Self::Black,
105            "red" => Self::Red,
106            "green" => Self::Green,
107            "yellow" => Self::Yellow,
108            "blue" => Self::Blue,
109            "magenta" => Self::Magenta,
110            "cyan" => Self::Cyan,
111            "gray" => Self::Gray,
112            "darkgray" => Self::DarkGray,
113            "lightred" => Self::LightRed,
114            "lightgreen" => Self::LightGreen,
115            "lightyellow" => Self::LightYellow,
116            "lightblue" => Self::LightBlue,
117            "lightmagenta" => Self::LightMagenta,
118            "lightcyan" => Self::LightCyan,
119            "white" => Self::White,
120            _ => {
121                if let Ok(index) = s.parse::<u8>() {
122                    Self::Indexed(index)
123                } else if let Some((r, g, b)) = parse_hex_color(s) {
124                    Self::Rgb(r, g, b)
125                } else {
126                    return Err(ParseColorError);
127                }
128            }
129        })
130    }
131}
132
133fn parse_hex_color(s: &str) -> Option<(u8, u8, u8)> {
134    let hex = s.strip_prefix('#')?;
135    if hex.len() != 6 {
136        return None;
137    }
138
139    // Indexing would panic where a multi-byte char straddles the split.
140    let channel = |range: std::ops::Range<usize>| u8::from_str_radix(hex.get(range)?, 16).ok();
141    Some((channel(0..2)?, channel(2..4)?, channel(4..6)?))
142}
143
144#[derive(Debug)]
145pub struct ParseColorError;
146
147impl std::fmt::Display for ParseColorError {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        f.write_str("invalid color")
150    }
151}
152
153impl std::error::Error for ParseColorError {}
154
155impl<'de> Deserialize<'de> for Color {
156    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
157        let s = String::deserialize(deserializer)?;
158        Color::from_str(&s).map_err(|_| de::Error::custom(format!("unknown color: {s}")))
159    }
160}
161
162#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
163pub struct Modifier(pub u16);
164
165impl Modifier {
166    pub const BOLD: Self = Modifier(0b0000_0000_0001);
167    pub const DIM: Self = Modifier(0b0000_0000_0010);
168    pub const ITALIC: Self = Modifier(0b0000_0000_0100);
169    pub const UNDERLINED: Self = Modifier(0b0000_0000_1000);
170    pub const SLOW_BLINK: Self = Modifier(0b0000_0001_0000);
171    pub const RAPID_BLINK: Self = Modifier(0b0000_0010_0000);
172    pub const REVERSED: Self = Modifier(0b0000_0100_0000);
173    pub const HIDDEN: Self = Modifier(0b0000_1000_0000);
174    pub const CROSSED_OUT: Self = Modifier(0b0001_0000_0000);
175
176    const NAMED: &'static [(&'static str, Self)] = &[
177        ("BOLD", Self::BOLD),
178        ("DIM", Self::DIM),
179        ("ITALIC", Self::ITALIC),
180        ("UNDERLINED", Self::UNDERLINED),
181        ("SLOW_BLINK", Self::SLOW_BLINK),
182        ("RAPID_BLINK", Self::RAPID_BLINK),
183        ("REVERSED", Self::REVERSED),
184        ("HIDDEN", Self::HIDDEN),
185        ("CROSSED_OUT", Self::CROSSED_OUT),
186    ];
187
188    pub const fn empty() -> Self {
189        Modifier(0)
190    }
191
192    pub fn contains(self, other: Self) -> bool {
193        self.0 & other.0 == other.0
194    }
195
196    pub fn insert(&mut self, other: Self) {
197        self.0 |= other.0;
198    }
199
200    pub fn remove(&mut self, other: Self) {
201        self.0 &= !other.0;
202    }
203}
204
205impl std::ops::BitOr for Modifier {
206    type Output = Self;
207
208    fn bitor(self, other: Self) -> Self {
209        Modifier(self.0 | other.0)
210    }
211}
212
213impl std::ops::BitOrAssign for Modifier {
214    fn bitor_assign(&mut self, other: Self) {
215        self.insert(other);
216    }
217}
218
219impl FromStr for Modifier {
220    type Err = ParseModifierError;
221
222    fn from_str(s: &str) -> Result<Self, Self::Err> {
223        let mut modifier = Modifier::empty();
224
225        if s.trim().is_empty() {
226            return Ok(modifier);
227        }
228
229        for name in s.split('|') {
230            let name = name.trim();
231            let (_, flag) = Modifier::NAMED
232                .iter()
233                .find(|(named, _)| named.eq_ignore_ascii_case(name))
234                .ok_or(ParseModifierError)?;
235
236            modifier.insert(*flag);
237        }
238
239        Ok(modifier)
240    }
241}
242
243#[derive(Debug)]
244pub struct ParseModifierError;
245
246impl std::fmt::Display for ParseModifierError {
247    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248        f.write_str("invalid modifier")
249    }
250}
251
252impl std::error::Error for ParseModifierError {}
253
254impl<'de> Deserialize<'de> for Modifier {
255    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
256        let s = String::deserialize(deserializer)?;
257        Modifier::from_str(&s).map_err(|_| de::Error::custom(format!("unknown modifier: {s}")))
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264
265    #[test]
266    fn parse_modifiers() {
267        assert_eq!(Modifier::from_str("").unwrap(), Modifier::empty());
268        assert_eq!(Modifier::from_str("BOLD").unwrap(), Modifier::BOLD);
269
270        let mut both = Modifier::BOLD;
271        both.insert(Modifier::ITALIC);
272        assert_eq!(Modifier::from_str("BOLD|ITALIC").unwrap(), both);
273        assert_eq!(Modifier::from_str("BOLD | ITALIC").unwrap(), both);
274
275        assert!(Modifier::from_str("NONSENSE").is_err());
276    }
277
278    #[test]
279    fn parse_colors() {
280        assert_eq!(Color::from_str("light blue").unwrap(), Color::LightBlue);
281        assert_eq!(Color::from_str("LightBlue").unwrap(), Color::LightBlue);
282        assert_eq!(Color::from_str("bright-blue").unwrap(), Color::LightBlue);
283        assert_eq!(Color::from_str("grey").unwrap(), Color::Gray);
284        assert_eq!(Color::from_str("reset").unwrap(), Color::Reset);
285        assert_eq!(Color::from_str("255").unwrap(), Color::Indexed(255));
286        assert_eq!(
287            Color::from_str("#707070").unwrap(),
288            Color::Rgb(112, 112, 112)
289        );
290
291        assert!(Color::from_str("#70707").is_err());
292        assert!(Color::from_str("#€abc").is_err());
293        assert!(Color::from_str("nonsense").is_err());
294    }
295}