| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- use std::iter::Peekable;
- use std::ops::FnMut;
- use nu_ansi_term::Color::*;
- use nu_ansi_term::{Color as Colour, Style};
- // 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.
- pub struct LSColors<'var>(pub &'var str);
- impl<'var> LSColors<'var> {
- pub fn each_pair<C>(&mut self, mut callback: C)
- where
- C: FnMut(Pair<'var>),
- {
- for next in self.0.split(':') {
- let bits = next.split('=').take(3).collect::<Vec<_>>();
- if bits.len() == 2 && !bits[0].is_empty() && !bits[1].is_empty() {
- callback(Pair {
- key: bits[0],
- value: bits[1],
- });
- }
- }
- }
- }
- fn parse_into_high_colour<'a, I>(iter: &mut Peekable<I>) -> Option<Colour>
- where
- I: Iterator<Item = &'a str>,
- {
- match iter.peek() {
- Some(&"5") => {
- let _ = iter.next();
- if let Some(byte) = iter.next() {
- if let Ok(num) = byte.parse() {
- return Some(Fixed(num));
- }
- }
- }
- Some(&"2") => {
- let _ = iter.next();
- if let Some(hexes) = iter.next() {
- // Some terminals support R:G:B instead of R;G;B
- // but this clashes with splitting on ‘:’ in each_pair above.
- /*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));
- }
- }*/
- 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));
- }
- }
- }
- _ => {}
- }
- None
- }
- pub struct Pair<'var> {
- pub key: &'var str,
- pub value: &'var str,
- }
- impl<'var> Pair<'var> {
- pub fn to_style(&self) -> Style {
- let mut style = Style::default();
- let mut iter = self.value.split(';').peekable();
- while let Some(num) = iter.next() {
- match num.trim_start_matches('0') {
- // Bold and italic
- "1" => style = style.bold(),
- "2" => style = style.dimmed(),
- "3" => style = style.italic(),
- "4" => style = style.underline(),
- "5" => style = style.blink(),
- // 6 is supposedly a faster blink
- "7" => style = style.reverse(),
- "8" => style = style.hidden(),
- "9" => style = style.strikethrough(),
- // 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),
- // Bright foreground colours
- "90" => style = style.fg(DarkGray),
- "91" => style = style.fg(LightRed),
- "92" => style = style.fg(LightGreen),
- "93" => style = style.fg(LightYellow),
- "94" => style = style.fg(LightBlue),
- "95" => style = style.fg(LightPurple),
- "96" => style = style.fg(LightCyan),
- "97" => style = style.fg(LightGray),
- "38" => {
- if let Some(c) = parse_into_high_colour(&mut iter) {
- style = style.fg(c);
- }
- }
- // 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),
- // Bright background colours
- "100" => style = style.on(DarkGray),
- "101" => style = style.on(LightRed),
- "102" => style = style.on(LightGreen),
- "103" => style = style.on(LightYellow),
- "104" => style = style.on(LightBlue),
- "105" => style = style.on(LightPurple),
- "106" => style = style.on(LightCyan),
- "107" => style = style.on(LightGray),
- "48" => {
- if let Some(c) = parse_into_high_colour(&mut iter) {
- style = style.on(c);
- }
- }
- _ => { /* ignore the error and do nothing */ }
- }
- }
- style
- }
- }
- #[cfg(test)]
- mod ansi_test {
- use super::*;
- use nu_ansi_term::Style;
- macro_rules! test {
- ($name:ident: $input:expr => $result:expr) => {
- #[test]
- fn $name() {
- assert_eq!(
- Pair {
- key: "",
- value: $input
- }
- .to_style(),
- $result
- );
- }
- };
- }
- // Styles
- test!(bold: "1" => Style::default().bold());
- test!(bold2: "01" => Style::default().bold());
- test!(under: "4" => Style::default().underline());
- test!(unde2: "04" => Style::default().underline());
- test!(both: "1;4" => Style::default().bold().underline());
- test!(both2: "01;04" => Style::default().bold().underline());
- test!(fg: "31" => Red.normal());
- test!(bg: "43" => Style::default().on(Yellow));
- test!(bfg: "31;43" => Red.on(Yellow));
- test!(bfg2: "0031;0043" => Red.on(Yellow));
- 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());
- // 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());
- 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());
- 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());
- }
- #[cfg(test)]
- mod test {
- use super::*;
- macro_rules! test {
- ($name:ident: $input:expr => $result:expr) => {
- #[test]
- fn $name() {
- let mut lscs = Vec::new();
- LSColors($input).each_pair(|p| lscs.push((p.key.clone(), p.to_style())));
- assert_eq!(lscs, $result.to_vec());
- }
- };
- }
- // Bad parses
- test!(empty: "" => []);
- test!(jibber: "blah" => []);
- test!(equals: "=" => []);
- test!(starts: "=di" => []);
- test!(ends: "id=" => []);
- // Foreground colours
- test!(green: "cb=32" => [ ("cb", Green.normal()) ]);
- test!(red: "di=31" => [ ("di", Red.normal()) ]);
- test!(blue: "la=34" => [ ("la", Blue.normal()) ]);
- // Background colours
- 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)) ]);
- // Bold and underline
- 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()) ]);
- // More and many
- 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()) ]);
- }
|