2020-10-10 18:49:46 +00:00
|
|
|
|
use std::iter::Peekable;
|
2017-08-26 21:33:16 +00:00
|
|
|
|
use std::ops::FnMut;
|
2017-08-20 21:59:22 +00:00
|
|
|
|
|
2017-09-03 16:05:38 +00:00
|
|
|
|
use ansi_term::{Colour, Style};
|
2017-08-20 21:59:22 +00:00
|
|
|
|
use ansi_term::Colour::*;
|
|
|
|
|
|
|
|
|
|
|
2018-10-08 02:59:51 +00:00
|
|
|
|
// Parsing the LS_COLORS environment variable into a map of names to Style values.
|
|
|
|
|
//
|
|
|
|
|
// This is sitting around undocumented at the moment because it’s a feature
|
|
|
|
|
// that should really be unnecessary! exa highlights its output by creating a
|
|
|
|
|
// theme of one Style value per part of the interface that can be coloured,
|
|
|
|
|
// then reading styles from that theme. The LS_COLORS variable, on the other
|
|
|
|
|
// hand, can contain arbitrary characters that ls is supposed to add to the
|
|
|
|
|
// output, without needing to know what they actually do. This puts exa in the
|
|
|
|
|
// annoying position of having to parse the ANSI escape codes _back_ into
|
|
|
|
|
// Style values before it’s able to use them. Doing this has a lot of
|
|
|
|
|
// downsides: if a new terminal feature is added with its own code, exa won’t
|
|
|
|
|
// be able to use this without explicit support for parsing the feature, while
|
|
|
|
|
// ls would not even need to know it existed. And there are some edge cases in
|
|
|
|
|
// ANSI codes, where terminals would accept codes exa is strict about it. It’s
|
|
|
|
|
// just not worth doing, and there should really be a way to just use slices
|
|
|
|
|
// of the LS_COLORS string without having to parse them.
|
|
|
|
|
|
2017-08-26 21:33:16 +00:00
|
|
|
|
pub struct LSColors<'var>(pub &'var str);
|
2017-08-20 21:59:22 +00:00
|
|
|
|
|
|
|
|
|
impl<'var> LSColors<'var> {
|
2020-10-10 12:33:50 +00:00
|
|
|
|
pub fn each_pair<C>(&mut self, mut callback: C)
|
|
|
|
|
where C: FnMut(Pair<'var>)
|
|
|
|
|
{
|
2018-06-19 12:58:03 +00:00
|
|
|
|
for next in self.0.split(':') {
|
|
|
|
|
let bits = next.split('=')
|
2017-08-26 21:33:16 +00:00
|
|
|
|
.take(3)
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
2020-10-10 18:49:46 +00:00
|
|
|
|
if bits.len() == 2 && ! bits[0].is_empty() && ! bits[1].is_empty() {
|
2017-08-26 21:33:16 +00:00
|
|
|
|
callback(Pair { key: bits[0], value: bits[1] });
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-20 21:59:22 +00:00
|
|
|
|
}
|
2017-08-26 21:33:16 +00:00
|
|
|
|
}
|
2017-08-20 21:59:22 +00:00
|
|
|
|
|
2017-08-22 17:06:42 +00:00
|
|
|
|
|
2017-09-03 16:05:38 +00:00
|
|
|
|
fn parse_into_high_colour<'a, I>(iter: &mut Peekable<I>) -> Option<Colour>
|
2020-10-10 18:49:46 +00:00
|
|
|
|
where I: Iterator<Item = &'a str>
|
|
|
|
|
{
|
2017-09-03 16:05:38 +00:00
|
|
|
|
match iter.peek() {
|
|
|
|
|
Some(&"5") => {
|
2018-10-13 21:07:35 +00:00
|
|
|
|
let _ = iter.next();
|
2017-09-03 16:05:38 +00:00
|
|
|
|
if let Some(byte) = iter.next() {
|
|
|
|
|
if let Ok(num) = byte.parse() {
|
|
|
|
|
return Some(Fixed(num));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-10 18:49:46 +00:00
|
|
|
|
|
2018-10-08 02:59:51 +00:00
|
|
|
|
Some(&"2") => {
|
2018-10-13 21:07:35 +00:00
|
|
|
|
let _ = iter.next();
|
2018-10-08 02:59:51 +00:00
|
|
|
|
if let Some(hexes) = iter.next() {
|
|
|
|
|
// Some terminals support R:G:B instead of R;G;B
|
2020-10-10 18:49:46 +00:00
|
|
|
|
// but this clashes with splitting on ‘:’ in each_pair above.
|
2018-10-08 02:59:51 +00:00
|
|
|
|
/*if hexes.contains(':') {
|
|
|
|
|
let rgb = hexes.splitn(3, ':').collect::<Vec<_>>();
|
|
|
|
|
if rgb.len() != 3 {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
else if let (Ok(r), Ok(g), Ok(b)) = (rgb[0].parse(), rgb[1].parse(), rgb[2].parse()) {
|
|
|
|
|
return Some(RGB(r, g, b));
|
|
|
|
|
}
|
|
|
|
|
}*/
|
2018-12-16 07:18:43 +00:00
|
|
|
|
|
2018-10-08 02:59:51 +00:00
|
|
|
|
if let (Some(r), Some(g), Some(b)) = (hexes.parse().ok(),
|
|
|
|
|
iter.next().and_then(|s| s.parse().ok()),
|
|
|
|
|
iter.next().and_then(|s| s.parse().ok())) {
|
|
|
|
|
return Some(RGB(r, g, b));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-10 18:49:46 +00:00
|
|
|
|
|
2017-09-03 16:05:38 +00:00
|
|
|
|
_ => {},
|
|
|
|
|
}
|
2020-10-10 18:49:46 +00:00
|
|
|
|
|
2017-09-03 16:05:38 +00:00
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-10 18:49:46 +00:00
|
|
|
|
|
|
|
|
|
pub struct Pair<'var> {
|
|
|
|
|
pub key: &'var str,
|
|
|
|
|
pub value: &'var str,
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-26 21:33:16 +00:00
|
|
|
|
impl<'var> Pair<'var> {
|
|
|
|
|
pub fn to_style(&self) -> Style {
|
|
|
|
|
let mut style = Style::default();
|
2018-06-19 12:58:03 +00:00
|
|
|
|
let mut iter = self.value.split(';').peekable();
|
2017-08-26 21:33:16 +00:00
|
|
|
|
|
2017-09-03 16:05:38 +00:00
|
|
|
|
while let Some(num) = iter.next() {
|
2018-12-16 07:18:43 +00:00
|
|
|
|
match num.trim_start_matches('0') {
|
2017-08-26 21:33:16 +00:00
|
|
|
|
|
|
|
|
|
// Bold and italic
|
2018-10-08 02:01:15 +00:00
|
|
|
|
"1" => style = style.bold(),
|
2018-10-08 02:59:51 +00:00
|
|
|
|
"2" => style = style.dimmed(),
|
|
|
|
|
"3" => style = style.italic(),
|
2018-10-08 02:01:15 +00:00
|
|
|
|
"4" => style = style.underline(),
|
2018-10-08 02:59:51 +00:00
|
|
|
|
"5" => style = style.blink(),
|
|
|
|
|
// 6 is supposedly a faster blink
|
|
|
|
|
"7" => style = style.reverse(),
|
|
|
|
|
"8" => style = style.hidden(),
|
|
|
|
|
"9" => style = style.strikethrough(),
|
2017-08-26 21:33:16 +00:00
|
|
|
|
|
|
|
|
|
// Foreground colours
|
|
|
|
|
"30" => style = style.fg(Black),
|
|
|
|
|
"31" => style = style.fg(Red),
|
|
|
|
|
"32" => style = style.fg(Green),
|
|
|
|
|
"33" => style = style.fg(Yellow),
|
|
|
|
|
"34" => style = style.fg(Blue),
|
|
|
|
|
"35" => style = style.fg(Purple),
|
|
|
|
|
"36" => style = style.fg(Cyan),
|
|
|
|
|
"37" => style = style.fg(White),
|
2017-09-03 16:05:38 +00:00
|
|
|
|
"38" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.fg(c) },
|
2017-08-26 21:33:16 +00:00
|
|
|
|
|
|
|
|
|
// Background colours
|
|
|
|
|
"40" => style = style.on(Black),
|
|
|
|
|
"41" => style = style.on(Red),
|
|
|
|
|
"42" => style = style.on(Green),
|
|
|
|
|
"43" => style = style.on(Yellow),
|
|
|
|
|
"44" => style = style.on(Blue),
|
|
|
|
|
"45" => style = style.on(Purple),
|
|
|
|
|
"46" => style = style.on(Cyan),
|
|
|
|
|
"47" => style = style.on(White),
|
2017-09-03 16:05:38 +00:00
|
|
|
|
"48" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.on(c) },
|
|
|
|
|
|
2020-10-10 18:49:46 +00:00
|
|
|
|
_ => {/* ignore the error and do nothing */},
|
2017-08-26 21:33:16 +00:00
|
|
|
|
}
|
2017-08-22 17:06:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-26 21:33:16 +00:00
|
|
|
|
style
|
|
|
|
|
}
|
2017-08-20 21:59:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-22 17:06:42 +00:00
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod ansi_test {
|
|
|
|
|
use super::*;
|
|
|
|
|
use ansi_term::Style;
|
|
|
|
|
|
|
|
|
|
macro_rules! test {
|
|
|
|
|
($name:ident: $input:expr => $result:expr) => {
|
|
|
|
|
#[test]
|
|
|
|
|
fn $name() {
|
2017-08-26 21:33:16 +00:00
|
|
|
|
assert_eq!(Pair { key: "", value: $input }.to_style(), $result);
|
2017-08-22 17:06:42 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
2017-08-20 21:59:22 +00:00
|
|
|
|
}
|
2017-08-22 17:06:42 +00:00
|
|
|
|
|
|
|
|
|
// Styles
|
|
|
|
|
test!(bold: "1" => Style::default().bold());
|
2018-10-08 02:01:15 +00:00
|
|
|
|
test!(bold2: "01" => Style::default().bold());
|
2017-08-22 17:06:42 +00:00
|
|
|
|
test!(under: "4" => Style::default().underline());
|
2018-10-08 02:01:15 +00:00
|
|
|
|
test!(unde2: "04" => Style::default().underline());
|
2017-08-22 17:06:42 +00:00
|
|
|
|
test!(both: "1;4" => Style::default().bold().underline());
|
2018-10-08 02:01:15 +00:00
|
|
|
|
test!(both2: "01;04" => Style::default().bold().underline());
|
2017-08-22 17:06:42 +00:00
|
|
|
|
test!(fg: "31" => Red.normal());
|
|
|
|
|
test!(bg: "43" => Style::default().on(Yellow));
|
|
|
|
|
test!(bfg: "31;43" => Red.on(Yellow));
|
2018-10-08 02:01:15 +00:00
|
|
|
|
test!(bfg2: "0031;0043" => Red.on(Yellow));
|
2017-08-22 17:06:42 +00:00
|
|
|
|
test!(all: "43;31;1;4" => Red.on(Yellow).bold().underline());
|
|
|
|
|
test!(again: "1;1;1;1;1" => Style::default().bold());
|
|
|
|
|
|
|
|
|
|
// Failure cases
|
|
|
|
|
test!(empty: "" => Style::default());
|
|
|
|
|
test!(semis: ";;;;;;" => Style::default());
|
|
|
|
|
test!(nines: "99999999" => Style::default());
|
|
|
|
|
test!(word: "GREEN" => Style::default());
|
2017-09-03 16:05:38 +00:00
|
|
|
|
|
|
|
|
|
// Higher colours
|
|
|
|
|
test!(hifg: "38;5;149" => Fixed(149).normal());
|
|
|
|
|
test!(hibg: "48;5;1" => Style::default().on(Fixed(1)));
|
|
|
|
|
test!(hibo: "48;5;1;1" => Style::default().on(Fixed(1)).bold());
|
|
|
|
|
test!(hiund: "4;48;5;1" => Style::default().on(Fixed(1)).underline());
|
|
|
|
|
|
2018-10-08 02:59:51 +00:00
|
|
|
|
test!(rgb: "38;2;255;100;0" => Style::default().fg(RGB(255, 100, 0)));
|
|
|
|
|
test!(rgbi: "38;2;255;100;0;3" => Style::default().fg(RGB(255, 100, 0)).italic());
|
|
|
|
|
test!(rgbbg: "48;2;255;100;0" => Style::default().on(RGB(255, 100, 0)));
|
|
|
|
|
test!(rgbbi: "48;2;255;100;0;3" => Style::default().on(RGB(255, 100, 0)).italic());
|
|
|
|
|
|
2017-09-03 16:05:38 +00:00
|
|
|
|
test!(fgbg: "38;5;121;48;5;212" => Fixed(121).on(Fixed(212)));
|
|
|
|
|
test!(bgfg: "48;5;121;38;5;212" => Fixed(212).on(Fixed(121)));
|
|
|
|
|
test!(toohi: "48;5;999" => Style::default());
|
2017-08-20 21:59:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod test {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
2017-08-22 17:06:42 +00:00
|
|
|
|
macro_rules! test {
|
2017-08-26 21:33:16 +00:00
|
|
|
|
($name:ident: $input:expr => $result:expr) => {
|
2017-08-22 17:06:42 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn $name() {
|
2017-08-26 21:33:16 +00:00
|
|
|
|
let mut lscs = Vec::new();
|
|
|
|
|
LSColors($input).each_pair(|p| lscs.push( (p.key.clone(), p.to_style()) ));
|
|
|
|
|
assert_eq!(lscs, $result.to_vec());
|
2017-08-22 17:06:42 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
2017-08-20 21:59:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-22 17:06:42 +00:00
|
|
|
|
// Bad parses
|
2017-08-26 21:33:16 +00:00
|
|
|
|
test!(empty: "" => []);
|
|
|
|
|
test!(jibber: "blah" => []);
|
2017-08-20 21:59:22 +00:00
|
|
|
|
|
2017-08-26 21:33:16 +00:00
|
|
|
|
test!(equals: "=" => []);
|
|
|
|
|
test!(starts: "=di" => []);
|
|
|
|
|
test!(ends: "id=" => []);
|
2017-08-20 21:59:22 +00:00
|
|
|
|
|
2017-08-22 17:06:42 +00:00
|
|
|
|
// Foreground colours
|
2017-08-26 21:33:16 +00:00
|
|
|
|
test!(green: "cb=32" => [ ("cb", Green.normal()) ]);
|
|
|
|
|
test!(red: "di=31" => [ ("di", Red.normal()) ]);
|
|
|
|
|
test!(blue: "la=34" => [ ("la", Blue.normal()) ]);
|
2017-08-20 21:59:22 +00:00
|
|
|
|
|
2017-08-22 17:06:42 +00:00
|
|
|
|
// Background colours
|
2017-08-26 21:33:16 +00:00
|
|
|
|
test!(yellow: "do=43" => [ ("do", Style::default().on(Yellow)) ]);
|
|
|
|
|
test!(purple: "re=45" => [ ("re", Style::default().on(Purple)) ]);
|
|
|
|
|
test!(cyan: "mi=46" => [ ("mi", Style::default().on(Cyan)) ]);
|
2017-08-20 21:59:22 +00:00
|
|
|
|
|
2017-08-22 17:06:42 +00:00
|
|
|
|
// Bold and underline
|
2017-08-26 21:33:16 +00:00
|
|
|
|
test!(bold: "fa=1" => [ ("fa", Style::default().bold()) ]);
|
|
|
|
|
test!(under: "so=4" => [ ("so", Style::default().underline()) ]);
|
|
|
|
|
test!(both: "la=1;4" => [ ("la", Style::default().bold().underline()) ]);
|
2017-08-20 21:59:22 +00:00
|
|
|
|
|
2017-08-22 17:06:42 +00:00
|
|
|
|
// More and many
|
2017-08-26 21:33:16 +00:00
|
|
|
|
test!(more: "me=43;21;55;34:yu=1;4;1" => [ ("me", Blue.on(Yellow)), ("yu", Style::default().bold().underline()) ]);
|
|
|
|
|
test!(many: "red=31:green=32:blue=34" => [ ("red", Red.normal()), ("green", Green.normal()), ("blue", Blue.normal()) ]);
|
2017-08-20 21:59:22 +00:00
|
|
|
|
}
|