Skip to main content

ratatui_core/style/
anstyle.rs

1//! This module contains conversion functions for styles from the `anstyle` crate.
2use anstyle::{Ansi256Color, AnsiColor, Effects, RgbColor};
3use thiserror::Error;
4
5use super::{Color, Modifier, Style};
6
7/// Error type for converting between `anstyle` colors and `Color`
8#[derive(Debug, Error, PartialEq, Eq)]
9pub enum TryFromColorError {
10    #[error("cannot convert Ratatui Color to an Ansi256Color as it is not an indexed color")]
11    Ansi256,
12    #[error("cannot convert Ratatui Color to AnsiColor as it is not a 4-bit color")]
13    Ansi,
14    #[error("cannot convert Ratatui Color to RgbColor as it is not an RGB color")]
15    RgbColor,
16}
17
18impl From<Ansi256Color> for Color {
19    fn from(color: Ansi256Color) -> Self {
20        Self::Indexed(color.index())
21    }
22}
23
24impl TryFrom<Color> for Ansi256Color {
25    type Error = TryFromColorError;
26
27    fn try_from(color: Color) -> Result<Self, Self::Error> {
28        match color {
29            Color::Indexed(index) => Ok(Self(index)),
30            _ => Err(TryFromColorError::Ansi256),
31        }
32    }
33}
34
35impl From<AnsiColor> for Color {
36    fn from(value: AnsiColor) -> Self {
37        match value {
38            AnsiColor::Black => Self::Black,
39            AnsiColor::Red => Self::Red,
40            AnsiColor::Green => Self::Green,
41            AnsiColor::Yellow => Self::Yellow,
42            AnsiColor::Blue => Self::Blue,
43            AnsiColor::Magenta => Self::Magenta,
44            AnsiColor::Cyan => Self::Cyan,
45            AnsiColor::White => Self::Gray,
46            AnsiColor::BrightBlack => Self::DarkGray,
47            AnsiColor::BrightRed => Self::LightRed,
48            AnsiColor::BrightGreen => Self::LightGreen,
49            AnsiColor::BrightYellow => Self::LightYellow,
50            AnsiColor::BrightBlue => Self::LightBlue,
51            AnsiColor::BrightMagenta => Self::LightMagenta,
52            AnsiColor::BrightCyan => Self::LightCyan,
53            AnsiColor::BrightWhite => Self::White,
54        }
55    }
56}
57
58impl TryFrom<Color> for AnsiColor {
59    type Error = TryFromColorError;
60
61    fn try_from(color: Color) -> Result<Self, Self::Error> {
62        match color {
63            Color::Black => Ok(Self::Black),
64            Color::Red => Ok(Self::Red),
65            Color::Green => Ok(Self::Green),
66            Color::Yellow => Ok(Self::Yellow),
67            Color::Blue => Ok(Self::Blue),
68            Color::Magenta => Ok(Self::Magenta),
69            Color::Cyan => Ok(Self::Cyan),
70            Color::Gray => Ok(Self::White),
71            Color::DarkGray => Ok(Self::BrightBlack),
72            Color::LightRed => Ok(Self::BrightRed),
73            Color::LightGreen => Ok(Self::BrightGreen),
74            Color::LightYellow => Ok(Self::BrightYellow),
75            Color::LightBlue => Ok(Self::BrightBlue),
76            Color::LightMagenta => Ok(Self::BrightMagenta),
77            Color::LightCyan => Ok(Self::BrightCyan),
78            Color::White => Ok(Self::BrightWhite),
79            _ => Err(TryFromColorError::Ansi),
80        }
81    }
82}
83
84impl From<RgbColor> for Color {
85    fn from(color: RgbColor) -> Self {
86        Self::Rgb(color.r(), color.g(), color.b())
87    }
88}
89
90impl TryFrom<Color> for RgbColor {
91    type Error = TryFromColorError;
92
93    fn try_from(color: Color) -> Result<Self, Self::Error> {
94        match color {
95            Color::Rgb(red, green, blue) => Ok(Self(red, green, blue)),
96            _ => Err(TryFromColorError::RgbColor),
97        }
98    }
99}
100
101impl From<anstyle::Color> for Color {
102    fn from(color: anstyle::Color) -> Self {
103        match color {
104            anstyle::Color::Ansi(ansi_color) => Self::from(ansi_color),
105            anstyle::Color::Ansi256(ansi256_color) => Self::from(ansi256_color),
106            anstyle::Color::Rgb(rgb_color) => Self::from(rgb_color),
107        }
108    }
109}
110
111impl From<Color> for anstyle::Color {
112    /// Converts a Ratatui `Color` into an `anstyle::Color`.
113    ///
114    /// # Panics
115    ///
116    /// This method will panic if the input is [`Color::Reset`], as there is no
117    /// equivalent representation for a reset color in `anstyle`.
118    fn from(color: Color) -> Self {
119        match color {
120            Color::Reset => panic!("Color::Reset has no equivalent in anstyle"),
121            Color::Rgb(_, _, _) => Self::Rgb(RgbColor::try_from(color).unwrap()),
122            Color::Indexed(_) => Self::Ansi256(Ansi256Color::try_from(color).unwrap()),
123            _ => Self::Ansi(AnsiColor::try_from(color).unwrap()),
124        }
125    }
126}
127
128impl From<Effects> for Modifier {
129    fn from(effect: Effects) -> Self {
130        let mut modifier = Self::empty();
131        if effect.contains(Effects::BOLD) {
132            modifier |= Self::BOLD;
133        }
134        if effect.contains(Effects::DIMMED) {
135            modifier |= Self::DIM;
136        }
137        if effect.contains(Effects::ITALIC) {
138            modifier |= Self::ITALIC;
139        }
140        if effect.contains(Effects::UNDERLINE)
141            || effect.contains(Effects::DOUBLE_UNDERLINE)
142            || effect.contains(Effects::CURLY_UNDERLINE)
143            || effect.contains(Effects::DOTTED_UNDERLINE)
144            || effect.contains(Effects::DASHED_UNDERLINE)
145        {
146            modifier |= Self::UNDERLINED;
147        }
148        if effect.contains(Effects::BLINK) {
149            modifier |= Self::SLOW_BLINK;
150        }
151        if effect.contains(Effects::INVERT) {
152            modifier |= Self::REVERSED;
153        }
154        if effect.contains(Effects::HIDDEN) {
155            modifier |= Self::HIDDEN;
156        }
157        if effect.contains(Effects::STRIKETHROUGH) {
158            modifier |= Self::CROSSED_OUT;
159        }
160        modifier
161    }
162}
163
164impl From<Modifier> for Effects {
165    fn from(modifier: Modifier) -> Self {
166        let mut effects = Self::new();
167        if modifier.contains(Modifier::BOLD) {
168            effects |= Self::BOLD;
169        }
170        if modifier.contains(Modifier::DIM) {
171            effects |= Self::DIMMED;
172        }
173        if modifier.contains(Modifier::ITALIC) {
174            effects |= Self::ITALIC;
175        }
176        if modifier.contains(Modifier::UNDERLINED) {
177            effects |= Self::UNDERLINE;
178        }
179        if modifier.contains(Modifier::SLOW_BLINK) || modifier.contains(Modifier::RAPID_BLINK) {
180            effects |= Self::BLINK;
181        }
182        if modifier.contains(Modifier::REVERSED) {
183            effects |= Self::INVERT;
184        }
185        if modifier.contains(Modifier::HIDDEN) {
186            effects |= Self::HIDDEN;
187        }
188        if modifier.contains(Modifier::CROSSED_OUT) {
189            effects |= Self::STRIKETHROUGH;
190        }
191        effects
192    }
193}
194
195impl From<anstyle::Style> for Style {
196    fn from(style: anstyle::Style) -> Self {
197        Self {
198            fg: style.get_fg_color().map(Color::from),
199            bg: style.get_bg_color().map(Color::from),
200            #[cfg(feature = "underline-color")]
201            underline_color: style.get_underline_color().map(Color::from),
202            add_modifier: style.get_effects().into(),
203            ..Default::default()
204        }
205    }
206}
207
208impl From<Style> for anstyle::Style {
209    fn from(style: Style) -> Self {
210        let mut anstyle_style = Self::new();
211        if let Some(fg) = style.fg {
212            let fg = anstyle::Color::from(fg);
213            anstyle_style = anstyle_style.fg_color(Some(fg));
214        }
215        if let Some(bg) = style.bg {
216            let bg = anstyle::Color::from(bg);
217            anstyle_style = anstyle_style.bg_color(Some(bg));
218        }
219        #[cfg(feature = "underline-color")]
220        if let Some(underline) = style.underline_color {
221            let underline = anstyle::Color::from(underline);
222            anstyle_style = anstyle_style.underline_color(Some(underline));
223        }
224        anstyle_style = anstyle_style.effects(style.add_modifier.into());
225        anstyle_style
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn anstyle_to_color() {
235        let anstyle_color = Ansi256Color(42);
236        let color = Color::from(anstyle_color);
237        assert_eq!(color, Color::Indexed(42));
238    }
239
240    #[test]
241    fn color_to_ansi256color() {
242        let color = Color::Indexed(42);
243        let anstyle_color = Ansi256Color::try_from(color);
244        assert_eq!(anstyle_color, Ok(Ansi256Color(42)));
245    }
246
247    #[test]
248    fn color_to_ansi256color_error() {
249        let color = Color::Rgb(0, 0, 0);
250        let anstyle_color = Ansi256Color::try_from(color);
251        assert_eq!(anstyle_color, Err(TryFromColorError::Ansi256));
252    }
253
254    #[test]
255    fn ansi_color_to_color() {
256        let ansi_color = AnsiColor::Red;
257        let color = Color::from(ansi_color);
258        assert_eq!(color, Color::Red);
259    }
260
261    #[test]
262    fn color_to_ansicolor() {
263        let color = Color::Red;
264        let ansi_color = AnsiColor::try_from(color);
265        assert_eq!(ansi_color, Ok(AnsiColor::Red));
266    }
267
268    #[test]
269    fn color_to_ansicolor_error() {
270        let color = Color::Rgb(0, 0, 0);
271        let ansi_color = AnsiColor::try_from(color);
272        assert_eq!(ansi_color, Err(TryFromColorError::Ansi));
273    }
274
275    #[test]
276    fn rgb_color_to_color() {
277        let rgb_color = RgbColor(255, 0, 0);
278        let color = Color::from(rgb_color);
279        assert_eq!(color, Color::Rgb(255, 0, 0));
280    }
281
282    #[test]
283    fn color_to_rgbcolor() {
284        let color = Color::Rgb(255, 0, 0);
285        let rgb_color = RgbColor::try_from(color);
286        assert_eq!(rgb_color, Ok(RgbColor(255, 0, 0)));
287    }
288
289    #[test]
290    fn color_to_rgbcolor_error() {
291        let color = Color::Indexed(42);
292        let rgb_color = RgbColor::try_from(color);
293        assert_eq!(rgb_color, Err(TryFromColorError::RgbColor));
294    }
295
296    #[test]
297    fn effects_to_modifier() {
298        let effects = Effects::BOLD | Effects::ITALIC;
299        let modifier = Modifier::from(effects);
300        assert!(modifier.contains(Modifier::BOLD));
301        assert!(modifier.contains(Modifier::ITALIC));
302    }
303
304    #[test]
305    fn modifier_to_effects() {
306        let modifier = Modifier::BOLD | Modifier::ITALIC;
307        let effects = Effects::from(modifier);
308        assert!(effects.contains(Effects::BOLD));
309        assert!(effects.contains(Effects::ITALIC));
310    }
311
312    #[test]
313    fn anstyle_style_to_style() {
314        let anstyle_style = anstyle::Style::new()
315            .fg_color(Some(anstyle::Color::Ansi(AnsiColor::Red)))
316            .bg_color(Some(anstyle::Color::Ansi(AnsiColor::Blue)))
317            .underline_color(Some(anstyle::Color::Ansi(AnsiColor::Green)))
318            .effects(Effects::BOLD | Effects::ITALIC);
319        let style = Style::from(anstyle_style);
320        assert_eq!(style.fg, Some(Color::Red));
321        assert_eq!(style.bg, Some(Color::Blue));
322        #[cfg(feature = "underline-color")]
323        assert_eq!(style.underline_color, Some(Color::Green));
324        assert!(style.add_modifier.contains(Modifier::BOLD));
325        assert!(style.add_modifier.contains(Modifier::ITALIC));
326    }
327
328    #[test]
329    fn style_to_anstyle_style() {
330        let style = Style {
331            fg: Some(Color::Red),
332            bg: Some(Color::Blue),
333            #[cfg(feature = "underline-color")]
334            underline_color: Some(Color::Green),
335            add_modifier: Modifier::BOLD | Modifier::ITALIC,
336            ..Default::default()
337        };
338        let anstyle_style = anstyle::Style::from(style);
339        assert_eq!(
340            anstyle_style.get_fg_color(),
341            Some(anstyle::Color::Ansi(AnsiColor::Red))
342        );
343        assert_eq!(
344            anstyle_style.get_bg_color(),
345            Some(anstyle::Color::Ansi(AnsiColor::Blue))
346        );
347        #[cfg(feature = "underline-color")]
348        assert_eq!(
349            anstyle_style.get_underline_color(),
350            Some(anstyle::Color::Ansi(AnsiColor::Green))
351        );
352        assert!(anstyle_style.get_effects().contains(Effects::BOLD));
353        assert!(anstyle_style.get_effects().contains(Effects::ITALIC));
354    }
355
356    #[test]
357    #[should_panic(expected = "Color::Reset has no equivalent in anstyle")]
358    fn converting_reset_panics_explicitly() {
359        let _ = anstyle::Color::from(Color::Reset);
360    }
361}