Skip to main content

rusty_rich/
color.rs

1//! Color system — equivalent to Rich's `color.py`.
2//!
3//! Supports ANSI standard (16) colors, 8-bit (256) colors, and 24-bit true
4//! color with automatic downgrade. Includes 256 named color constants, hex/RGB
5//! constructors, and color blending utilities.
6//!
7//! # Quick Example
8//!
9//! ```rust
10//! use rusty_rich::Color;
11//!
12//! // Named colors — 256 ANSI palette
13//! let red = Color::parse("red").unwrap();
14//! let hot_pink = Color::parse("hot_pink").unwrap();
15//!
16//! // Hex and RGB
17//! let orange = Color::from_hex("#FF6600").unwrap();
18//! let custom = Color::from_rgb(100, 200, 50);
19//! ```
20//!
21//! # Color Systems
22//!
23//! [`ColorSystem`] describes what the terminal supports:
24//!
25//! - [`ColorSystem::Standard`] — 16 ANSI colors
26//! - [`ColorSystem::EightBit`] — 256-color palette
27//! - [`ColorSystem::TrueColor`] — 24-bit RGB
28//!
29//! Use [`Color::downgrade`] to convert a color to a lower color system.
30//!
31//! # Named Color Map
32//!
33//! [`Color::from_ansi_name`] and [`Color::parse`] look up names in the
34//! 256-entry ANSI palette. Both `grey`/`gray` spellings are supported.
35
36use std::fmt;
37
38/// An RGB color triplet — equivalent to Rich's `ColorTriplet`.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
40pub struct ColorTriplet {
41    pub red: u8,
42    pub green: u8,
43    pub blue: u8,
44}
45
46impl ColorTriplet {
47    /// Create a new `ColorTriplet` from red, green, and blue components.
48    pub const fn new(red: u8, green: u8, blue: u8) -> Self {
49        Self { red, green, blue }
50    }
51}
52
53impl fmt::Display for ColorTriplet {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        write!(f, "#{:02x}{:02x}{:02x}", self.red, self.green, self.blue)
56    }
57}
58use std::hash::Hash;
59
60
61// ---------------------------------------------------------------------------
62// ColorSystem — what the terminal supports
63// ---------------------------------------------------------------------------
64
65/// The color system supported by the terminal.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
67pub enum ColorSystem {
68    /// 3-bit / 4-bit ANSI standard (8/16 colors)
69    Standard = 1,
70    /// 8-bit (256 colors)
71    EightBit = 2,
72    /// 24-bit true color
73    TrueColor = 3,
74}
75
76impl fmt::Display for ColorSystem {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        match self {
79            Self::Standard => write!(f, "standard"),
80            Self::EightBit => write!(f, "256"),
81            Self::TrueColor => write!(f, "truecolor"),
82        }
83    }
84}
85
86// ---------------------------------------------------------------------------
87// ColorType
88// ---------------------------------------------------------------------------
89
90/// How the color value is stored internally.
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92pub enum ColorType {
93    /// No color / inherit
94    Default,
95    /// One of the 16 ANSI named colors
96    Standard,
97    /// 8-bit palette entry (0–255)
98    EightBit,
99    /// 24-bit true color
100    TrueColor,
101}
102
103// ---------------------------------------------------------------------------
104// ANSI_COLOR_NAMES — maps name → index in the 256-color table
105// ---------------------------------------------------------------------------
106
107/// Full set of named ANSI colors. Use `Color::name_to_index()` to look up.
108
109// ---------------------------------------------------------------------------
110// Color
111// ---------------------------------------------------------------------------
112
113/// A terminal color.
114///
115/// Can be one of: default (inherit), a standard ANSI name, an 8-bit palette
116/// index, or a 24-bit true-color RGB triple.
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
118pub struct Color {
119    pub(crate) color_type: ColorType,
120    /// For Standard: the ANSI index (0–15).
121    /// For EightBit: the palette index (0–255).
122    pub(crate) number: Option<u8>,
123    /// For TrueColor: the RGB triple.
124    pub(crate) triplet: Option<(u8, u8, u8)>,
125    /// Optional name string (kept for round-tripping).
126    pub(crate) name: Option<&'static str>,
127}
128
129impl Color {
130    // -- constructors -------------------------------------------------------
131
132    /// Create a "default" color (inherit from parent).
133    pub const fn default() -> Self {
134        Self {
135            color_type: ColorType::Default,
136            number: None,
137            triplet: None,
138            name: None,
139        }
140    }
141
142    /// Create from an ANSI standard name (e.g. "red", "bright_blue").
143    pub fn from_ansi_name(name: &str) -> Option<Self> {
144        let n = ANSI_NAME_MAP.get(name).copied()?;
145        Some(Self {
146            color_type: if n < 16 {
147                ColorType::Standard
148            } else {
149                ColorType::EightBit
150            },
151            number: Some(n),
152            triplet: None,
153            name: None,
154        })
155    }
156
157    /// Create from an 8-bit (256) palette index.
158    pub fn from_8bit(n: u8) -> Self {
159        Self {
160            color_type: ColorType::EightBit,
161            number: Some(n),
162            triplet: None,
163            name: None,
164        }
165    }
166
167    /// Create from 24-bit RGB components.
168    pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
169        Self {
170            color_type: ColorType::TrueColor,
171            number: None,
172            triplet: Some((r, g, b)),
173            name: None,
174        }
175    }
176
177    /// Create from a hex string like "#ff0000" or "ff0000".
178    pub fn from_hex(hex: &str) -> Result<Self, ColorParseError> {
179        let hex = hex.trim_start_matches('#');
180        if hex.len() != 6 {
181            return Err(ColorParseError::InvalidHex(hex.to_string()));
182        }
183        let r = u8::from_str_radix(&hex[0..2], 16)
184            .map_err(|_| ColorParseError::InvalidHex(hex.to_string()))?;
185        let g = u8::from_str_radix(&hex[2..4], 16)
186            .map_err(|_| ColorParseError::InvalidHex(hex.to_string()))?;
187        let b = u8::from_str_radix(&hex[4..6], 16)
188            .map_err(|_| ColorParseError::InvalidHex(hex.to_string()))?;
189        Ok(Self::from_rgb(r, g, b))
190    }
191
192    /// Parse a color from a string (name, hex, or "default").
193    pub fn parse(s: &str) -> Result<Self, ColorParseError> {
194        let lower = s.to_lowercase();
195        if lower == "default" || lower.is_empty() {
196            return Ok(Self::default());
197        }
198        if let Some(c) = Self::from_ansi_name(&lower) {
199            return Ok(c);
200        }
201        if lower.starts_with('#') || lower.len() == 6 {
202            return Self::from_hex(&lower);
203        }
204        // Try "color<N>" format for 8-bit
205        if lower.starts_with("color") {
206            if let Ok(n) = lower[5..].parse::<u8>() {
207                return Ok(Self::from_8bit(n));
208            }
209        }
210        Err(ColorParseError::UnknownName(lower))
211    }
212
213    // -- queries ------------------------------------------------------------
214
215    /// Is this the default/inherit color?
216    pub fn is_default(&self) -> bool {
217        matches!(self.color_type, ColorType::Default)
218    }
219
220    /// Get the RGB triplet if available (computes it for named/8-bit colors
221    /// by looking up the palette).
222    pub fn get_truecolor(&self, theme: &TerminalTheme) -> (u8, u8, u8) {
223        match self.color_type {
224            ColorType::TrueColor => self.triplet.unwrap(),
225            ColorType::Default => theme.foreground_color,
226            _ => {
227                if let Some(n) = self.number {
228                    if let Some(&[r, g, b]) = EIGHT_BIT_PALETTE.get(n as usize) {
229                        return (r, g, b);
230                    }
231                }
232                theme.foreground_color
233            }
234        }
235    }
236
237    /// Downgrade this color to the given color system.
238    pub fn downgrade(&self, system: ColorSystem) -> Self {
239        match system {
240            ColorSystem::TrueColor => *self,
241            ColorSystem::EightBit => {
242                if matches!(self.color_type, ColorType::TrueColor) {
243                    let (r, g, b) = self.triplet.unwrap();
244                    let idx = rgb_to_8bit(r, g, b);
245                    Self::from_8bit(idx)
246                } else {
247                    *self
248                }
249            }
250            ColorSystem::Standard => {
251                if matches!(self.color_type, ColorType::TrueColor) {
252                    let (r, g, b) = self.triplet.unwrap();
253                    let idx = rgb_to_standard(r, g, b);
254                    Self {
255                        color_type: ColorType::Standard,
256                        number: Some(idx),
257                        triplet: None,
258                        name: None,
259                    }
260                } else if let Some(n) = self.number {
261                    if n >= 16 {
262                        let idx = n % 16;
263                        Self {
264                            color_type: ColorType::Standard,
265                            number: Some(idx),
266                            triplet: None,
267                            name: None,
268                        }
269                    } else {
270                        *self
271                    }
272                } else {
273                    *self
274                }
275            }
276        }
277    }
278}
279
280impl fmt::Display for Color {
281    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282        match self.color_type {
283            ColorType::Default => write!(f, "default"),
284            ColorType::Standard => write!(f, "{}", STANDARD_COLOR_NAMES[self.number.unwrap() as usize]),
285            ColorType::EightBit => write!(f, "color({})", self.number.unwrap()),
286            ColorType::TrueColor => {
287                let (r, g, b) = self.triplet.unwrap();
288                write!(f, "#{:02x}{:02x}{:02x}", r, g, b)
289            }
290        }
291    }
292}
293
294// ---------------------------------------------------------------------------
295// ColorParseError
296// ---------------------------------------------------------------------------
297
298/// Errors that can occur when parsing a color from a string.
299#[derive(Debug, Clone)]
300pub enum ColorParseError {
301    /// The color name was not found in the ANSI palette.
302    UnknownName(String),
303    /// The hex string was not a valid 6-digit RGB value.
304    InvalidHex(String),
305}
306
307impl fmt::Display for ColorParseError {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        match self {
310            Self::UnknownName(n) => write!(f, "unknown color name: {n}"),
311            Self::InvalidHex(h) => write!(f, "invalid hex color: {h}"),
312        }
313    }
314}
315
316impl std::error::Error for ColorParseError {}
317
318// ---------------------------------------------------------------------------
319// TerminalTheme
320// ---------------------------------------------------------------------------
321
322/// Describes the terminal's default theme colors (for blending / downgrade).
323#[derive(Debug, Clone, Copy)]
324pub struct TerminalTheme {
325    pub foreground_color: (u8, u8, u8),
326    pub background_color: (u8, u8, u8),
327}
328
329impl Default for TerminalTheme {
330    fn default() -> Self {
331        Self {
332            foreground_color: (255, 255, 255),
333            background_color: (0, 0, 0),
334        }
335    }
336}
337
338// ---------------------------------------------------------------------------
339// Built-in palettes
340// ---------------------------------------------------------------------------
341
342/// Standard 16-color ANSI palette.
343pub static STANDARD_PALETTE: &[(u8, u8, u8)] = &[
344    (0, 0, 0),       // 0: black
345    (128, 0, 0),     // 1: red
346    (0, 128, 0),     // 2: green
347    (128, 128, 0),   // 3: yellow
348    (0, 0, 128),     // 4: blue
349    (128, 0, 128),   // 5: magenta
350    (0, 128, 128),   // 6: cyan
351    (192, 192, 192), // 7: white
352    (128, 128, 128), // 8: bright black
353    (255, 0, 0),     // 9: bright red
354    (0, 255, 0),     // 10: bright green
355    (255, 255, 0),   // 11: bright yellow
356    (0, 0, 255),     // 12: bright blue
357    (255, 0, 255),   // 13: bright magenta
358    (0, 255, 255),   // 14: bright cyan
359    (255, 255, 255), // 15: bright white
360];
361
362/// The 16 ANSI standard color names in palette order (black, red, ..., bright_white).
363pub static STANDARD_COLOR_NAMES: &[&str] = &[
364    "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
365    "bright_black", "bright_red", "bright_green", "bright_yellow",
366    "bright_blue", "bright_magenta", "bright_cyan", "bright_white",
367];
368
369/// The 6×6×6 color cube + greyscale ramp = 256-color palette.
370pub static EIGHT_BIT_PALETTE: Lazy<[[u8; 3]; 256]> = Lazy::new(|| {
371    let mut table = [[0u8; 3]; 256];
372    let std: [[u8; 3]; 16] = [
373        [0, 0, 0],
374        [128, 0, 0],
375        [0, 128, 0],
376        [128, 128, 0],
377        [0, 0, 128],
378        [128, 0, 128],
379        [0, 128, 128],
380        [192, 192, 192],
381        [128, 128, 128],
382        [255, 0, 0],
383        [0, 255, 0],
384        [255, 255, 0],
385        [0, 0, 255],
386        [255, 0, 255],
387        [0, 255, 255],
388        [255, 255, 255],
389    ];
390    for i in 0..16 {
391        table[i] = std[i];
392    }
393    let levels = [0u8, 95, 135, 175, 215, 255];
394    for r in 0..6 {
395        for g in 0..6 {
396            for b in 0..6 {
397                let idx = 16 + 36 * r + 6 * g + b;
398                table[idx] = [levels[r], levels[g], levels[b]];
399            }
400        }
401    }
402    for gr in 0..24 {
403        let val = (gr * 10 + 8) as u8;
404        table[232 + gr] = [val, val, val];
405    }
406    table
407});
408
409// ---------------------------------------------------------------------------
410// Color utilities
411// ---------------------------------------------------------------------------
412
413/// Convert RGB to the nearest 8-bit palette index.
414pub fn rgb_to_8bit(r: u8, g: u8, b: u8) -> u8 {
415    // Check if it's close to a greyscale value
416    let grey = ((r as u32 + g as u32 + b as u32) / 3) as u8;
417    if r == g && g == b {
418        if grey < 8 {
419            return 16; // black
420        }
421        if grey > 248 {
422            return 231; // white
423        }
424        return 232 + ((grey - 8) / 10) as u8;
425    }
426
427    // Find nearest cube color
428    let r6 = ((r as f64 / 255.0) * 5.0).round() as u8;
429    let g6 = ((g as f64 / 255.0) * 5.0).round() as u8;
430    let b6 = ((b as f64 / 255.0) * 5.0).round() as u8;
431    16 + 36 * r6 + 6 * g6 + b6
432}
433
434/// Convert RGB to nearest standard (16) ANSI color.
435pub fn rgb_to_standard(_r: u8, _g: u8, _b: u8) -> u8 {
436    // Simplified: just use the nearest by Euclidean distance
437    let mut best_idx = 0u8;
438    let mut best_dist = u32::MAX;
439    for (i, &(pr, pg, pb)) in STANDARD_PALETTE.iter().enumerate() {
440        let dr = _r as i32 - pr as i32;
441        let dg = _g as i32 - pg as i32;
442        let db = _b as i32 - pb as i32;
443        let dist = (dr * dr + dg * dg + db * db) as u32;
444        if dist < best_dist {
445            best_dist = dist;
446            best_idx = i as u8;
447        }
448    }
449    best_idx
450}
451
452/// Blend two RGB colors (like Rich's `blend_rgb`).
453pub fn blend_rgb(
454    color1: (u8, u8, u8),
455    color2: (u8, u8, u8),
456    cross_fade: f64,
457) -> (u8, u8, u8) {
458    let r = (color1.0 as f64 + (color2.0 as f64 - color1.0 as f64) * cross_fade) as u8;
459    let g = (color1.1 as f64 + (color2.1 as f64 - color1.1 as f64) * cross_fade) as u8;
460    let b = (color1.2 as f64 + (color2.2 as f64 - color1.2 as f64) * cross_fade) as u8;
461    (r, g, b)
462}
463
464/// Blend two Colors (downgrading to the supported system).
465pub fn blend_colors(
466    color1: &Color,
467    color2: &Color,
468    cross_fade: f64,
469    theme: &TerminalTheme,
470) -> Color {
471    let rgb1 = color1.get_truecolor(theme);
472    let rgb2 = color2.get_truecolor(theme);
473    let blended = blend_rgb(rgb1, rgb2, cross_fade);
474    Color::from_rgb(blended.0, blended.1, blended.2)
475}
476
477// ---------------------------------------------------------------------------
478// Phf map workaround — since we can't use phf easily, use a lazy static
479// ---------------------------------------------------------------------------
480
481// We use a simple linear scan backed by a slice — fast enough for the small
482// set of named colors.
483
484use once_cell::sync::Lazy;
485use std::collections::HashMap;
486
487static ANSI_NAME_MAP: Lazy<HashMap<&'static str, u8>> = Lazy::new(|| {
488    let mut m = HashMap::new();
489    // Standard ANSI (0-15)
490    m.insert("black", 0u8);
491    m.insert("red", 1u8);
492    m.insert("green", 2u8);
493    m.insert("yellow", 3u8);
494    m.insert("blue", 4u8);
495    m.insert("magenta", 5u8);
496    m.insert("cyan", 6u8);
497    m.insert("white", 7u8);
498    m.insert("bright_black", 8u8);
499    m.insert("grey", 8u8);
500    m.insert("gray", 8u8);
501    m.insert("bright_red", 9u8);
502    m.insert("bright_green", 10u8);
503    m.insert("bright_yellow", 11u8);
504    m.insert("bright_blue", 12u8);
505    m.insert("bright_magenta", 13u8);
506    m.insert("bright_cyan", 14u8);
507    m.insert("bright_white", 15u8);
508    // Color cube (16-231)
509    m.insert("grey0", 16u8);
510    m.insert("gray0", 16u8);
511    m.insert("navy_blue", 17u8);
512    m.insert("dark_blue", 18u8);
513    m.insert("blue3", 20u8);
514    m.insert("blue1", 21u8);
515    m.insert("dark_green", 22u8);
516    m.insert("deep_sky_blue4", 25u8);
517    m.insert("dodger_blue3", 26u8);
518    m.insert("dodger_blue2", 27u8);
519    m.insert("green4", 28u8);
520    m.insert("spring_green4", 29u8);
521    m.insert("turquoise4", 30u8);
522    m.insert("deep_sky_blue3", 32u8);
523    m.insert("dodger_blue1", 33u8);
524    m.insert("dark_cyan", 36u8);
525    m.insert("light_sea_green", 37u8);
526    m.insert("deep_sky_blue2", 38u8);
527    m.insert("deep_sky_blue1", 39u8);
528    m.insert("green3", 40u8);
529    m.insert("spring_green3", 41u8);
530    m.insert("cyan3", 43u8);
531    m.insert("dark_turquoise", 44u8);
532    m.insert("turquoise2", 45u8);
533    m.insert("green1", 46u8);
534    m.insert("spring_green2", 47u8);
535    m.insert("spring_green1", 48u8);
536    m.insert("medium_spring_green", 49u8);
537    m.insert("cyan2", 50u8);
538    m.insert("cyan1", 51u8);
539    m.insert("purple4", 55u8);
540    m.insert("purple3", 56u8);
541    m.insert("blue_violet", 57u8);
542    m.insert("grey37", 59u8);
543    m.insert("gray37", 59u8);
544    m.insert("medium_purple4", 60u8);
545    m.insert("slate_blue3", 62u8);
546    m.insert("royal_blue1", 63u8);
547    m.insert("chartreuse4", 64u8);
548    m.insert("pale_turquoise4", 66u8);
549    m.insert("steel_blue", 67u8);
550    m.insert("steel_blue3", 68u8);
551    m.insert("cornflower_blue", 69u8);
552    m.insert("dark_sea_green4", 71u8);
553    m.insert("dark_sea_green", 71u8);
554    m.insert("cadet_blue", 73u8);
555    m.insert("sky_blue3", 74u8);
556    m.insert("chartreuse3", 76u8);
557    m.insert("sea_green3", 78u8);
558    m.insert("aquamarine3", 79u8);
559    m.insert("medium_turquoise", 80u8);
560    m.insert("steel_blue1", 81u8);
561    m.insert("sea_green2", 83u8);
562    m.insert("sea_green1", 85u8);
563    m.insert("dark_slate_gray2", 87u8);
564    m.insert("dark_red", 88u8);
565    m.insert("dark_magenta", 91u8);
566    m.insert("orange4", 94u8);
567    m.insert("light_pink4", 95u8);
568    m.insert("plum4", 96u8);
569    m.insert("medium_purple3", 98u8);
570    m.insert("slate_blue1", 99u8);
571    m.insert("wheat4", 101u8);
572    m.insert("grey53", 102u8);
573    m.insert("gray53", 102u8);
574    m.insert("light_slate_grey", 103u8);
575    m.insert("light_slate_gray", 103u8);
576    m.insert("medium_purple", 104u8);
577    m.insert("light_slate_blue", 105u8);
578    m.insert("yellow4", 106u8);
579    m.insert("dark_olive_green3", 110u8); // adjusted for gap
580    m.insert("light_sky_blue3", 110u8);
581    m.insert("sky_blue2", 111u8);
582    m.insert("chartreuse2", 112u8);
583    m.insert("pale_green3", 114u8);
584    m.insert("dark_slate_gray3", 116u8);
585    m.insert("sky_blue1", 117u8);
586    m.insert("chartreuse1", 118u8);
587    m.insert("light_green", 120u8);
588    m.insert("aquamarine1", 122u8);
589    m.insert("dark_slate_gray1", 123u8);
590    m.insert("deep_pink4", 125u8);
591    m.insert("medium_violet_red", 126u8);
592    m.insert("dark_violet", 128u8);
593    m.insert("purple", 129u8);
594    m.insert("medium_orchid3", 133u8);
595    m.insert("medium_orchid", 134u8);
596    m.insert("dark_goldenrod", 136u8);
597    m.insert("rosy_brown", 138u8);
598    m.insert("grey63", 139u8);
599    m.insert("gray63", 139u8);
600    m.insert("medium_purple2", 140u8);
601    m.insert("medium_purple1", 141u8);
602    m.insert("dark_khaki", 143u8);
603    m.insert("navajo_white3", 144u8);
604    m.insert("grey69", 145u8);
605    m.insert("gray69", 145u8);
606    m.insert("light_steel_blue3", 146u8);
607    m.insert("light_steel_blue", 147u8);
608    m.insert("dark_olive_green2", 155u8);
609    m.insert("pale_green1", 156u8);
610    m.insert("dark_sea_green2", 157u8);
611    m.insert("pale_turquoise1", 159u8);
612    m.insert("red3", 160u8);
613    m.insert("deep_pink3", 162u8);
614    m.insert("magenta3", 164u8);
615    m.insert("dark_orange3", 166u8);
616    m.insert("indian_red", 167u8);
617    m.insert("hot_pink3", 168u8);
618    m.insert("hot_pink2", 169u8);
619    m.insert("orchid", 170u8);
620    m.insert("orange3", 172u8);
621    m.insert("light_salmon3", 173u8);
622    m.insert("light_pink3", 174u8);
623    m.insert("pink3", 175u8);
624    m.insert("plum3", 176u8);
625    m.insert("violet", 177u8);
626    m.insert("gold3", 178u8);
627    m.insert("light_goldenrod3", 179u8);
628    m.insert("tan", 180u8);
629    m.insert("misty_rose3", 181u8);
630    m.insert("thistle3", 182u8);
631    m.insert("plum2", 183u8);
632    m.insert("yellow3", 184u8);
633    m.insert("khaki3", 185u8);
634    m.insert("light_yellow3", 187u8);
635    m.insert("grey84", 188u8);
636    m.insert("gray84", 188u8);
637    m.insert("light_steel_blue1", 189u8);
638    m.insert("yellow2", 190u8);
639    m.insert("dark_olive_green1", 192u8);
640    m.insert("dark_sea_green1", 193u8);
641    m.insert("honeydew2", 194u8);
642    m.insert("light_cyan1", 195u8);
643    m.insert("red1", 196u8);
644    m.insert("deep_pink2", 197u8);
645    m.insert("deep_pink1", 199u8);
646    m.insert("magenta2", 200u8);
647    m.insert("magenta1", 201u8);
648    m.insert("orange_red1", 202u8);
649    m.insert("indian_red1", 204u8);
650    m.insert("hot_pink", 206u8);
651    m.insert("medium_orchid1", 207u8);
652    m.insert("dark_orange", 208u8);
653    m.insert("salmon1", 209u8);
654    m.insert("light_coral", 210u8);
655    m.insert("pale_violet_red1", 211u8);
656    m.insert("orchid2", 212u8);
657    m.insert("orchid1", 213u8);
658    m.insert("orange1", 214u8);
659    m.insert("sandy_brown", 215u8);
660    m.insert("light_salmon1", 216u8);
661    m.insert("light_pink1", 217u8);
662    m.insert("pink1", 218u8);
663    m.insert("plum1", 219u8);
664    m.insert("gold1", 220u8);
665    m.insert("light_goldenrod2", 222u8);
666    m.insert("navajo_white1", 223u8);
667    m.insert("misty_rose1", 224u8);
668    m.insert("thistle1", 225u8);
669    m.insert("yellow1", 226u8);
670    m.insert("light_goldenrod1", 227u8);
671    m.insert("khaki1", 228u8);
672    m.insert("wheat1", 229u8);
673    m.insert("cornsilk1", 230u8);
674    m.insert("grey100", 231u8);
675    m.insert("gray100", 231u8);
676    // Greyscale (232-255)
677    m.insert("grey3", 232u8);
678    m.insert("gray3", 232u8);
679    m.insert("grey7", 233u8);
680    m.insert("gray7", 233u8);
681    m.insert("grey11", 234u8);
682    m.insert("gray11", 234u8);
683    m.insert("grey15", 235u8);
684    m.insert("gray15", 235u8);
685    m.insert("grey19", 236u8);
686    m.insert("gray19", 236u8);
687    m.insert("grey23", 237u8);
688    m.insert("gray23", 237u8);
689    m.insert("grey27", 238u8);
690    m.insert("gray27", 238u8);
691    m.insert("grey30", 239u8);
692    m.insert("gray30", 239u8);
693    m.insert("grey35", 240u8);
694    m.insert("gray35", 240u8);
695    m.insert("grey39", 241u8);
696    m.insert("gray39", 241u8);
697    m.insert("grey42", 242u8);
698    m.insert("gray42", 242u8);
699    m.insert("grey46", 243u8);
700    m.insert("gray46", 243u8);
701    m.insert("grey50", 244u8);
702    m.insert("gray50", 244u8);
703    m.insert("grey54", 245u8);
704    m.insert("gray54", 245u8);
705    m.insert("grey58", 246u8);
706    m.insert("gray58", 246u8);
707    m.insert("grey62", 247u8);
708    m.insert("gray62", 247u8);
709    m.insert("grey66", 248u8);
710    m.insert("gray66", 248u8);
711    m.insert("dark_grey", 248u8);
712    m.insert("dark_gray", 248u8);
713    m.insert("grey70", 249u8);
714    m.insert("gray70", 249u8);
715    m.insert("grey74", 250u8);
716    m.insert("gray74", 250u8);
717    m.insert("light_grey", 250u8);
718    m.insert("light_gray", 250u8);
719    m.insert("grey78", 251u8);
720    m.insert("gray78", 251u8);
721    m.insert("grey82", 252u8);
722    m.insert("gray82", 252u8);
723    m.insert("grey85", 253u8);
724    m.insert("gray85", 253u8);
725    m.insert("grey89", 254u8);
726    m.insert("gray89", 254u8);
727    m.insert("grey93", 255u8);
728    m.insert("gray93", 255u8);
729    m
730});
731
732impl Color {
733    /// Look up a named color index.
734    pub fn name_to_index(name: &str) -> Option<u8> {
735        ANSI_NAME_MAP.get(name).copied()
736    }
737}
738
739#[cfg(test)]
740mod tests {
741    use super::*;
742
743    #[test]
744    fn test_default_color() {
745        let c = Color::default();
746        assert!(c.is_default());
747    }
748
749    #[test]
750    fn test_parse_red() {
751        let c = Color::parse("red").unwrap();
752        assert_eq!(c.number, Some(1));
753    }
754
755    #[test]
756    fn test_parse_hex() {
757        let c = Color::parse("#ff0000").unwrap();
758        assert_eq!(c.triplet, Some((255, 0, 0)));
759    }
760
761    #[test]
762    fn test_rgb_to_8bit_black() {
763        assert_eq!(rgb_to_8bit(0, 0, 0), 16);
764    }
765}