1#[derive(Clone, Copy, PartialEq, Eq, Debug)]
8pub enum Color {
9 Reset,
10 Black,
11 Red,
12 Green,
13 Yellow,
14 Blue,
15 Magenta,
16 Cyan,
17 White,
18 DarkGray,
19 LightRed,
20 LightGreen,
21 LightYellow,
22 LightBlue,
23 LightMagenta,
24 LightCyan,
25}
26
27#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
29pub struct Style {
30 pub fg: Option<Color>,
31 pub bg: Option<Color>,
32 pub bold: bool,
33 pub underline: bool,
34 pub reversed: bool,
35}
36
37impl Style {
38 pub const fn new() -> Style {
39 Style {
40 fg: None,
41 bg: None,
42 bold: false,
43 underline: false,
44 reversed: false,
45 }
46 }
47
48 pub const fn fg(mut self, color: Color) -> Style {
49 self.fg = Some(color);
50 self
51 }
52
53 pub const fn bg(mut self, color: Color) -> Style {
54 self.bg = Some(color);
55 self
56 }
57
58 pub const fn bold(mut self) -> Style {
59 self.bold = true;
60 self
61 }
62
63 pub const fn underline(mut self) -> Style {
64 self.underline = true;
65 self
66 }
67
68 pub const fn reversed(mut self) -> Style {
69 self.reversed = true;
70 self
71 }
72
73 pub fn patch(self, other: Style) -> Style {
76 Style {
77 fg: other.fg.or(self.fg),
78 bg: other.bg.or(self.bg),
79 bold: self.bold || other.bold,
80 underline: self.underline || other.underline,
81 reversed: self.reversed || other.reversed,
82 }
83 }
84}
85
86pub fn parse_color(name: &str) -> Option<Color> {
88 Some(match name.to_lowercase().as_str() {
89 "default" => Color::Reset,
90 "black" => Color::Black,
91 "red" => Color::Red,
92 "green" => Color::Green,
93 "yellow" => Color::Yellow,
94 "blue" => Color::Blue,
95 "magenta" => Color::Magenta,
96 "cyan" => Color::Cyan,
97 "white" => Color::White,
98 "gray" | "grey" | "darkgray" | "darkgrey" => Color::DarkGray,
99 "lightred" => Color::LightRed,
100 "lightgreen" => Color::LightGreen,
101 "lightyellow" => Color::LightYellow,
102 "lightblue" => Color::LightBlue,
103 "lightmagenta" => Color::LightMagenta,
104 "lightcyan" => Color::LightCyan,
105 _ => return None,
106 })
107}
108
109pub fn rule_style(
113 rule: &rmut_core::config::ColorRule,
114 what: &str,
115 warnings: &mut Vec<String>,
116) -> Style {
117 let mut style = Style::new();
118 for (name, is_fg) in [(&rule.fg, true), (&rule.bg, false)] {
119 let Some(name) = name else { continue };
120 match name.as_str() {
121 "bold" => style = style.bold(),
122 "underline" => style = style.underline(),
123 "reverse" | "standout" => style = style.reversed(),
124 "none" => {}
125 _ => match parse_color(name) {
126 Some(color) if is_fg => style = style.fg(color),
127 Some(color) => style = style.bg(color),
128 None => warnings.push(format!("unknown {what} color {name:?}")),
129 },
130 }
131 }
132 style
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn patch_overlays_colors_and_adds_attributes() {
141 let base = Style::new().fg(Color::Red).bold();
142 let over = Style::new().bg(Color::Blue).reversed();
143 let got = base.patch(over);
144 assert_eq!(
145 got,
146 Style::new()
147 .fg(Color::Red)
148 .bg(Color::Blue)
149 .bold()
150 .reversed()
151 );
152 let got = got.patch(Style::new().fg(Color::Green));
153 assert_eq!(got.fg, Some(Color::Green));
154 assert!(got.bold && got.reversed);
155 }
156
157 #[test]
158 fn rules_read_attributes_and_colors_in_either_slot() {
159 let rule = rmut_core::config::ColorRule {
160 pattern: String::new(),
161 fg: Some("bold".into()),
162 bg: Some("blue".into()),
163 };
164 let mut warnings = Vec::new();
165 let style = rule_style(&rule, "color_index", &mut warnings);
166 assert_eq!(style, Style::new().bold().bg(Color::Blue));
167 assert!(warnings.is_empty());
168 let rule = rmut_core::config::ColorRule {
169 pattern: String::new(),
170 fg: Some("chartreuse".into()),
171 bg: None,
172 };
173 assert_eq!(rule_style(&rule, "color_body", &mut warnings), Style::new());
174 assert_eq!(warnings.len(), 1);
175 }
176}