Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extended color support #452

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,16 @@ lazy_static! {
color settings for {type}.\n\nFor example, the following \
command will change the match color to magenta and the \
background color for line numbers to yellow:\n\n\
rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo.");
rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo.\n\n\
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. \n\nFor example, the \
following command will change the match background color to that \
represented by the rgb value (0,128,255):\n\n\
rg --colors 'match:bg:0,128,255'\n\nNote that the the intense \
and nointense style flags will have no effect when used \
alongside these extended color codes.");
doc!(h, "encoding",
"Specify the text encoding of files to search.",
"Specify the text encoding that ripgrep will use on all files \
Expand Down
167 changes: 164 additions & 3 deletions termcolor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,60 @@ impl<W: io::Write> Ansi<W> {
}
}
}
macro_rules! write_var_ansi_code {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some eyes on this would be good if its preferred over format!. I've added some test cases here but could have missed something.

($pre:expr, $($code:expr),+) => {{
// The loop generates at worst a literal of the form
// '255,255,255m' which is 12-bytes.
// The largest `pre` expression we currently use is 7 bytes.
// This gives us the maximum of 19-bytes for our work buffer.
let pre_len = $pre.len();
assert!(pre_len <= 7);
let mut fmt = [0u8; 19];
fmt[..pre_len].copy_from_slice($pre);
let mut i = pre_len - 1;
$(
let c1: u8 = ($code / 100) % 10;
let c2: u8 = ($code / 10) % 10;
let c3: u8 = $code % 10;
let mut printed = false;

if c1 != 0 {
printed = true;
i += 1;
fmt[i] = b'0' + c1;
}
if c2 != 0 || printed {
i += 1;
fmt[i] = b'0' + c2;
}
// If we received a zero value we must still print a value.
i += 1;
fmt[i] = b'0' + c3;
i += 1;
fmt[i] = b';';
)+

fmt[i] = b'm';
self.write_all(&fmt[0..i+1])
}}
}
macro_rules! write_custom {
($ansi256:expr) => {
if fg {
write_var_ansi_code!(b"\x1B[38;5;", $ansi256)
} else {
write_var_ansi_code!(b"\x1B[48;5;", $ansi256)
}
};

($r:expr, $g:expr, $b:expr) => {{
if fg {
write_var_ansi_code!(b"\x1B[38;2;", $r, $g, $b)
} else {
write_var_ansi_code!(b"\x1B[48;2;", $r, $g, $b)
}
}};
}
if intense {
match *c {
Color::Black => write_intense!("8"),
Expand All @@ -964,6 +1018,8 @@ impl<W: io::Write> Ansi<W> {
Color::Magenta => write_intense!("13"),
Color::Yellow => write_intense!("11"),
Color::White => write_intense!("15"),
Color::Ansi256(c) => write_custom!(c),
Color::Rgb(r, g, b) => write_custom!(r, g, b),
Color::__Nonexhaustive => unreachable!(),
}
} else {
Expand All @@ -976,6 +1032,8 @@ impl<W: io::Write> Ansi<W> {
Color::Magenta => write_normal!("5"),
Color::Yellow => write_normal!("3"),
Color::White => write_normal!("7"),
Color::Ansi256(c) => write_custom!(c),
Color::Rgb(r, g, b) => write_custom!(r, g, b),
Color::__Nonexhaustive => unreachable!(),
}
}
Expand Down Expand Up @@ -1170,7 +1228,13 @@ impl ColorSpec {
}
}

/// The set of available English colors for the terminal foreground/background.
/// The set of available colors for the terminal foreground/background.
///
/// The `Ansi256` and `Rgb` colors will only output the correct codes when
/// paired with the `Ansi` `WriteColor` implementation.
///
/// Usage under windows currently falls back to `White` as the default if
/// encountered.
///
/// Note that this set may expand over time.
#[allow(missing_docs)]
Expand All @@ -1184,6 +1248,8 @@ pub enum Color {
Magenta,
Yellow,
White,
Ansi256(u8),
Rgb(u8, u8, u8),
#[doc(hidden)]
__Nonexhaustive,
}
Expand All @@ -1201,6 +1267,10 @@ impl Color {
Color::Magenta => wincolor::Color::Magenta,
Color::Yellow => wincolor::Color::Yellow,
Color::White => wincolor::Color::White,
// We cannot handle these color codes currently on windows so we
// fall back to white as a default.
Color::Ansi256(_) => wincolor::Color::White,
Color::Rgb(_, _, _) => wincolor::Color::White,
Color::__Nonexhaustive => unreachable!(),
}
}
Expand All @@ -1222,7 +1292,8 @@ impl error::Error for ParseColorError {
impl fmt::Display for ParseColorError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Unrecognized color name '{}'. Choose from: \
black, blue, green, red, cyan, magenta, yellow, white.",
black, blue, green, red, cyan, magenta, yellow, white, \
'[0-255]','[0-255],[0-255],[0-255]'",
self.0)
}
}
Expand All @@ -1240,7 +1311,34 @@ impl FromStr for Color {
"magenta" => Ok(Color::Magenta),
"yellow" => Ok(Color::Yellow),
"white" => Ok(Color::White),
_ => Err(ParseColorError(s.to_string())),

_ => {
// - Ansi256: '[0-255]'
// - Rgb: '[0-255],[0-255],[0-255]'
let codes: Vec<&str> = s.split(',').collect();
if codes.len() == 1 {
let p = codes[0].parse::<u8>();
if p.is_ok() {
Ok(Color::Ansi256(p.unwrap()))
} else {
Err(ParseColorError(s.to_string()))
}
}
else if codes.len() == 3 {
let mut v = Vec::new();
for code in codes {
let r = code.parse::<u8>();
if r.is_err() {
return Err(ParseColorError(s.to_string()));
}
v.push(r.unwrap());
}
Ok(Color::Rgb(v[0], v[1], v[2]))
} else {
Err(ParseColorError(s.to_string()))
}
}

}
}
}
Expand Down Expand Up @@ -1320,3 +1418,66 @@ fn write_lossy_utf8<W: io::Write>(mut w: W, buf: &[u8]) -> io::Result<usize> {
Err(e) => w.write(&buf[..e.valid_up_to()]),
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_simple_parse_ok() {
let color = "green".parse::<Color>();
assert_eq!(color, Ok(Color::Green));
}

#[test]
fn test_256_parse_ok() {
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)));
}

#[test]
fn test_rgb_parse_ok() {
let color = "0,0,0".parse::<Color>();
assert_eq!(color, Ok(Color::Rgb(0, 0, 0)));

let color = "0,128,255".parse::<Color>();
assert_eq!(color, Ok(Color::Rgb(0, 128, 255)));
}

#[test]
fn test_rgb_parse_err_out_of_range() {
let color = "0,0,256".parse::<Color>();
assert_eq!(color, Err(ParseColorError("0,0,256".into())));
}

#[test]
fn test_rgb_parse_err_bad_format() {
let color = "0,0".parse::<Color>();
assert_eq!(color, Err(ParseColorError("0,0".into())));

let color = "not_a_color".parse::<Color>();
assert_eq!(color, Err(ParseColorError("not_a_color".into())));
}

#[test]
fn test_var_ansi_write_rgb() {
let mut buf = Ansi::new(Vec::new());
let _ = buf.write_color(true, &Color::Rgb(254, 253, 255), false);
assert_eq!(buf.0, b"\x1B[38;2;254;253;255m");
}

#[test]
fn test_var_ansi_write_256() {
let mut buf = Ansi::new(Vec::new());
let _ = buf.write_color(false, &Color::Ansi256(7), false);
assert_eq!(buf.0, b"\x1B[48;5;7m");

let mut buf = Ansi::new(Vec::new());
let _ = buf.write_color(false, &Color::Ansi256(208), false);
assert_eq!(buf.0, b"\x1B[48;5;208m");
}
}