From 7e5589f07d8f3f677d530412704b72d19aff5015 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Sat, 10 Feb 2018 13:02:48 -0500 Subject: [PATCH] termcolor: permit hex colors This commit adds support for specifying Ansi256 or RGB colors using hexadecimal notation. --- src/app.rs | 7 ++- termcolor/src/lib.rs | 140 +++++++++++++++++++++++++++---------------- 2 files changed, 94 insertions(+), 53 deletions(-) diff --git a/src/app.rs b/src/app.rs index f2f214d3..e293e9e3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -696,13 +696,18 @@ the background color for line numbers to yellow: Extended colors can be used for {value} when the terminal supports ANSI color sequences. These are specified as either 'x' (256-color) or 'x,x,x' (24-bit -truecolor) where x is a number between 0 and 255 inclusive. +truecolor) where x is a number between 0 and 255 inclusive. x may be given as +a normal decimal number or a hexadecimal number, which is prefixed by `0x`. For example, the following command will change the match background color to that represented by the rgb value (0,128,255): rg --colors 'match:bg:0,128,255' +or, equivalently, + + rg --colors 'match:bg:0x0,0x80,0xFF' + Note that the the intense and nointense style flags will have no effect when used alongside these extended color codes. "); diff --git a/termcolor/src/lib.rs b/termcolor/src/lib.rs index 769029b1..72b276f9 100644 --- a/termcolor/src/lib.rs +++ b/termcolor/src/lib.rs @@ -1293,7 +1293,18 @@ impl ColorSpec { /// on Windows using the console. If they are used on Windows, then they are /// silently ignored and no colors will be emitted. /// -/// Note that this set may expand over time. +/// This set may expand over time. +/// +/// This type has a `FromStr` impl that can parse colors from their human +/// readable form. The format is as follows: +/// +/// 1. Any of the explicitly listed colors in English. They are matched +/// case insensitively. +/// 2. A single 8-bit integer, in either decimal or hexadecimal format. +/// 3. A triple of 8-bit integers separated by a comma, where each integer is +/// in decimal or hexadecimal format. +/// +/// Hexadecimal numbers are written with a `0x` prefix. #[allow(missing_docs)] #[derive(Clone, Debug, Eq, PartialEq)] pub enum Color { @@ -1311,9 +1322,9 @@ pub enum Color { __Nonexhaustive, } -#[cfg(windows)] impl Color { /// Translate this color to a wincolor::Color. + #[cfg(windows)] fn to_windows(&self) -> Option { match *self { Color::Black => Some(wincolor::Color::Black), @@ -1329,6 +1340,68 @@ impl Color { Color::__Nonexhaustive => unreachable!(), } } + + /// Parses a numeric color string, either ANSI or RGB. + fn from_str_numeric(s: &str) -> Result { + // The "ansi256" format is a single number (decimal or hex) + // corresponding to one of 256 colors. + // + // The "rgb" format is a triple of numbers (decimal or hex) delimited + // by a comma corresponding to one of 256^3 colors. + + fn parse_number(s: &str) -> Option { + use std::u8; + + if s.starts_with("0x") { + u8::from_str_radix(&s[2..], 16).ok() + } else { + u8::from_str_radix(s, 10).ok() + } + } + + let codes: Vec<&str> = s.split(',').collect(); + if codes.len() == 1 { + if let Some(n) = parse_number(&codes[0]) { + Ok(Color::Ansi256(n)) + } else { + if s.chars().all(|c| c.is_digit(16)) { + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidAnsi256, + given: s.to_string(), + }) + } else { + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidName, + given: s.to_string(), + }) + } + } + } else if codes.len() == 3 { + let mut v = vec![]; + for code in codes { + let n = parse_number(code).ok_or_else(|| { + ParseColorError { + kind: ParseColorErrorKind::InvalidRgb, + given: s.to_string(), + } + })?; + v.push(n); + } + Ok(Color::Rgb(v[0], v[1], v[2])) + } else { + Err(if s.contains(",") { + ParseColorError { + kind: ParseColorErrorKind::InvalidRgb, + given: s.to_string(), + } + } else { + ParseColorError { + kind: ParseColorErrorKind::InvalidName, + given: s.to_string(), + } + }) + } + } } /// An error from parsing an invalid color specification. @@ -1373,13 +1446,13 @@ impl fmt::Display for ParseColorError { } InvalidAnsi256 => { write!(f, "unrecognized ansi256 color number, \ - should be '[0-255]', but is '{}'", + should be '[0-255]' (or a hex number), but is '{}'", self.given) } InvalidRgb => { write!(f, "unrecognized RGB color triple, \ - should be '[0-255],[0-255],[0-255]', but is '{}'", - self.given) + should be '[0-255],[0-255],[0-255]' (or a hex \ + triple), but is '{}'", self.given) } } } @@ -1398,52 +1471,7 @@ impl FromStr for Color { "magenta" => Ok(Color::Magenta), "yellow" => Ok(Color::Yellow), "white" => Ok(Color::White), - _ => { - // - Ansi256: '[0-255]' - // - Rgb: '[0-255],[0-255],[0-255]' - let codes: Vec<&str> = s.split(',').collect(); - if codes.len() == 1 { - if let Ok(n) = codes[0].parse::() { - Ok(Color::Ansi256(n)) - } else { - if s.chars().all(|c| c.is_digit(10)) { - Err(ParseColorError { - kind: ParseColorErrorKind::InvalidAnsi256, - given: s.to_string(), - }) - } else { - Err(ParseColorError { - kind: ParseColorErrorKind::InvalidName, - given: s.to_string(), - }) - } - } - } else if codes.len() == 3 { - let mut v = vec![]; - for code in codes { - let n = code.parse::().map_err(|_| { - ParseColorError { - kind: ParseColorErrorKind::InvalidRgb, - given: s.to_string(), - } - })?; - v.push(n); - } - Ok(Color::Rgb(v[0], v[1], v[2])) - } else { - Err(if s.contains(",") { - ParseColorError { - kind: ParseColorErrorKind::InvalidRgb, - given: s.to_string(), - } - } else { - ParseColorError { - kind: ParseColorErrorKind::InvalidName, - given: s.to_string(), - } - }) - } - } + _ => Color::from_str_numeric(s), } } } @@ -1550,9 +1578,11 @@ mod tests { let color = "7".parse::(); assert_eq!(color, Ok(Color::Ansi256(7))); - // In range of standard color codes let color = "32".parse::(); assert_eq!(color, Ok(Color::Ansi256(32))); + + let color = "0xFF".parse::(); + assert_eq!(color, Ok(Color::Ansi256(0xFF))); } #[test] @@ -1571,6 +1601,12 @@ mod tests { let color = "0,128,255".parse::(); assert_eq!(color, Ok(Color::Rgb(0, 128, 255))); + + let color = "0x0,0x0,0x0".parse::(); + assert_eq!(color, Ok(Color::Rgb(0, 0, 0))); + + let color = "0x33,0x66,0xFF".parse::(); + assert_eq!(color, Ok(Color::Rgb(0x33, 0x66, 0xFF))); } #[test]