summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Gallant <jamslam@gmail.com>2018-02-10 13:02:48 -0500
committerAndrew Gallant <jamslam@gmail.com>2018-02-10 22:28:12 -0500
commit7e5589f07d8f3f677d530412704b72d19aff5015 (patch)
tree79e8c2ad9cbe5063bcc47bffea44a02a9f53ff70
parent09c5b2c4eab68a6a59a044ea149b8487b8ff5002 (diff)
termcolor: permit hex colors
This commit adds support for specifying Ansi256 or RGB colors using hexadecimal notation.
-rw-r--r--src/app.rs7
-rw-r--r--termcolor/src/lib.rs140
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<wincolor::Color> {
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<Color, ParseColorError> {
+ // 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<u8> {
+ 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::<u8>() {
- 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::<u8>().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::<Color>();
assert_eq!(color, Ok(Color::Ansi256(7)));
- // In range of standard color codes
let color = "32".parse::<Color>();
assert_eq!(color, Ok(Color::Ansi256(32)));
+
+ let color = "0xFF".parse::<Color>();
+ assert_eq!(color, Ok(Color::Ansi256(0xFF)));
}
#[test]
@@ -1571,6 +1601,12 @@ mod tests {
let color = "0,128,255".parse::<Color>();
assert_eq!(color, Ok(Color::Rgb(0, 128, 255)));
+
+ let color = "0x0,0x0,0x0".parse::<Color>();
+ assert_eq!(color, Ok(Color::Rgb(0, 0, 0)));
+
+ let color = "0x33,0x66,0xFF".parse::<Color>();
+ assert_eq!(color, Ok(Color::Rgb(0x33, 0x66, 0xFF)));
}
#[test]