1use std::{fmt, ops::BitOr};
7
8use crate::{
9 grid::{
10 ansi::{ANSIBuf, ANSIFmt, ANSIStr as StaticColor},
11 config::{ColoredConfig, Entity},
12 },
13 settings::{CellOption, TableOption},
14};
15
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub struct Color {
40 inner: ColorInner,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
44enum ColorInner {
45 Static(StaticColor<'static>),
46 Buf(ANSIBuf),
47}
48
49#[rustfmt::skip]
50impl Color {
51 pub const FG_BLACK: Self = Self::new_static("\u{1b}[30m", "\u{1b}[39m");
55 pub const FG_BLUE: Self = Self::new_static("\u{1b}[34m", "\u{1b}[39m");
59 pub const FG_BRIGHT_BLACK: Self = Self::new_static("\u{1b}[90m", "\u{1b}[39m");
63 pub const FG_BRIGHT_BLUE: Self = Self::new_static("\u{1b}[94m", "\u{1b}[39m");
67 pub const FG_BRIGHT_CYAN: Self = Self::new_static("\u{1b}[96m", "\u{1b}[39m");
71 pub const FG_BRIGHT_GREEN: Self = Self::new_static("\u{1b}[92m", "\u{1b}[39m");
75 pub const FG_BRIGHT_MAGENTA: Self = Self::new_static("\u{1b}[95m", "\u{1b}[39m");
79 pub const FG_BRIGHT_RED: Self = Self::new_static("\u{1b}[91m", "\u{1b}[39m");
83 pub const FG_BRIGHT_WHITE: Self = Self::new_static("\u{1b}[97m", "\u{1b}[39m");
87 pub const FG_BRIGHT_YELLOW: Self = Self::new_static("\u{1b}[93m", "\u{1b}[39m");
91 pub const FG_CYAN: Self = Self::new_static("\u{1b}[36m", "\u{1b}[39m");
95 pub const FG_GREEN: Self = Self::new_static("\u{1b}[32m", "\u{1b}[39m");
99 pub const FG_MAGENTA: Self = Self::new_static("\u{1b}[35m", "\u{1b}[39m");
103 pub const FG_RED: Self = Self::new_static("\u{1b}[31m", "\u{1b}[39m");
107 pub const FG_WHITE: Self = Self::new_static("\u{1b}[37m", "\u{1b}[39m");
111 pub const FG_YELLOW: Self = Self::new_static("\u{1b}[33m", "\u{1b}[39m");
115 pub const BG_BLACK: Self = Self::new_static("\u{1b}[40m", "\u{1b}[49m");
119 pub const BG_BLUE: Self = Self::new_static("\u{1b}[44m", "\u{1b}[49m");
123 pub const BG_BRIGHT_BLACK: Self = Self::new_static("\u{1b}[100m", "\u{1b}[49m");
127 pub const BG_BRIGHT_BLUE: Self = Self::new_static("\u{1b}[104m", "\u{1b}[49m");
131 pub const BG_BRIGHT_CYAN: Self = Self::new_static("\u{1b}[106m", "\u{1b}[49m");
135 pub const BG_BRIGHT_GREEN: Self = Self::new_static("\u{1b}[102m", "\u{1b}[49m");
139 pub const BG_BRIGHT_MAGENTA: Self = Self::new_static("\u{1b}[105m", "\u{1b}[49m");
143 pub const BG_BRIGHT_RED: Self = Self::new_static("\u{1b}[101m", "\u{1b}[49m");
147 pub const BG_BRIGHT_WHITE: Self = Self::new_static("\u{1b}[107m", "\u{1b}[49m");
151 pub const BG_BRIGHT_YELLOW: Self = Self::new_static("\u{1b}[103m", "\u{1b}[49m");
155 pub const BG_CYAN: Self = Self::new_static("\u{1b}[46m", "\u{1b}[49m");
159 pub const BG_GREEN: Self = Self::new_static("\u{1b}[42m", "\u{1b}[49m");
163 pub const BG_MAGENTA: Self = Self::new_static("\u{1b}[45m", "\u{1b}[49m");
167 pub const BG_RED: Self = Self::new_static("\u{1b}[41m", "\u{1b}[49m");
171 pub const BG_WHITE: Self = Self::new_static("\u{1b}[47m", "\u{1b}[49m");
175 pub const BG_YELLOW: Self = Self::new_static("\u{1b}[43m", "\u{1b}[49m");
179 pub const BOLD: Self = Self::new_static("\u{1b}[1m", "\u{1b}[22m");
183 pub const UNDERLINE: Self = Self::new_static("\u{1b}[4m", "\u{1b}[24m");
187}
188
189impl Color {
190 pub fn new<P, S>(prefix: P, suffix: S) -> Self
193 where
194 P: Into<String>,
195 S: Into<String>,
196 {
197 let color = ANSIBuf::new(prefix, suffix);
198 let inner = ColorInner::Buf(color);
199
200 Self { inner }
201 }
202
203 pub const fn empty() -> Self {
205 Self::new_static("", "")
206 }
207
208 pub fn get_prefix(&self) -> &str {
210 match &self.inner {
211 ColorInner::Static(color) => color.get_prefix(),
212 ColorInner::Buf(color) => color.get_prefix(),
213 }
214 }
215
216 pub fn get_suffix(&self) -> &str {
218 match &self.inner {
219 ColorInner::Static(color) => color.get_suffix(),
220 ColorInner::Buf(color) => color.get_suffix(),
221 }
222 }
223
224 pub fn as_ansi_str(&self) -> Option<StaticColor<'static>> {
226 match self.inner {
227 ColorInner::Static(value) => Some(value),
228 ColorInner::Buf(_) => None,
229 }
230 }
231
232 #[cfg(feature = "ansi")]
239 pub fn parse<S>(text: S) -> Self
240 where
241 S: AsRef<str>,
242 {
243 std::convert::TryFrom::try_from(text.as_ref()).unwrap()
244 }
245
246 pub fn rgb_fg(r: u8, g: u8, b: u8) -> Self {
248 Self {
249 inner: ColorInner::Buf(ANSIBuf::new(
250 format!("\u{1b}[38;2;{};{};{}m", r, g, b),
251 "\u{1b}[39m",
252 )),
253 }
254 }
255
256 pub fn rgb_bg(r: u8, g: u8, b: u8) -> Self {
260 Self {
261 inner: ColorInner::Buf(ANSIBuf::new(
262 format!("\u{1b}[48;2;{};{};{}m", r, g, b),
263 "\u{1b}[49m",
264 )),
265 }
266 }
267
268 pub fn colorize<S>(&self, text: S) -> String
270 where
271 S: AsRef<str>,
272 {
273 let mut buf = String::new();
274 for (i, line) in text.as_ref().lines().enumerate() {
275 if i > 0 {
276 buf.push('\n');
277 }
278
279 buf.push_str(self.get_prefix());
280 buf.push_str(line);
281 buf.push_str(self.get_suffix());
282 }
283
284 buf
285 }
286
287 const fn new_static(prefix: &'static str, suffix: &'static str) -> Self {
288 let color = StaticColor::new(prefix, suffix);
289 let inner = ColorInner::Static(color);
290
291 Self { inner }
292 }
293}
294
295impl Default for Color {
296 fn default() -> Self {
297 Self {
298 inner: ColorInner::Static(StaticColor::default()),
299 }
300 }
301}
302
303impl From<Color> for ANSIBuf {
304 fn from(color: Color) -> Self {
305 match color.inner {
306 ColorInner::Static(color) => ANSIBuf::from(color),
307 ColorInner::Buf(color) => color,
308 }
309 }
310}
311
312impl From<ANSIBuf> for Color {
313 fn from(color: ANSIBuf) -> Self {
314 Self {
315 inner: ColorInner::Buf(color),
316 }
317 }
318}
319
320impl From<StaticColor<'static>> for Color {
321 fn from(color: StaticColor<'static>) -> Self {
322 Self {
323 inner: ColorInner::Static(color),
324 }
325 }
326}
327
328impl BitOr for Color {
329 type Output = Color;
330
331 fn bitor(self, rhs: Self) -> Self::Output {
332 let l_prefix = self.get_prefix();
333 let l_suffix = self.get_suffix();
334 let r_prefix = rhs.get_prefix();
335 let r_suffix = rhs.get_suffix();
336
337 let mut prefix = l_prefix.to_string();
338 if l_prefix != r_prefix {
339 prefix.push_str(r_prefix);
340 }
341
342 let mut suffix = l_suffix.to_string();
343 if l_suffix != r_suffix {
344 suffix.push_str(r_suffix);
345 }
346
347 Self::new(prefix, suffix)
348 }
349}
350
351#[cfg(feature = "ansi")]
352impl std::convert::TryFrom<&str> for Color {
353 type Error = ();
354
355 fn try_from(value: &str) -> Result<Self, Self::Error> {
356 let buf = ANSIBuf::try_from(value)?;
357
358 Ok(Color {
359 inner: ColorInner::Buf(buf),
360 })
361 }
362}
363
364#[cfg(feature = "ansi")]
365impl std::convert::TryFrom<String> for Color {
366 type Error = ();
367
368 fn try_from(value: String) -> Result<Self, Self::Error> {
369 let buf = ANSIBuf::try_from(value)?;
370
371 Ok(Color {
372 inner: ColorInner::Buf(buf),
373 })
374 }
375}
376
377impl<R, D> TableOption<R, ColoredConfig, D> for Color {
378 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
379 let color = self.into();
380 let _ = cfg.set_color(Entity::Global, color);
381 }
382
383 fn hint_change(&self) -> Option<Entity> {
384 None
385 }
386}
387
388impl<R> CellOption<R, ColoredConfig> for Color {
389 fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
390 let color = self.into();
391 let _ = cfg.set_color(entity, color);
392 }
393
394 fn hint_change(&self) -> Option<Entity> {
395 None
396 }
397}
398
399impl<R> CellOption<R, ColoredConfig> for &Color {
400 fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
401 let color = self.clone().into();
402 let _ = cfg.set_color(entity, color);
403 }
404
405 fn hint_change(&self) -> Option<Entity> {
406 None
407 }
408}
409
410impl ANSIFmt for Color {
411 fn fmt_ansi_prefix<W: fmt::Write>(&self, f: &mut W) -> fmt::Result {
412 match &self.inner {
413 ColorInner::Static(color) => color.fmt_ansi_prefix(f),
414 ColorInner::Buf(color) => color.fmt_ansi_prefix(f),
415 }
416 }
417
418 fn fmt_ansi_suffix<W: fmt::Write>(&self, f: &mut W) -> fmt::Result {
419 match &self.inner {
420 ColorInner::Static(color) => color.fmt_ansi_suffix(f),
421 ColorInner::Buf(color) => color.fmt_ansi_suffix(f),
422 }
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 #[cfg(feature = "ansi")]
431 use std::convert::TryFrom;
432
433 #[test]
434 fn test_xor_operation() {
435 assert_eq!(
436 Color::FG_BLACK | Color::FG_BLUE,
437 Color::new("\u{1b}[30m\u{1b}[34m", "\u{1b}[39m")
438 );
439 assert_eq!(
440 Color::FG_BRIGHT_GREEN | Color::BG_BLUE,
441 Color::new("\u{1b}[92m\u{1b}[44m", "\u{1b}[39m\u{1b}[49m")
442 );
443 assert_eq!(
444 Color::new("...", "!!!") | Color::new("@@@", "###"),
445 Color::new("...@@@", "!!!###")
446 );
447 assert_eq!(
448 Color::new("...", "!!!") | Color::new("@@@", "###") | Color::new("$$$", "%%%"),
449 Color::new("...@@@$$$", "!!!###%%%")
450 );
451 }
452
453 #[cfg(feature = "ansi")]
454 #[test]
455 fn test_try_from() {
456 assert_eq!(Color::try_from(""), Err(()));
457 assert_eq!(
458 Color::try_from("\u{1b}[31m\u{1b}[42m\u{1b}[39m\u{1b}[49m"),
459 Err(())
460 );
461 assert_eq!(Color::try_from("."), Ok(Color::new("", "")));
462 assert_eq!(Color::try_from("...."), Ok(Color::new("", "")));
463 assert_eq!(
464 Color::try_from(String::from("\u{1b}[31m\u{1b}[42m.\u{1b}[39m\u{1b}[49m")),
465 Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m"))
466 );
467 assert_eq!(
468 Color::try_from(String::from("\u{1b}[31m\u{1b}[42m...\u{1b}[39m\u{1b}[49m")),
469 Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m"))
470 );
471 assert_eq!(
472 Color::try_from(String::from(
473 "\u{1b}[31m\u{1b}[42m.\n.\n.\u{1b}[39m\u{1b}[49m"
474 )),
475 Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m"))
476 );
477 assert_eq!(
478 Color::try_from(String::from(
479 "\u{1b}[31m\u{1b}[42m.\n.\n.\n\u{1b}[39m\u{1b}[49m"
480 )),
481 Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m"))
482 );
483 assert_eq!(
484 Color::try_from(String::from("\u{1b}[31m\u{1b}[42m\n\u{1b}[39m\u{1b}[49m")),
485 Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m"))
486 );
487 }
488
489 #[test]
490 fn test_rgb_color() {
491 assert_eq!(
492 Color::rgb_bg(255, 255, 255),
493 Color::new("\u{1b}[48;2;255;255;255m", "\u{1b}[49m")
494 );
495 assert_eq!(
496 Color::rgb_bg(0, 255, 128),
497 Color::new("\u{1b}[48;2;0;255;128m", "\u{1b}[49m")
498 );
499
500 assert_eq!(
501 Color::rgb_fg(0, 255, 128),
502 Color::new("\u{1b}[38;2;0;255;128m", "\u{1b}[39m")
503 );
504 assert_eq!(
505 Color::rgb_fg(255, 255, 255),
506 Color::new("\u{1b}[38;2;255;255;255m", "\u{1b}[39m")
507 );
508
509 assert_eq!(
510 Color::rgb_bg(255, 255, 255) | Color::rgb_fg(0, 0, 0),
511 Color::new(
512 "\u{1b}[48;2;255;255;255m\u{1b}[38;2;0;0;0m",
513 "\u{1b}[49m\u{1b}[39m"
514 )
515 )
516 }
517}