lsc.rs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. use std::iter::Peekable;
  2. use std::ops::FnMut;
  3. use ansiterm::{Colour, Style};
  4. use ansiterm::Colour::*;
  5. // Parsing the LS_COLORS environment variable into a map of names to Style values.
  6. //
  7. // This is sitting around undocumented at the moment because it’s a feature
  8. // that should really be unnecessary! exa highlights its output by creating a
  9. // theme of one Style value per part of the interface that can be coloured,
  10. // then reading styles from that theme. The LS_COLORS variable, on the other
  11. // hand, can contain arbitrary characters that ls is supposed to add to the
  12. // output, without needing to know what they actually do. This puts exa in the
  13. // annoying position of having to parse the ANSI escape codes _back_ into
  14. // Style values before it’s able to use them. Doing this has a lot of
  15. // downsides: if a new terminal feature is added with its own code, exa won’t
  16. // be able to use this without explicit support for parsing the feature, while
  17. // ls would not even need to know it existed. And there are some edge cases in
  18. // ANSI codes, where terminals would accept codes exa is strict about it. It’s
  19. // just not worth doing, and there should really be a way to just use slices
  20. // of the LS_COLORS string without having to parse them.
  21. pub struct LSColors<'var>(pub &'var str);
  22. impl<'var> LSColors<'var> {
  23. pub fn each_pair<C>(&mut self, mut callback: C)
  24. where C: FnMut(Pair<'var>)
  25. {
  26. for next in self.0.split(':') {
  27. let bits = next.split('=')
  28. .take(3)
  29. .collect::<Vec<_>>();
  30. if bits.len() == 2 && ! bits[0].is_empty() && ! bits[1].is_empty() {
  31. callback(Pair { key: bits[0], value: bits[1] });
  32. }
  33. }
  34. }
  35. }
  36. fn parse_into_high_colour<'a, I>(iter: &mut Peekable<I>) -> Option<Colour>
  37. where I: Iterator<Item = &'a str>
  38. {
  39. match iter.peek() {
  40. Some(&"5") => {
  41. let _ = iter.next();
  42. if let Some(byte) = iter.next() {
  43. if let Ok(num) = byte.parse() {
  44. return Some(Fixed(num));
  45. }
  46. }
  47. }
  48. Some(&"2") => {
  49. let _ = iter.next();
  50. if let Some(hexes) = iter.next() {
  51. // Some terminals support R:G:B instead of R;G;B
  52. // but this clashes with splitting on ‘:’ in each_pair above.
  53. /*if hexes.contains(':') {
  54. let rgb = hexes.splitn(3, ':').collect::<Vec<_>>();
  55. if rgb.len() != 3 {
  56. return None;
  57. }
  58. else if let (Ok(r), Ok(g), Ok(b)) = (rgb[0].parse(), rgb[1].parse(), rgb[2].parse()) {
  59. return Some(RGB(r, g, b));
  60. }
  61. }*/
  62. if let (Some(r), Some(g), Some(b)) = (hexes.parse().ok(),
  63. iter.next().and_then(|s| s.parse().ok()),
  64. iter.next().and_then(|s| s.parse().ok()))
  65. {
  66. return Some(RGB(r, g, b));
  67. }
  68. }
  69. }
  70. _ => {},
  71. }
  72. None
  73. }
  74. pub struct Pair<'var> {
  75. pub key: &'var str,
  76. pub value: &'var str,
  77. }
  78. impl<'var> Pair<'var> {
  79. pub fn to_style(&self) -> Style {
  80. let mut style = Style::default();
  81. let mut iter = self.value.split(';').peekable();
  82. while let Some(num) = iter.next() {
  83. match num.trim_start_matches('0') {
  84. // Bold and italic
  85. "1" => style = style.bold(),
  86. "2" => style = style.dimmed(),
  87. "3" => style = style.italic(),
  88. "4" => style = style.underline(),
  89. "5" => style = style.blink(),
  90. // 6 is supposedly a faster blink
  91. "7" => style = style.reverse(),
  92. "8" => style = style.hidden(),
  93. "9" => style = style.strikethrough(),
  94. // Foreground colours
  95. "30" => style = style.fg(Black),
  96. "31" => style = style.fg(Red),
  97. "32" => style = style.fg(Green),
  98. "33" => style = style.fg(Yellow),
  99. "34" => style = style.fg(Blue),
  100. "35" => style = style.fg(Purple),
  101. "36" => style = style.fg(Cyan),
  102. "37" => style = style.fg(White),
  103. // Bright foreground colours
  104. "90" => style = style.fg(DarkGray),
  105. "91" => style = style.fg(BrightRed),
  106. "92" => style = style.fg(BrightGreen),
  107. "93" => style = style.fg(BrightYellow),
  108. "94" => style = style.fg(BrightBlue),
  109. "95" => style = style.fg(BrightPurple),
  110. "96" => style = style.fg(BrightCyan),
  111. "97" => style = style.fg(BrightGray),
  112. "38" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.fg(c) },
  113. // Background colours
  114. "40" => style = style.on(Black),
  115. "41" => style = style.on(Red),
  116. "42" => style = style.on(Green),
  117. "43" => style = style.on(Yellow),
  118. "44" => style = style.on(Blue),
  119. "45" => style = style.on(Purple),
  120. "46" => style = style.on(Cyan),
  121. "47" => style = style.on(White),
  122. // Bright background colours
  123. "100" => style = style.on(DarkGray),
  124. "101" => style = style.on(BrightRed),
  125. "102" => style = style.on(BrightGreen),
  126. "103" => style = style.on(BrightYellow),
  127. "104" => style = style.on(BrightBlue),
  128. "105" => style = style.on(BrightPurple),
  129. "106" => style = style.on(BrightCyan),
  130. "107" => style = style.on(BrightGray),
  131. "48" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.on(c) },
  132. _ => {/* ignore the error and do nothing */},
  133. }
  134. }
  135. style
  136. }
  137. }
  138. #[cfg(test)]
  139. mod ansi_test {
  140. use super::*;
  141. use ansiterm::Style;
  142. macro_rules! test {
  143. ($name:ident: $input:expr => $result:expr) => {
  144. #[test]
  145. fn $name() {
  146. assert_eq!(Pair { key: "", value: $input }.to_style(), $result);
  147. }
  148. };
  149. }
  150. // Styles
  151. test!(bold: "1" => Style::default().bold());
  152. test!(bold2: "01" => Style::default().bold());
  153. test!(under: "4" => Style::default().underline());
  154. test!(unde2: "04" => Style::default().underline());
  155. test!(both: "1;4" => Style::default().bold().underline());
  156. test!(both2: "01;04" => Style::default().bold().underline());
  157. test!(fg: "31" => Red.normal());
  158. test!(bg: "43" => Style::default().on(Yellow));
  159. test!(bfg: "31;43" => Red.on(Yellow));
  160. test!(bfg2: "0031;0043" => Red.on(Yellow));
  161. test!(all: "43;31;1;4" => Red.on(Yellow).bold().underline());
  162. test!(again: "1;1;1;1;1" => Style::default().bold());
  163. // Failure cases
  164. test!(empty: "" => Style::default());
  165. test!(semis: ";;;;;;" => Style::default());
  166. test!(nines: "99999999" => Style::default());
  167. test!(word: "GREEN" => Style::default());
  168. // Higher colours
  169. test!(hifg: "38;5;149" => Fixed(149).normal());
  170. test!(hibg: "48;5;1" => Style::default().on(Fixed(1)));
  171. test!(hibo: "48;5;1;1" => Style::default().on(Fixed(1)).bold());
  172. test!(hiund: "4;48;5;1" => Style::default().on(Fixed(1)).underline());
  173. test!(rgb: "38;2;255;100;0" => Style::default().fg(RGB(255, 100, 0)));
  174. test!(rgbi: "38;2;255;100;0;3" => Style::default().fg(RGB(255, 100, 0)).italic());
  175. test!(rgbbg: "48;2;255;100;0" => Style::default().on(RGB(255, 100, 0)));
  176. test!(rgbbi: "48;2;255;100;0;3" => Style::default().on(RGB(255, 100, 0)).italic());
  177. test!(fgbg: "38;5;121;48;5;212" => Fixed(121).on(Fixed(212)));
  178. test!(bgfg: "48;5;121;38;5;212" => Fixed(212).on(Fixed(121)));
  179. test!(toohi: "48;5;999" => Style::default());
  180. }
  181. #[cfg(test)]
  182. mod test {
  183. use super::*;
  184. macro_rules! test {
  185. ($name:ident: $input:expr => $result:expr) => {
  186. #[test]
  187. fn $name() {
  188. let mut lscs = Vec::new();
  189. LSColors($input).each_pair(|p| lscs.push( (p.key.clone(), p.to_style()) ));
  190. assert_eq!(lscs, $result.to_vec());
  191. }
  192. };
  193. }
  194. // Bad parses
  195. test!(empty: "" => []);
  196. test!(jibber: "blah" => []);
  197. test!(equals: "=" => []);
  198. test!(starts: "=di" => []);
  199. test!(ends: "id=" => []);
  200. // Foreground colours
  201. test!(green: "cb=32" => [ ("cb", Green.normal()) ]);
  202. test!(red: "di=31" => [ ("di", Red.normal()) ]);
  203. test!(blue: "la=34" => [ ("la", Blue.normal()) ]);
  204. // Background colours
  205. test!(yellow: "do=43" => [ ("do", Style::default().on(Yellow)) ]);
  206. test!(purple: "re=45" => [ ("re", Style::default().on(Purple)) ]);
  207. test!(cyan: "mi=46" => [ ("mi", Style::default().on(Cyan)) ]);
  208. // Bold and underline
  209. test!(bold: "fa=1" => [ ("fa", Style::default().bold()) ]);
  210. test!(under: "so=4" => [ ("so", Style::default().underline()) ]);
  211. test!(both: "la=1;4" => [ ("la", Style::default().bold().underline()) ]);
  212. // More and many
  213. test!(more: "me=43;21;55;34:yu=1;4;1" => [ ("me", Blue.on(Yellow)), ("yu", Style::default().bold().underline()) ]);
  214. test!(many: "red=31:green=32:blue=34" => [ ("red", Red.normal()), ("green", Green.normal()), ("blue", Blue.normal()) ]);
  215. }