1use anstyle::{Ansi256Color, AnsiColor, Effects, RgbColor};
3use thiserror::Error;
4
5use super::{Color, Modifier, Style};
6
7#[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 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}