parse_style/
color.rs

1use super::ParseColorError;
2use crate::color256::Color256;
3use crate::rgbcolor::RgbColor;
4use crate::style::Style;
5use std::fmt;
6
7/// An enum of the different color types
8#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum Color {
10    /// The terminal's default foreground or background color
11    #[default]
12    Default,
13    Color256(Color256),
14    Rgb(RgbColor),
15}
16
17impl Color {
18    /// Return a new [`Style`] that uses this color as the foreground color
19    pub fn as_foreground(self) -> Style {
20        Style::new().foreground(Some(self))
21    }
22
23    /// Return a new [`Style`] that uses this color as the background color
24    pub fn as_background(self) -> Style {
25        Style::new().background(Some(self))
26    }
27
28    /// Return a new [`Style`] that uses this color as the foreground color and
29    /// `bg` as the background color
30    pub fn on<C: Into<Color>>(self, bg: C) -> Style {
31        Style::new()
32            .foreground(Some(self))
33            .background(Some(bg.into()))
34    }
35}
36
37impl From<Color256> for Color {
38    fn from(value: Color256) -> Color {
39        Color::Color256(value)
40    }
41}
42
43impl From<RgbColor> for Color {
44    fn from(value: RgbColor) -> Color {
45        Color::Rgb(value)
46    }
47}
48
49impl From<u8> for Color {
50    fn from(value: u8) -> Color {
51        Color::Color256(Color256::from(value))
52    }
53}
54
55impl From<(u8, u8, u8)> for Color {
56    fn from(value: (u8, u8, u8)) -> Color {
57        Color::Rgb(RgbColor::from(value))
58    }
59}
60
61#[cfg(feature = "anstyle")]
62#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
63impl TryFrom<Color> for anstyle::Color {
64    type Error = crate::ConversionError;
65
66    /// Convert a `Color` to an [`anstyle::Color`]
67    ///
68    /// # Errors
69    ///
70    /// Returns `Err` if `value` is `Color::Default`, which is not
71    /// representable by `anstyle::Color`
72    fn try_from(value: Color) -> Result<anstyle::Color, crate::ConversionError> {
73        match value {
74            Color::Default => Err(crate::ConversionError),
75            Color::Color256(c) => Ok(anstyle::Color::Ansi256(c.into())),
76            Color::Rgb(c) => Ok(anstyle::Color::Rgb(c.into())),
77        }
78    }
79}
80
81#[cfg(feature = "anstyle")]
82#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
83impl From<anstyle::Color> for Color {
84    /// Convert an [`anstyle::Color`] to a `Color`
85    fn from(value: anstyle::Color) -> Color {
86        match value {
87            anstyle::Color::Ansi(c) => Color::Color256(c.into()),
88            anstyle::Color::Ansi256(c) => Color::Color256(c.into()),
89            anstyle::Color::Rgb(c) => Color::Rgb(c.into()),
90        }
91    }
92}
93
94#[cfg(feature = "crossterm")]
95#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
96impl From<Color> for crossterm::style::Color {
97    /// Convert a `Color` to a [`crossterm::style::Color`]
98    fn from(value: Color) -> crossterm::style::Color {
99        match value {
100            Color::Default => crossterm::style::Color::Reset,
101            Color::Color256(c) => c.into(),
102            Color::Rgb(c) => c.into(),
103        }
104    }
105}
106
107#[cfg(feature = "crossterm")]
108#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
109impl From<crossterm::style::Color> for Color {
110    /// Convert a [`crossterm::style::Color`] to a `Color`
111    fn from(value: crossterm::style::Color) -> Color {
112        match value {
113            crossterm::style::Color::Reset => Color::Default,
114            crossterm::style::Color::Black => Color256::BRIGHT_BLACK.into(),
115            crossterm::style::Color::DarkGrey => Color256::BLACK.into(),
116            crossterm::style::Color::Red => Color256::BRIGHT_RED.into(),
117            crossterm::style::Color::DarkRed => Color256::RED.into(),
118            crossterm::style::Color::Green => Color256::BRIGHT_GREEN.into(),
119            crossterm::style::Color::DarkGreen => Color256::GREEN.into(),
120            crossterm::style::Color::Yellow => Color256::BRIGHT_YELLOW.into(),
121            crossterm::style::Color::DarkYellow => Color256::YELLOW.into(),
122            crossterm::style::Color::Blue => Color256::BRIGHT_BLUE.into(),
123            crossterm::style::Color::DarkBlue => Color256::BLUE.into(),
124            crossterm::style::Color::Magenta => Color256::BRIGHT_MAGENTA.into(),
125            crossterm::style::Color::DarkMagenta => Color256::MAGENTA.into(),
126            crossterm::style::Color::Cyan => Color256::BRIGHT_CYAN.into(),
127            crossterm::style::Color::DarkCyan => Color256::CYAN.into(),
128            crossterm::style::Color::White => Color256::BRIGHT_WHITE.into(),
129            crossterm::style::Color::Grey => Color256::WHITE.into(),
130            crossterm::style::Color::Rgb { r, g, b } => RgbColor(r, g, b).into(),
131            crossterm::style::Color::AnsiValue(index) => Color256(index).into(),
132        }
133    }
134}
135
136#[cfg(feature = "ratatui")]
137#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
138impl From<Color> for ratatui::style::Color {
139    /// Convert a `Color` to a [`ratatui::style::Color`]
140    fn from(value: Color) -> ratatui::style::Color {
141        match value {
142            Color::Default => ratatui::style::Color::Reset,
143            Color::Color256(c) => c.into(),
144            Color::Rgb(c) => c.into(),
145        }
146    }
147}
148
149#[cfg(feature = "ratatui")]
150#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
151impl From<ratatui::style::Color> for Color {
152    /// Convert a [`ratatui::style::Color`] to a `Color`
153    fn from(value: ratatui::style::Color) -> Color {
154        match value {
155            ratatui::style::Color::Reset => Color::Default,
156            ratatui::style::Color::Black => Color256::BLACK.into(),
157            ratatui::style::Color::Red => Color256::RED.into(),
158            ratatui::style::Color::Green => Color256::GREEN.into(),
159            ratatui::style::Color::Yellow => Color256::YELLOW.into(),
160            ratatui::style::Color::Blue => Color256::BLUE.into(),
161            ratatui::style::Color::Magenta => Color256::MAGENTA.into(),
162            ratatui::style::Color::Cyan => Color256::CYAN.into(),
163            ratatui::style::Color::Gray => Color256::WHITE.into(),
164            ratatui::style::Color::DarkGray => Color256::BRIGHT_BLACK.into(),
165            ratatui::style::Color::LightRed => Color256::BRIGHT_RED.into(),
166            ratatui::style::Color::LightGreen => Color256::BRIGHT_GREEN.into(),
167            ratatui::style::Color::LightYellow => Color256::BRIGHT_YELLOW.into(),
168            ratatui::style::Color::LightBlue => Color256::BRIGHT_BLUE.into(),
169            ratatui::style::Color::LightMagenta => Color256::BRIGHT_MAGENTA.into(),
170            ratatui::style::Color::LightCyan => Color256::BRIGHT_CYAN.into(),
171            ratatui::style::Color::White => Color256::BRIGHT_WHITE.into(),
172            ratatui::style::Color::Rgb(r, g, b) => RgbColor(r, g, b).into(),
173            ratatui::style::Color::Indexed(index) => Color256(index).into(),
174        }
175    }
176}
177
178impl fmt::Display for Color {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        match self {
181            Color::Default => write!(f, "default"),
182            Color::Color256(c) => write!(f, "{c}"),
183            Color::Rgb(c) => write!(f, "{c}"),
184        }
185    }
186}
187
188impl std::str::FromStr for Color {
189    type Err = ParseColorError;
190
191    fn from_str(s: &str) -> Result<Color, ParseColorError> {
192        if s.eq_ignore_ascii_case("default") {
193            Ok(Color::Default)
194        } else {
195            s.parse::<Color256>()
196                .map(Color::from)
197                .or_else(|_| s.parse::<RgbColor>().map(Color::from))
198        }
199    }
200}
201
202#[cfg(feature = "serde")]
203#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
204impl serde::Serialize for Color {
205    fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
206        serializer.collect_str(self)
207    }
208}
209
210#[cfg(feature = "serde")]
211#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
212impl<'de> serde::Deserialize<'de> for Color {
213    fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
214        struct Visitor;
215
216        impl serde::de::Visitor<'_> for Visitor {
217            type Value = Color;
218
219            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220                f.write_str(r##"a color word or a string of the form "color(INT)", "rgb(INT,INT,INT)", or "#xxxxxx""##)
221            }
222
223            fn visit_str<E>(self, input: &str) -> Result<Self::Value, E>
224            where
225                E: serde::de::Error,
226            {
227                input
228                    .parse::<Color>()
229                    .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(input), &self))
230            }
231        }
232
233        deserializer.deserialize_str(Visitor)
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_display_default() {
243        assert_eq!(Color::Default.to_string(), "default");
244    }
245
246    #[test]
247    fn test_display_color256() {
248        assert_eq!(Color::from(118).to_string(), "chartreuse1");
249    }
250
251    #[test]
252    fn test_display_rgbcolor() {
253        assert_eq!(Color::from((111, 120, 189)).to_string(), "#6f78bd");
254    }
255
256    #[test]
257    fn test_parse_default() {
258        assert_eq!("default".parse::<Color>().unwrap(), Color::Default);
259    }
260
261    #[test]
262    fn test_parse_color256() {
263        assert_eq!(
264            "chartreuse1".parse::<Color>().unwrap(),
265            Color::Color256(Color256(118))
266        );
267    }
268
269    #[test]
270    fn test_parse_rgbcolor() {
271        assert_eq!(
272            "#6f78bd".parse::<Color>().unwrap(),
273            Color::Rgb(RgbColor(111, 120, 189))
274        );
275    }
276
277    #[test]
278    fn test_parse_err() {
279        assert!("mauve".parse::<Color>().is_err());
280    }
281}