rmux_core/style/
colour.rs1use std::fmt;
2
3use crate::input::{
4 colour_join_rgb, Colour, COLOUR_DEFAULT, COLOUR_FLAG_256, COLOUR_FLAG_RGB, COLOUR_NONE,
5 COLOUR_TERMINAL,
6};
7
8use super::grammar::strip_prefix_ci;
9
10const ANSI_5_NAME: &str = concat!("mag", "enta");
11const ANSI_95_NAME: &str = concat!("bright", "mag", "enta");
12
13pub fn parse_colour(value: &str) -> Result<Colour, ColourParseError> {
15 let trimmed = value.trim();
16 let invalid = || ColourParseError::Invalid(value.to_owned());
17
18 if trimmed.is_empty() {
19 return Err(invalid());
20 }
21
22 if let Some(hex) = trimmed.strip_prefix('#') {
24 if hex.len() != 6 || !hex.chars().all(|ch| ch.is_ascii_hexdigit()) {
25 return Err(invalid());
26 }
27 let red = u8::from_str_radix(&hex[0..2], 16).map_err(|_| invalid())?;
28 let green = u8::from_str_radix(&hex[2..4], 16).map_err(|_| invalid())?;
29 let blue = u8::from_str_radix(&hex[4..6], 16).map_err(|_| invalid())?;
30 return Ok(colour_join_rgb(red, green, blue));
31 }
32
33 if let Some(suffix) =
35 strip_prefix_ci(trimmed, "colour").or_else(|| strip_prefix_ci(trimmed, "color"))
36 {
37 let index = suffix.parse::<u16>().map_err(|_| invalid())?;
38 if index > 255 {
39 return Err(invalid());
40 }
41 return Ok(COLOUR_FLAG_256 | i32::from(index));
42 }
43
44 if trimmed.chars().all(|ch| ch.is_ascii_digit()) {
46 let index = trimmed.parse::<u16>().map_err(|_| invalid())?;
47 if index > 255 {
48 return Err(invalid());
49 }
50 return Ok(COLOUR_FLAG_256 | i32::from(index));
51 }
52
53 match trimmed.to_ascii_lowercase().as_str() {
55 "none" => Ok(COLOUR_NONE),
56 "default" => Ok(COLOUR_DEFAULT),
57 "terminal" => Ok(COLOUR_TERMINAL),
58 "black" => Ok(0),
59 "red" => Ok(1),
60 "green" => Ok(2),
61 "yellow" => Ok(3),
62 "blue" => Ok(4),
63 value if value == ANSI_5_NAME => Ok(5),
64 "cyan" => Ok(6),
65 "white" => Ok(7),
66 "brightblack" => Ok(90),
67 "brightred" => Ok(91),
68 "brightgreen" => Ok(92),
69 "brightyellow" => Ok(93),
70 "brightblue" => Ok(94),
71 value if value == ANSI_95_NAME => Ok(95),
72 "brightcyan" => Ok(96),
73 "brightwhite" => Ok(97),
74 _ => Err(invalid()),
75 }
76}
77
78#[must_use]
80pub fn colour_to_string(colour: Colour) -> String {
81 if colour == COLOUR_NONE {
82 return "none".to_owned();
83 }
84 if colour & COLOUR_FLAG_RGB != 0 {
85 let red = ((colour >> 16) & 0xff) as u8;
86 let green = ((colour >> 8) & 0xff) as u8;
87 let blue = (colour & 0xff) as u8;
88 return format!("#{red:02x}{green:02x}{blue:02x}");
89 }
90 if colour & COLOUR_FLAG_256 != 0 {
91 return format!("colour{}", colour & 0xff);
92 }
93
94 match colour {
95 0 => "black".to_owned(),
96 1 => "red".to_owned(),
97 2 => "green".to_owned(),
98 3 => "yellow".to_owned(),
99 4 => "blue".to_owned(),
100 5 => ANSI_5_NAME.to_owned(),
101 6 => "cyan".to_owned(),
102 7 => "white".to_owned(),
103 COLOUR_DEFAULT => "default".to_owned(),
104 COLOUR_TERMINAL => "terminal".to_owned(),
105 90 => "brightblack".to_owned(),
106 91 => "brightred".to_owned(),
107 92 => "brightgreen".to_owned(),
108 93 => "brightyellow".to_owned(),
109 94 => "brightblue".to_owned(),
110 95 => ANSI_95_NAME.to_owned(),
111 96 => "brightcyan".to_owned(),
112 97 => "brightwhite".to_owned(),
113 10..=89 | 98..=255 => format!("colour{colour}"),
115 _ => "default".to_owned(),
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
121pub enum ColourParseError {
122 Invalid(String),
124}
125
126impl fmt::Display for ColourParseError {
127 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
128 match self {
129 Self::Invalid(value) => write!(formatter, "invalid colour: {value}"),
130 }
131 }
132}
133
134impl std::error::Error for ColourParseError {}