| 1 | use std::{
|
| 2 | convert::{AsRef, TryFrom},
|
| 3 | str::FromStr,
|
| 4 | };
|
| 5 |
|
| 6 | #[cfg (feature = "serde" )]
|
| 7 | use std::fmt;
|
| 8 |
|
| 9 | use crate::style::parse_next_u8;
|
| 10 |
|
| 11 | /// Represents a color.
|
| 12 | ///
|
| 13 | /// # Platform-specific Notes
|
| 14 | ///
|
| 15 | /// The following list of 16 base colors are available for almost all terminals (Windows 7 and 8 included).
|
| 16 | ///
|
| 17 | /// | Light | Dark |
|
| 18 | /// | :--------- | :------------ |
|
| 19 | /// | `DarkGrey` | `Black` |
|
| 20 | /// | `Red` | `DarkRed` |
|
| 21 | /// | `Green` | `DarkGreen` |
|
| 22 | /// | `Yellow` | `DarkYellow` |
|
| 23 | /// | `Blue` | `DarkBlue` |
|
| 24 | /// | `Magenta` | `DarkMagenta` |
|
| 25 | /// | `Cyan` | `DarkCyan` |
|
| 26 | /// | `White` | `Grey` |
|
| 27 | ///
|
| 28 | /// Most UNIX terminals and Windows 10 consoles support additional colors.
|
| 29 | /// See [`Color::Rgb`] or [`Color::AnsiValue`] for more info.
|
| 30 | #[derive (Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
| 31 | pub enum Color {
|
| 32 | /// Resets the terminal color.
|
| 33 | Reset,
|
| 34 |
|
| 35 | /// Black color.
|
| 36 | Black,
|
| 37 |
|
| 38 | /// Dark grey color.
|
| 39 | DarkGrey,
|
| 40 |
|
| 41 | /// Light red color.
|
| 42 | Red,
|
| 43 |
|
| 44 | /// Dark red color.
|
| 45 | DarkRed,
|
| 46 |
|
| 47 | /// Light green color.
|
| 48 | Green,
|
| 49 |
|
| 50 | /// Dark green color.
|
| 51 | DarkGreen,
|
| 52 |
|
| 53 | /// Light yellow color.
|
| 54 | Yellow,
|
| 55 |
|
| 56 | /// Dark yellow color.
|
| 57 | DarkYellow,
|
| 58 |
|
| 59 | /// Light blue color.
|
| 60 | Blue,
|
| 61 |
|
| 62 | /// Dark blue color.
|
| 63 | DarkBlue,
|
| 64 |
|
| 65 | /// Light magenta color.
|
| 66 | Magenta,
|
| 67 |
|
| 68 | /// Dark magenta color.
|
| 69 | DarkMagenta,
|
| 70 |
|
| 71 | /// Light cyan color.
|
| 72 | Cyan,
|
| 73 |
|
| 74 | /// Dark cyan color.
|
| 75 | DarkCyan,
|
| 76 |
|
| 77 | /// White color.
|
| 78 | White,
|
| 79 |
|
| 80 | /// Grey color.
|
| 81 | Grey,
|
| 82 |
|
| 83 | /// An RGB color. See [RGB color model](https://en.wikipedia.org/wiki/RGB_color_model) for more info.
|
| 84 | ///
|
| 85 | /// Most UNIX terminals and Windows 10 supported only.
|
| 86 | /// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info.
|
| 87 | Rgb { r: u8, g: u8, b: u8 },
|
| 88 |
|
| 89 | /// An ANSI color. See [256 colors - cheat sheet](https://jonasjacek.github.io/colors/) for more info.
|
| 90 | ///
|
| 91 | /// Most UNIX terminals and Windows 10 supported only.
|
| 92 | /// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info.
|
| 93 | AnsiValue(u8),
|
| 94 | }
|
| 95 |
|
| 96 | impl Color {
|
| 97 | /// Parses an ANSI color sequence.
|
| 98 | ///
|
| 99 | /// # Examples
|
| 100 | ///
|
| 101 | /// ```
|
| 102 | /// use crossterm::style::Color;
|
| 103 | ///
|
| 104 | /// assert_eq!(Color::parse_ansi("5;0" ), Some(Color::Black));
|
| 105 | /// assert_eq!(Color::parse_ansi("5;26" ), Some(Color::AnsiValue(26)));
|
| 106 | /// assert_eq!(Color::parse_ansi("2;50;60;70" ), Some(Color::Rgb { r: 50, g: 60, b: 70 }));
|
| 107 | /// assert_eq!(Color::parse_ansi("invalid color" ), None);
|
| 108 | /// ```
|
| 109 | ///
|
| 110 | /// Currently, 3/4 bit color values aren't supported so return `None`.
|
| 111 | ///
|
| 112 | /// See also: [`Colored::parse_ansi`](crate::style::Colored::parse_ansi).
|
| 113 | pub fn parse_ansi(ansi: &str) -> Option<Self> {
|
| 114 | Self::parse_ansi_iter(&mut ansi.split(';' ))
|
| 115 | }
|
| 116 |
|
| 117 | /// The logic for parse_ansi, takes an iterator of the sequences terms (the numbers between the
|
| 118 | /// ';'). It's a separate function so it can be used by both Color::parse_ansi and
|
| 119 | /// colored::parse_ansi.
|
| 120 | /// Tested in Colored tests.
|
| 121 | pub(crate) fn parse_ansi_iter<'a>(values: &mut impl Iterator<Item = &'a str>) -> Option<Self> {
|
| 122 | let color = match parse_next_u8(values)? {
|
| 123 | // 8 bit colors: `5;<n>`
|
| 124 | 5 => {
|
| 125 | let n = parse_next_u8(values)?;
|
| 126 |
|
| 127 | use Color::*;
|
| 128 | [
|
| 129 | Black, // 0
|
| 130 | DarkRed, // 1
|
| 131 | DarkGreen, // 2
|
| 132 | DarkYellow, // 3
|
| 133 | DarkBlue, // 4
|
| 134 | DarkMagenta, // 5
|
| 135 | DarkCyan, // 6
|
| 136 | Grey, // 7
|
| 137 | DarkGrey, // 8
|
| 138 | Red, // 9
|
| 139 | Green, // 10
|
| 140 | Yellow, // 11
|
| 141 | Blue, // 12
|
| 142 | Magenta, // 13
|
| 143 | Cyan, // 14
|
| 144 | White, // 15
|
| 145 | ]
|
| 146 | .get(n as usize)
|
| 147 | .copied()
|
| 148 | .unwrap_or(Color::AnsiValue(n))
|
| 149 | }
|
| 150 |
|
| 151 | // 24 bit colors: `2;<r>;<g>;<b>`
|
| 152 | 2 => Color::Rgb {
|
| 153 | r: parse_next_u8(values)?,
|
| 154 | g: parse_next_u8(values)?,
|
| 155 | b: parse_next_u8(values)?,
|
| 156 | },
|
| 157 |
|
| 158 | _ => return None,
|
| 159 | };
|
| 160 | // If there's another value, it's unexpected so return None.
|
| 161 | if values.next().is_some() {
|
| 162 | return None;
|
| 163 | }
|
| 164 | Some(color)
|
| 165 | }
|
| 166 | }
|
| 167 |
|
| 168 | impl TryFrom<&str> for Color {
|
| 169 | type Error = ();
|
| 170 |
|
| 171 | /// Try to create a `Color` from the string representation. This returns an error if the string does not match.
|
| 172 | fn try_from(src: &str) -> Result<Self, Self::Error> {
|
| 173 | let src = src.to_lowercase();
|
| 174 |
|
| 175 | match src.as_ref() {
|
| 176 | "reset" => Ok(Color::Reset),
|
| 177 | "black" => Ok(Color::Black),
|
| 178 | "dark_grey" => Ok(Color::DarkGrey),
|
| 179 | "red" => Ok(Color::Red),
|
| 180 | "dark_red" => Ok(Color::DarkRed),
|
| 181 | "green" => Ok(Color::Green),
|
| 182 | "dark_green" => Ok(Color::DarkGreen),
|
| 183 | "yellow" => Ok(Color::Yellow),
|
| 184 | "dark_yellow" => Ok(Color::DarkYellow),
|
| 185 | "blue" => Ok(Color::Blue),
|
| 186 | "dark_blue" => Ok(Color::DarkBlue),
|
| 187 | "magenta" => Ok(Color::Magenta),
|
| 188 | "dark_magenta" => Ok(Color::DarkMagenta),
|
| 189 | "cyan" => Ok(Color::Cyan),
|
| 190 | "dark_cyan" => Ok(Color::DarkCyan),
|
| 191 | "white" => Ok(Color::White),
|
| 192 | "grey" => Ok(Color::Grey),
|
| 193 | _ => Err(()),
|
| 194 | }
|
| 195 | }
|
| 196 | }
|
| 197 |
|
| 198 | impl FromStr for Color {
|
| 199 | type Err = ();
|
| 200 |
|
| 201 | /// Creates a `Color` from the string representation.
|
| 202 | ///
|
| 203 | /// # Notes
|
| 204 | ///
|
| 205 | /// * Returns `Color::White` in case of an unknown color.
|
| 206 | /// * Does not return `Err` and you can safely unwrap.
|
| 207 | fn from_str(src: &str) -> Result<Self, Self::Err> {
|
| 208 | Ok(Color::try_from(src).unwrap_or(default:Color::White))
|
| 209 | }
|
| 210 | }
|
| 211 |
|
| 212 | impl From<(u8, u8, u8)> for Color {
|
| 213 | /// Creates a 'Color' from the tuple representation.
|
| 214 | fn from(val: (u8, u8, u8)) -> Self {
|
| 215 | let (r: u8, g: u8, b: u8) = val;
|
| 216 | Self::Rgb { r, g, b }
|
| 217 | }
|
| 218 | }
|
| 219 |
|
| 220 | #[cfg (feature = "serde" )]
|
| 221 | impl serde::ser::Serialize for Color {
|
| 222 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
| 223 | where
|
| 224 | S: serde::ser::Serializer,
|
| 225 | {
|
| 226 | let str = match *self {
|
| 227 | Color::Reset => "reset" ,
|
| 228 | Color::Black => "black" ,
|
| 229 | Color::DarkGrey => "dark_grey" ,
|
| 230 | Color::Red => "red" ,
|
| 231 | Color::DarkRed => "dark_red" ,
|
| 232 | Color::Green => "green" ,
|
| 233 | Color::DarkGreen => "dark_green" ,
|
| 234 | Color::Yellow => "yellow" ,
|
| 235 | Color::DarkYellow => "dark_yellow" ,
|
| 236 | Color::Blue => "blue" ,
|
| 237 | Color::DarkBlue => "dark_blue" ,
|
| 238 | Color::Magenta => "magenta" ,
|
| 239 | Color::DarkMagenta => "dark_magenta" ,
|
| 240 | Color::Cyan => "cyan" ,
|
| 241 | Color::DarkCyan => "dark_cyan" ,
|
| 242 | Color::White => "white" ,
|
| 243 | Color::Grey => "grey" ,
|
| 244 | _ => "" ,
|
| 245 | };
|
| 246 |
|
| 247 | if str.is_empty() {
|
| 248 | match *self {
|
| 249 | Color::AnsiValue(value) => serializer.serialize_str(&format!("ansi_({})" , value)),
|
| 250 | Color::Rgb { r, g, b } => {
|
| 251 | serializer.serialize_str(&format!("rgb_({},{},{})" , r, g, b))
|
| 252 | }
|
| 253 | _ => Err(serde::ser::Error::custom("Could not serialize enum type" )),
|
| 254 | }
|
| 255 | } else {
|
| 256 | serializer.serialize_str(str)
|
| 257 | }
|
| 258 | }
|
| 259 | }
|
| 260 |
|
| 261 | #[cfg (feature = "serde" )]
|
| 262 | impl<'de> serde::de::Deserialize<'de> for Color {
|
| 263 | fn deserialize<D>(deserializer: D) -> Result<Color, D::Error>
|
| 264 | where
|
| 265 | D: serde::de::Deserializer<'de>,
|
| 266 | {
|
| 267 | struct ColorVisitor;
|
| 268 | impl serde::de::Visitor<'_> for ColorVisitor {
|
| 269 | type Value = Color;
|
| 270 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
| 271 | formatter.write_str(
|
| 272 | "`reset`, `black`, `blue`, `dark_blue`, `cyan`, `dark_cyan`, `green`, `dark_green`, `grey`, `dark_grey`, `magenta`, `dark_magenta`, `red`, `dark_red`, `white`, `yellow`, `dark_yellow`, `ansi_(value)`, or `rgb_(r,g,b)` or `#rgbhex`" ,
|
| 273 | )
|
| 274 | }
|
| 275 | fn visit_str<E>(self, value: &str) -> Result<Color, E>
|
| 276 | where
|
| 277 | E: serde::de::Error,
|
| 278 | {
|
| 279 | if let Ok(c) = Color::try_from(value) {
|
| 280 | Ok(c)
|
| 281 | } else {
|
| 282 | if value.contains("ansi" ) {
|
| 283 | // strip away `ansi_(..)' and get the inner value between parenthesis.
|
| 284 | let results = value.replace("ansi_(" , "" ).replace(")" , "" );
|
| 285 |
|
| 286 | let ansi_val = results.parse::<u8>();
|
| 287 |
|
| 288 | if let Ok(ansi) = ansi_val {
|
| 289 | return Ok(Color::AnsiValue(ansi));
|
| 290 | }
|
| 291 | } else if value.contains("rgb" ) {
|
| 292 | // strip away `rgb_(..)' and get the inner values between parenthesis.
|
| 293 | let results = value
|
| 294 | .replace("rgb_(" , "" )
|
| 295 | .replace(")" , "" )
|
| 296 | .split(',' )
|
| 297 | .map(|x| x.to_string())
|
| 298 | .collect::<Vec<String>>();
|
| 299 |
|
| 300 | if results.len() == 3 {
|
| 301 | let r = results[0].parse::<u8>();
|
| 302 | let g = results[1].parse::<u8>();
|
| 303 | let b = results[2].parse::<u8>();
|
| 304 |
|
| 305 | if let (Ok(r), Ok(g), Ok(b)) = (r, g, b) {
|
| 306 | return Ok(Color::Rgb { r, g, b });
|
| 307 | }
|
| 308 | }
|
| 309 | } else if let Some(hex) = value.strip_prefix('#' ) {
|
| 310 | if hex.is_ascii() && hex.len() == 6 {
|
| 311 | let r = u8::from_str_radix(&hex[0..2], 16);
|
| 312 | let g = u8::from_str_radix(&hex[2..4], 16);
|
| 313 | let b = u8::from_str_radix(&hex[4..6], 16);
|
| 314 |
|
| 315 | if let (Ok(r), Ok(g), Ok(b)) = (r, g, b) {
|
| 316 | return Ok(Color::Rgb { r, g, b });
|
| 317 | }
|
| 318 | }
|
| 319 | }
|
| 320 |
|
| 321 | Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
|
| 322 | }
|
| 323 | }
|
| 324 | }
|
| 325 |
|
| 326 | deserializer.deserialize_str(ColorVisitor)
|
| 327 | }
|
| 328 | }
|
| 329 |
|
| 330 | #[cfg (test)]
|
| 331 | mod tests {
|
| 332 | use super::Color;
|
| 333 |
|
| 334 | #[test ]
|
| 335 | fn test_known_color_conversion() {
|
| 336 | assert_eq!("reset" .parse(), Ok(Color::Reset));
|
| 337 | assert_eq!("grey" .parse(), Ok(Color::Grey));
|
| 338 | assert_eq!("dark_grey" .parse(), Ok(Color::DarkGrey));
|
| 339 | assert_eq!("red" .parse(), Ok(Color::Red));
|
| 340 | assert_eq!("dark_red" .parse(), Ok(Color::DarkRed));
|
| 341 | assert_eq!("green" .parse(), Ok(Color::Green));
|
| 342 | assert_eq!("dark_green" .parse(), Ok(Color::DarkGreen));
|
| 343 | assert_eq!("yellow" .parse(), Ok(Color::Yellow));
|
| 344 | assert_eq!("dark_yellow" .parse(), Ok(Color::DarkYellow));
|
| 345 | assert_eq!("blue" .parse(), Ok(Color::Blue));
|
| 346 | assert_eq!("dark_blue" .parse(), Ok(Color::DarkBlue));
|
| 347 | assert_eq!("magenta" .parse(), Ok(Color::Magenta));
|
| 348 | assert_eq!("dark_magenta" .parse(), Ok(Color::DarkMagenta));
|
| 349 | assert_eq!("cyan" .parse(), Ok(Color::Cyan));
|
| 350 | assert_eq!("dark_cyan" .parse(), Ok(Color::DarkCyan));
|
| 351 | assert_eq!("white" .parse(), Ok(Color::White));
|
| 352 | assert_eq!("black" .parse(), Ok(Color::Black));
|
| 353 | }
|
| 354 |
|
| 355 | #[test ]
|
| 356 | fn test_unknown_color_conversion_yields_white() {
|
| 357 | assert_eq!("foo" .parse(), Ok(Color::White));
|
| 358 | }
|
| 359 |
|
| 360 | #[test ]
|
| 361 | fn test_know_rgb_color_conversion() {
|
| 362 | assert_eq!(Color::from((0, 0, 0)), Color::Rgb { r: 0, g: 0, b: 0 });
|
| 363 | assert_eq!(
|
| 364 | Color::from((255, 255, 255)),
|
| 365 | Color::Rgb {
|
| 366 | r: 255,
|
| 367 | g: 255,
|
| 368 | b: 255
|
| 369 | }
|
| 370 | );
|
| 371 | }
|
| 372 | }
|
| 373 |
|
| 374 | #[cfg (test)]
|
| 375 | #[cfg (feature = "serde" )]
|
| 376 | mod serde_tests {
|
| 377 | use super::Color;
|
| 378 | use serde_json;
|
| 379 |
|
| 380 | #[test ]
|
| 381 | fn test_deserial_known_color_conversion() {
|
| 382 | assert_eq!(
|
| 383 | serde_json::from_str::<Color>(" \"Reset \"" ).unwrap(),
|
| 384 | Color::Reset
|
| 385 | );
|
| 386 | assert_eq!(
|
| 387 | serde_json::from_str::<Color>(" \"reset \"" ).unwrap(),
|
| 388 | Color::Reset
|
| 389 | );
|
| 390 | assert_eq!(
|
| 391 | serde_json::from_str::<Color>(" \"Red \"" ).unwrap(),
|
| 392 | Color::Red
|
| 393 | );
|
| 394 | assert_eq!(
|
| 395 | serde_json::from_str::<Color>(" \"red \"" ).unwrap(),
|
| 396 | Color::Red
|
| 397 | );
|
| 398 | assert_eq!(
|
| 399 | serde_json::from_str::<Color>(" \"dark_red \"" ).unwrap(),
|
| 400 | Color::DarkRed
|
| 401 | );
|
| 402 | assert_eq!(
|
| 403 | serde_json::from_str::<Color>(" \"grey \"" ).unwrap(),
|
| 404 | Color::Grey
|
| 405 | );
|
| 406 | assert_eq!(
|
| 407 | serde_json::from_str::<Color>(" \"dark_grey \"" ).unwrap(),
|
| 408 | Color::DarkGrey
|
| 409 | );
|
| 410 | assert_eq!(
|
| 411 | serde_json::from_str::<Color>(" \"green \"" ).unwrap(),
|
| 412 | Color::Green
|
| 413 | );
|
| 414 | assert_eq!(
|
| 415 | serde_json::from_str::<Color>(" \"dark_green \"" ).unwrap(),
|
| 416 | Color::DarkGreen
|
| 417 | );
|
| 418 | assert_eq!(
|
| 419 | serde_json::from_str::<Color>(" \"yellow \"" ).unwrap(),
|
| 420 | Color::Yellow
|
| 421 | );
|
| 422 | assert_eq!(
|
| 423 | serde_json::from_str::<Color>(" \"dark_yellow \"" ).unwrap(),
|
| 424 | Color::DarkYellow
|
| 425 | );
|
| 426 | assert_eq!(
|
| 427 | serde_json::from_str::<Color>(" \"blue \"" ).unwrap(),
|
| 428 | Color::Blue
|
| 429 | );
|
| 430 | assert_eq!(
|
| 431 | serde_json::from_str::<Color>(" \"dark_blue \"" ).unwrap(),
|
| 432 | Color::DarkBlue
|
| 433 | );
|
| 434 | assert_eq!(
|
| 435 | serde_json::from_str::<Color>(" \"magenta \"" ).unwrap(),
|
| 436 | Color::Magenta
|
| 437 | );
|
| 438 | assert_eq!(
|
| 439 | serde_json::from_str::<Color>(" \"dark_magenta \"" ).unwrap(),
|
| 440 | Color::DarkMagenta
|
| 441 | );
|
| 442 | assert_eq!(
|
| 443 | serde_json::from_str::<Color>(" \"cyan \"" ).unwrap(),
|
| 444 | Color::Cyan
|
| 445 | );
|
| 446 | assert_eq!(
|
| 447 | serde_json::from_str::<Color>(" \"dark_cyan \"" ).unwrap(),
|
| 448 | Color::DarkCyan
|
| 449 | );
|
| 450 | assert_eq!(
|
| 451 | serde_json::from_str::<Color>(" \"white \"" ).unwrap(),
|
| 452 | Color::White
|
| 453 | );
|
| 454 | assert_eq!(
|
| 455 | serde_json::from_str::<Color>(" \"black \"" ).unwrap(),
|
| 456 | Color::Black
|
| 457 | );
|
| 458 | }
|
| 459 |
|
| 460 | #[test ]
|
| 461 | fn test_deserial_unknown_color_conversion() {
|
| 462 | assert!(serde_json::from_str::<Color>(" \"unknown \"" ).is_err());
|
| 463 | }
|
| 464 |
|
| 465 | #[test ]
|
| 466 | fn test_deserial_ansi_value() {
|
| 467 | assert_eq!(
|
| 468 | serde_json::from_str::<Color>(" \"ansi_(255) \"" ).unwrap(),
|
| 469 | Color::AnsiValue(255)
|
| 470 | );
|
| 471 | }
|
| 472 |
|
| 473 | #[test ]
|
| 474 | fn test_deserial_unvalid_ansi_value() {
|
| 475 | assert!(serde_json::from_str::<Color>(" \"ansi_(256) \"" ).is_err());
|
| 476 | assert!(serde_json::from_str::<Color>(" \"ansi_(-1) \"" ).is_err());
|
| 477 | }
|
| 478 |
|
| 479 | #[test ]
|
| 480 | fn test_deserial_rgb() {
|
| 481 | assert_eq!(
|
| 482 | serde_json::from_str::<Color>(" \"rgb_(255,255,255) \"" ).unwrap(),
|
| 483 | Color::from((255, 255, 255))
|
| 484 | );
|
| 485 | }
|
| 486 |
|
| 487 | #[test ]
|
| 488 | fn test_deserial_unvalid_rgb() {
|
| 489 | assert!(serde_json::from_str::<Color>(" \"rgb_(255,255,255,255) \"" ).is_err());
|
| 490 | assert!(serde_json::from_str::<Color>(" \"rgb_(256,255,255) \"" ).is_err());
|
| 491 | }
|
| 492 |
|
| 493 | #[test ]
|
| 494 | fn test_deserial_rgb_hex() {
|
| 495 | assert_eq!(
|
| 496 | serde_json::from_str::<Color>(" \"#ffffff \"" ).unwrap(),
|
| 497 | Color::from((255, 255, 255))
|
| 498 | );
|
| 499 | assert_eq!(
|
| 500 | serde_json::from_str::<Color>(" \"#FFFFFF \"" ).unwrap(),
|
| 501 | Color::from((255, 255, 255))
|
| 502 | );
|
| 503 | }
|
| 504 |
|
| 505 | #[test ]
|
| 506 | fn test_deserial_unvalid_rgb_hex() {
|
| 507 | assert!(serde_json::from_str::<Color>(" \"#FFFFFFFF \"" ).is_err());
|
| 508 | assert!(serde_json::from_str::<Color>(" \"#FFGFFF \"" ).is_err());
|
| 509 | // Ferris is 4 bytes so this will be considered the correct length.
|
| 510 | assert!(serde_json::from_str::<Color>(" \"#ff🦀 \"" ).is_err());
|
| 511 | }
|
| 512 | }
|
| 513 | |