1 use ansi_term::Style;
2
3 use crate::fs::File;
4 use crate::output::file_name::Colours as FileNameColours;
5 use crate::output::render;
6
7 mod ui_styles;
8 pub use self::ui_styles::UiStyles;
9 pub use self::ui_styles::Size as SizeColours;
10
11 mod lsc;
12 pub use self::lsc::LSColors;
13
14 mod default_theme;
15
16
17 #[derive(PartialEq, Debug)]
18 pub struct Options {
19
20 pub use_colours: UseColours,
21
22 pub colour_scale: ColourScale,
23
24 pub definitions: Definitions,
25 }
26
27 /// Under what circumstances we should display coloured, rather than plain,
28 /// output to the terminal.
29 ///
30 /// By default, we want to display the colours when stdout can display them.
31 /// Turning them on when output is going to, say, a pipe, would make programs
32 /// such as `grep` or `more` not work properly. So the `Automatic` mode does
33 /// this check and only displays colours when they can be truly appreciated.
34 #[derive(PartialEq, Debug, Copy, Clone)]
35 pub enum UseColours {
36
37 /// Display them even when output isn’t going to a terminal.
38 Always,
39
40 /// Display them when output is going to a terminal, but not otherwise.
41 Automatic,
42
43 /// Never display them, even when output is going to a terminal.
44 Never,
45 }
46
47 #[derive(PartialEq, Debug, Copy, Clone)]
48 pub enum ColourScale {
49 Fixed,
50 Gradient,
51 }
52
53 #[derive(PartialEq, Debug, Default)]
54 pub struct Definitions {
55 pub ls: Option<String>,
56 pub exa: Option<String>,
57 }
58
59
60 pub struct Theme {
61 pub ui: UiStyles,
62 pub exts: Box<dyn FileColours>,
63 }
64
65 impl Options {
66
67 #[allow(trivial_casts)] // the `as Box<_>` stuff below warns about this for some reason
to_theme(&self, isatty: bool) -> Theme68 pub fn to_theme(&self, isatty: bool) -> Theme {
69 use crate::info::filetype::FileExtensions;
70
71 if self.use_colours == UseColours::Never || (self.use_colours == UseColours::Automatic && ! isatty) {
72 let ui = UiStyles::plain();
73 let exts = Box::new(NoFileColours);
74 return Theme { ui, exts };
75 }
76
77 // Parse the environment variables into colours and extension mappings
78 let mut ui = UiStyles::default_theme(self.colour_scale);
79 let (exts, use_default_filetypes) = self.definitions.parse_color_vars(&mut ui);
80
81 // Use between 0 and 2 file name highlighters
82 let exts = match (exts.is_non_empty(), use_default_filetypes) {
83 (false, false) => Box::new(NoFileColours) as Box<_>,
84 (false, true) => Box::new(FileExtensions) as Box<_>,
85 ( true, false) => Box::new(exts) as Box<_>,
86 ( true, true) => Box::new((exts, FileExtensions)) as Box<_>,
87 };
88
89 Theme { ui, exts }
90 }
91 }
92
93 impl Definitions {
94
95 /// Parse the environment variables into `LS_COLORS` pairs, putting file glob
96 /// colours into the `ExtensionMappings` that gets returned, and using the
97 /// two-character UI codes to modify the mutable `Colours`.
98 ///
99 /// Also returns if the `EXA_COLORS` variable should reset the existing file
100 /// type mappings or not. The `reset` code needs to be the first one.
parse_color_vars(&self, colours: &mut UiStyles) -> (ExtensionMappings, bool)101 fn parse_color_vars(&self, colours: &mut UiStyles) -> (ExtensionMappings, bool) {
102 use log::*;
103
104 let mut exts = ExtensionMappings::default();
105
106 if let Some(lsc) = &self.ls {
107 LSColors(lsc).each_pair(|pair| {
108 if ! colours.set_ls(&pair) {
109 match glob::Pattern::new(pair.key) {
110 Ok(pat) => {
111 exts.add(pat, pair.to_style());
112 }
113 Err(e) => {
114 warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
115 }
116 }
117 }
118 });
119 }
120
121 let mut use_default_filetypes = true;
122
123 if let Some(exa) = &self.exa {
124 // Is this hacky? Yes.
125 if exa == "reset" || exa.starts_with("reset:") {
126 use_default_filetypes = false;
127 }
128
129 LSColors(exa).each_pair(|pair| {
130 if ! colours.set_ls(&pair) && ! colours.set_exa(&pair) {
131 match glob::Pattern::new(pair.key) {
132 Ok(pat) => {
133 exts.add(pat, pair.to_style());
134 }
135 Err(e) => {
136 warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
137 }
138 }
139 };
140 });
141 }
142
143 (exts, use_default_filetypes)
144 }
145 }
146
147
148 pub trait FileColours: std::marker::Sync {
colour_file(&self, file: &File<'_>) -> Option<Style>149 fn colour_file(&self, file: &File<'_>) -> Option<Style>;
150 }
151
152 #[derive(PartialEq, Debug)]
153 struct NoFileColours;
154 impl FileColours for NoFileColours {
colour_file(&self, _file: &File<'_>) -> Option<Style>155 fn colour_file(&self, _file: &File<'_>) -> Option<Style> {
156 None
157 }
158 }
159
160 // When getting the colour of a file from a *pair* of colourisers, try the
161 // first one then try the second one. This lets the user provide their own
162 // file type associations, while falling back to the default set if not set
163 // explicitly.
164 impl<A, B> FileColours for (A, B)
165 where A: FileColours,
166 B: FileColours,
167 {
colour_file(&self, file: &File<'_>) -> Option<Style>168 fn colour_file(&self, file: &File<'_>) -> Option<Style> {
169 self.0.colour_file(file)
170 .or_else(|| self.1.colour_file(file))
171 }
172 }
173
174
175 #[derive(PartialEq, Debug, Default)]
176 struct ExtensionMappings {
177 mappings: Vec<(glob::Pattern, Style)>,
178 }
179
180 // Loop through backwards so that colours specified later in the list override
181 // colours specified earlier, like we do with options and strict mode
182
183 impl FileColours for ExtensionMappings {
colour_file(&self, file: &File<'_>) -> Option<Style>184 fn colour_file(&self, file: &File<'_>) -> Option<Style> {
185 self.mappings.iter().rev()
186 .find(|t| t.0.matches(&file.name))
187 .map (|t| t.1)
188 }
189 }
190
191 impl ExtensionMappings {
is_non_empty(&self) -> bool192 fn is_non_empty(&self) -> bool {
193 ! self.mappings.is_empty()
194 }
195
add(&mut self, pattern: glob::Pattern, style: Style)196 fn add(&mut self, pattern: glob::Pattern, style: Style) {
197 self.mappings.push((pattern, style))
198 }
199 }
200
201
202
203
204 impl render::BlocksColours for Theme {
block_count(&self) -> Style205 fn block_count(&self) -> Style { self.ui.blocks }
no_blocks(&self) -> Style206 fn no_blocks(&self) -> Style { self.ui.punctuation }
207 }
208
209 impl render::FiletypeColours for Theme {
normal(&self) -> Style210 fn normal(&self) -> Style { self.ui.filekinds.normal }
directory(&self) -> Style211 fn directory(&self) -> Style { self.ui.filekinds.directory }
pipe(&self) -> Style212 fn pipe(&self) -> Style { self.ui.filekinds.pipe }
symlink(&self) -> Style213 fn symlink(&self) -> Style { self.ui.filekinds.symlink }
block_device(&self) -> Style214 fn block_device(&self) -> Style { self.ui.filekinds.block_device }
char_device(&self) -> Style215 fn char_device(&self) -> Style { self.ui.filekinds.char_device }
socket(&self) -> Style216 fn socket(&self) -> Style { self.ui.filekinds.socket }
special(&self) -> Style217 fn special(&self) -> Style { self.ui.filekinds.special }
218 }
219
220 impl render::GitColours for Theme {
not_modified(&self) -> Style221 fn not_modified(&self) -> Style { self.ui.punctuation }
222 #[allow(clippy::new_ret_no_self)]
new(&self) -> Style223 fn new(&self) -> Style { self.ui.git.new }
modified(&self) -> Style224 fn modified(&self) -> Style { self.ui.git.modified }
deleted(&self) -> Style225 fn deleted(&self) -> Style { self.ui.git.deleted }
renamed(&self) -> Style226 fn renamed(&self) -> Style { self.ui.git.renamed }
type_change(&self) -> Style227 fn type_change(&self) -> Style { self.ui.git.typechange }
ignored(&self) -> Style228 fn ignored(&self) -> Style { self.ui.git.ignored }
conflicted(&self) -> Style229 fn conflicted(&self) -> Style { self.ui.git.conflicted }
230 }
231
232 impl render::GroupColours for Theme {
yours(&self) -> Style233 fn yours(&self) -> Style { self.ui.users.group_yours }
not_yours(&self) -> Style234 fn not_yours(&self) -> Style { self.ui.users.group_not_yours }
235 }
236
237 impl render::LinksColours for Theme {
normal(&self) -> Style238 fn normal(&self) -> Style { self.ui.links.normal }
multi_link_file(&self) -> Style239 fn multi_link_file(&self) -> Style { self.ui.links.multi_link_file }
240 }
241
242 impl render::PermissionsColours for Theme {
dash(&self) -> Style243 fn dash(&self) -> Style { self.ui.punctuation }
user_read(&self) -> Style244 fn user_read(&self) -> Style { self.ui.perms.user_read }
user_write(&self) -> Style245 fn user_write(&self) -> Style { self.ui.perms.user_write }
user_execute_file(&self) -> Style246 fn user_execute_file(&self) -> Style { self.ui.perms.user_execute_file }
user_execute_other(&self) -> Style247 fn user_execute_other(&self) -> Style { self.ui.perms.user_execute_other }
group_read(&self) -> Style248 fn group_read(&self) -> Style { self.ui.perms.group_read }
group_write(&self) -> Style249 fn group_write(&self) -> Style { self.ui.perms.group_write }
group_execute(&self) -> Style250 fn group_execute(&self) -> Style { self.ui.perms.group_execute }
other_read(&self) -> Style251 fn other_read(&self) -> Style { self.ui.perms.other_read }
other_write(&self) -> Style252 fn other_write(&self) -> Style { self.ui.perms.other_write }
other_execute(&self) -> Style253 fn other_execute(&self) -> Style { self.ui.perms.other_execute }
special_user_file(&self) -> Style254 fn special_user_file(&self) -> Style { self.ui.perms.special_user_file }
special_other(&self) -> Style255 fn special_other(&self) -> Style { self.ui.perms.special_other }
attribute(&self) -> Style256 fn attribute(&self) -> Style { self.ui.perms.attribute }
257 }
258
259 impl render::SizeColours for Theme {
size(&self, prefix: Option<number_prefix::Prefix>) -> Style260 fn size(&self, prefix: Option<number_prefix::Prefix>) -> Style {
261 use number_prefix::Prefix::*;
262
263 match prefix {
264 None => self.ui.size.number_byte,
265 Some(Kilo) | Some(Kibi) => self.ui.size.number_kilo,
266 Some(Mega) | Some(Mebi) => self.ui.size.number_mega,
267 Some(Giga) | Some(Gibi) => self.ui.size.number_giga,
268 Some(_) => self.ui.size.number_huge,
269 }
270 }
271
unit(&self, prefix: Option<number_prefix::Prefix>) -> Style272 fn unit(&self, prefix: Option<number_prefix::Prefix>) -> Style {
273 use number_prefix::Prefix::*;
274
275 match prefix {
276 None => self.ui.size.unit_byte,
277 Some(Kilo) | Some(Kibi) => self.ui.size.unit_kilo,
278 Some(Mega) | Some(Mebi) => self.ui.size.unit_mega,
279 Some(Giga) | Some(Gibi) => self.ui.size.unit_giga,
280 Some(_) => self.ui.size.unit_huge,
281 }
282 }
283
no_size(&self) -> Style284 fn no_size(&self) -> Style { self.ui.punctuation }
major(&self) -> Style285 fn major(&self) -> Style { self.ui.size.major }
comma(&self) -> Style286 fn comma(&self) -> Style { self.ui.punctuation }
minor(&self) -> Style287 fn minor(&self) -> Style { self.ui.size.minor }
288 }
289
290 impl render::UserColours for Theme {
you(&self) -> Style291 fn you(&self) -> Style { self.ui.users.user_you }
someone_else(&self) -> Style292 fn someone_else(&self) -> Style { self.ui.users.user_someone_else }
293 }
294
295 impl FileNameColours for Theme {
normal_arrow(&self) -> Style296 fn normal_arrow(&self) -> Style { self.ui.punctuation }
broken_symlink(&self) -> Style297 fn broken_symlink(&self) -> Style { self.ui.broken_symlink }
broken_filename(&self) -> Style298 fn broken_filename(&self) -> Style { apply_overlay(self.ui.broken_symlink, self.ui.broken_path_overlay) }
broken_control_char(&self) -> Style299 fn broken_control_char(&self) -> Style { apply_overlay(self.ui.control_char, self.ui.broken_path_overlay) }
control_char(&self) -> Style300 fn control_char(&self) -> Style { self.ui.control_char }
symlink_path(&self) -> Style301 fn symlink_path(&self) -> Style { self.ui.symlink_path }
executable_file(&self) -> Style302 fn executable_file(&self) -> Style { self.ui.filekinds.executable }
303
colour_file(&self, file: &File<'_>) -> Style304 fn colour_file(&self, file: &File<'_>) -> Style {
305 self.exts.colour_file(file).unwrap_or(self.ui.filekinds.normal)
306 }
307 }
308
309
310 /// Some of the styles are **overlays**: although they have the same attribute
311 /// set as regular styles (foreground and background colours, bold, underline,
312 /// etc), they’re intended to be used to *amend* existing styles.
313 ///
314 /// For example, the target path of a broken symlink is displayed in a red,
315 /// underlined style by default. Paths can contain control characters, so
316 /// these control characters need to be underlined too, otherwise it looks
317 /// weird. So instead of having four separate configurable styles for “link
318 /// path”, “broken link path”, “control character” and “broken control
319 /// character”, there are styles for “link path”, “control character”, and
320 /// “broken link overlay”, the latter of which is just set to override the
321 /// underline attribute on the other two.
apply_overlay(mut base: Style, overlay: Style) -> Style322 fn apply_overlay(mut base: Style, overlay: Style) -> Style {
323 if let Some(fg) = overlay.foreground { base.foreground = Some(fg); }
324 if let Some(bg) = overlay.background { base.background = Some(bg); }
325
326 if overlay.is_bold { base.is_bold = true; }
327 if overlay.is_dimmed { base.is_dimmed = true; }
328 if overlay.is_italic { base.is_italic = true; }
329 if overlay.is_underline { base.is_underline = true; }
330 if overlay.is_blink { base.is_blink = true; }
331 if overlay.is_reverse { base.is_reverse = true; }
332 if overlay.is_hidden { base.is_hidden = true; }
333 if overlay.is_strikethrough { base.is_strikethrough = true; }
334
335 base
336 }
337 // TODO: move this function to the ansi_term crate
338
339
340 #[cfg(test)]
341 mod customs_test {
342 use super::*;
343 use crate::theme::ui_styles::UiStyles;
344 use ansi_term::Colour::*;
345
346 macro_rules! test {
347 ($name:ident: ls $ls:expr, exa $exa:expr => colours $expected:ident -> $process_expected:expr) => {
348 #[test]
349 fn $name() {
350 let mut $expected = UiStyles::default();
351 $process_expected();
352
353 let definitions = Definitions {
354 ls: Some($ls.into()),
355 exa: Some($exa.into()),
356 };
357
358 let mut result = UiStyles::default();
359 let (_exts, _reset) = definitions.parse_color_vars(&mut result);
360 assert_eq!($expected, result);
361 }
362 };
363 ($name:ident: ls $ls:expr, exa $exa:expr => exts $mappings:expr) => {
364 #[test]
365 fn $name() {
366 let mappings: Vec<(glob::Pattern, Style)>
367 = $mappings.iter()
368 .map(|t| (glob::Pattern::new(t.0).unwrap(), t.1))
369 .collect();
370
371 let definitions = Definitions {
372 ls: Some($ls.into()),
373 exa: Some($exa.into()),
374 };
375
376 let (result, _reset) = definitions.parse_color_vars(&mut UiStyles::default());
377 assert_eq!(ExtensionMappings { mappings }, result);
378 }
379 };
380 ($name:ident: ls $ls:expr, exa $exa:expr => colours $expected:ident -> $process_expected:expr, exts $mappings:expr) => {
381 #[test]
382 fn $name() {
383 let mut $expected = UiStyles::colourful(false);
384 $process_expected();
385
386 let mappings: Vec<(glob::Pattern, Style)>
387 = $mappings.into_iter()
388 .map(|t| (glob::Pattern::new(t.0).unwrap(), t.1))
389 .collect();
390
391 let definitions = Definitions {
392 ls: Some($ls.into()),
393 exa: Some($exa.into()),
394 };
395
396 let mut meh = UiStyles::colourful(false);
397 let (result, _reset) = definitions.parse_color_vars(&vars, &mut meh);
398 assert_eq!(ExtensionMappings { mappings }, result);
399 assert_eq!($expected, meh);
400 }
401 };
402 }
403
404
405 // LS_COLORS can affect all of these colours:
406 test!(ls_di: ls "di=31", exa "" => colours c -> { c.filekinds.directory = Red.normal(); });
407 test!(ls_ex: ls "ex=32", exa "" => colours c -> { c.filekinds.executable = Green.normal(); });
408 test!(ls_fi: ls "fi=33", exa "" => colours c -> { c.filekinds.normal = Yellow.normal(); });
409 test!(ls_pi: ls "pi=34", exa "" => colours c -> { c.filekinds.pipe = Blue.normal(); });
410 test!(ls_so: ls "so=35", exa "" => colours c -> { c.filekinds.socket = Purple.normal(); });
411 test!(ls_bd: ls "bd=36", exa "" => colours c -> { c.filekinds.block_device = Cyan.normal(); });
412 test!(ls_cd: ls "cd=35", exa "" => colours c -> { c.filekinds.char_device = Purple.normal(); });
413 test!(ls_ln: ls "ln=34", exa "" => colours c -> { c.filekinds.symlink = Blue.normal(); });
414 test!(ls_or: ls "or=33", exa "" => colours c -> { c.broken_symlink = Yellow.normal(); });
415
416 // EXA_COLORS can affect all those colours too:
417 test!(exa_di: ls "", exa "di=32" => colours c -> { c.filekinds.directory = Green.normal(); });
418 test!(exa_ex: ls "", exa "ex=33" => colours c -> { c.filekinds.executable = Yellow.normal(); });
419 test!(exa_fi: ls "", exa "fi=34" => colours c -> { c.filekinds.normal = Blue.normal(); });
420 test!(exa_pi: ls "", exa "pi=35" => colours c -> { c.filekinds.pipe = Purple.normal(); });
421 test!(exa_so: ls "", exa "so=36" => colours c -> { c.filekinds.socket = Cyan.normal(); });
422 test!(exa_bd: ls "", exa "bd=35" => colours c -> { c.filekinds.block_device = Purple.normal(); });
423 test!(exa_cd: ls "", exa "cd=34" => colours c -> { c.filekinds.char_device = Blue.normal(); });
424 test!(exa_ln: ls "", exa "ln=33" => colours c -> { c.filekinds.symlink = Yellow.normal(); });
425 test!(exa_or: ls "", exa "or=32" => colours c -> { c.broken_symlink = Green.normal(); });
426
427 // EXA_COLORS will even override options from LS_COLORS:
428 test!(ls_exa_di: ls "di=31", exa "di=32" => colours c -> { c.filekinds.directory = Green.normal(); });
429 test!(ls_exa_ex: ls "ex=32", exa "ex=33" => colours c -> { c.filekinds.executable = Yellow.normal(); });
430 test!(ls_exa_fi: ls "fi=33", exa "fi=34" => colours c -> { c.filekinds.normal = Blue.normal(); });
431
432 // But more importantly, EXA_COLORS has its own, special list of colours:
433 test!(exa_ur: ls "", exa "ur=38;5;100" => colours c -> { c.perms.user_read = Fixed(100).normal(); });
434 test!(exa_uw: ls "", exa "uw=38;5;101" => colours c -> { c.perms.user_write = Fixed(101).normal(); });
435 test!(exa_ux: ls "", exa "ux=38;5;102" => colours c -> { c.perms.user_execute_file = Fixed(102).normal(); });
436 test!(exa_ue: ls "", exa "ue=38;5;103" => colours c -> { c.perms.user_execute_other = Fixed(103).normal(); });
437 test!(exa_gr: ls "", exa "gr=38;5;104" => colours c -> { c.perms.group_read = Fixed(104).normal(); });
438 test!(exa_gw: ls "", exa "gw=38;5;105" => colours c -> { c.perms.group_write = Fixed(105).normal(); });
439 test!(exa_gx: ls "", exa "gx=38;5;106" => colours c -> { c.perms.group_execute = Fixed(106).normal(); });
440 test!(exa_tr: ls "", exa "tr=38;5;107" => colours c -> { c.perms.other_read = Fixed(107).normal(); });
441 test!(exa_tw: ls "", exa "tw=38;5;108" => colours c -> { c.perms.other_write = Fixed(108).normal(); });
442 test!(exa_tx: ls "", exa "tx=38;5;109" => colours c -> { c.perms.other_execute = Fixed(109).normal(); });
443 test!(exa_su: ls "", exa "su=38;5;110" => colours c -> { c.perms.special_user_file = Fixed(110).normal(); });
444 test!(exa_sf: ls "", exa "sf=38;5;111" => colours c -> { c.perms.special_other = Fixed(111).normal(); });
445 test!(exa_xa: ls "", exa "xa=38;5;112" => colours c -> { c.perms.attribute = Fixed(112).normal(); });
446
447 test!(exa_sn: ls "", exa "sn=38;5;113" => colours c -> {
448 c.size.number_byte = Fixed(113).normal();
449 c.size.number_kilo = Fixed(113).normal();
450 c.size.number_mega = Fixed(113).normal();
451 c.size.number_giga = Fixed(113).normal();
452 c.size.number_huge = Fixed(113).normal();
453 });
454 test!(exa_sb: ls "", exa "sb=38;5;114" => colours c -> {
455 c.size.unit_byte = Fixed(114).normal();
456 c.size.unit_kilo = Fixed(114).normal();
457 c.size.unit_mega = Fixed(114).normal();
458 c.size.unit_giga = Fixed(114).normal();
459 c.size.unit_huge = Fixed(114).normal();
460 });
461
462 test!(exa_nb: ls "", exa "nb=38;5;115" => colours c -> { c.size.number_byte = Fixed(115).normal(); });
463 test!(exa_nk: ls "", exa "nk=38;5;116" => colours c -> { c.size.number_kilo = Fixed(116).normal(); });
464 test!(exa_nm: ls "", exa "nm=38;5;117" => colours c -> { c.size.number_mega = Fixed(117).normal(); });
465 test!(exa_ng: ls "", exa "ng=38;5;118" => colours c -> { c.size.number_giga = Fixed(118).normal(); });
466 test!(exa_nh: ls "", exa "nh=38;5;119" => colours c -> { c.size.number_huge = Fixed(119).normal(); });
467
468 test!(exa_ub: ls "", exa "ub=38;5;115" => colours c -> { c.size.unit_byte = Fixed(115).normal(); });
469 test!(exa_uk: ls "", exa "uk=38;5;116" => colours c -> { c.size.unit_kilo = Fixed(116).normal(); });
470 test!(exa_um: ls "", exa "um=38;5;117" => colours c -> { c.size.unit_mega = Fixed(117).normal(); });
471 test!(exa_ug: ls "", exa "ug=38;5;118" => colours c -> { c.size.unit_giga = Fixed(118).normal(); });
472 test!(exa_uh: ls "", exa "uh=38;5;119" => colours c -> { c.size.unit_huge = Fixed(119).normal(); });
473
474 test!(exa_df: ls "", exa "df=38;5;115" => colours c -> { c.size.major = Fixed(115).normal(); });
475 test!(exa_ds: ls "", exa "ds=38;5;116" => colours c -> { c.size.minor = Fixed(116).normal(); });
476
477 test!(exa_uu: ls "", exa "uu=38;5;117" => colours c -> { c.users.user_you = Fixed(117).normal(); });
478 test!(exa_un: ls "", exa "un=38;5;118" => colours c -> { c.users.user_someone_else = Fixed(118).normal(); });
479 test!(exa_gu: ls "", exa "gu=38;5;119" => colours c -> { c.users.group_yours = Fixed(119).normal(); });
480 test!(exa_gn: ls "", exa "gn=38;5;120" => colours c -> { c.users.group_not_yours = Fixed(120).normal(); });
481
482 test!(exa_lc: ls "", exa "lc=38;5;121" => colours c -> { c.links.normal = Fixed(121).normal(); });
483 test!(exa_lm: ls "", exa "lm=38;5;122" => colours c -> { c.links.multi_link_file = Fixed(122).normal(); });
484
485 test!(exa_ga: ls "", exa "ga=38;5;123" => colours c -> { c.git.new = Fixed(123).normal(); });
486 test!(exa_gm: ls "", exa "gm=38;5;124" => colours c -> { c.git.modified = Fixed(124).normal(); });
487 test!(exa_gd: ls "", exa "gd=38;5;125" => colours c -> { c.git.deleted = Fixed(125).normal(); });
488 test!(exa_gv: ls "", exa "gv=38;5;126" => colours c -> { c.git.renamed = Fixed(126).normal(); });
489 test!(exa_gt: ls "", exa "gt=38;5;127" => colours c -> { c.git.typechange = Fixed(127).normal(); });
490
491 test!(exa_xx: ls "", exa "xx=38;5;128" => colours c -> { c.punctuation = Fixed(128).normal(); });
492 test!(exa_da: ls "", exa "da=38;5;129" => colours c -> { c.date = Fixed(129).normal(); });
493 test!(exa_in: ls "", exa "in=38;5;130" => colours c -> { c.inode = Fixed(130).normal(); });
494 test!(exa_bl: ls "", exa "bl=38;5;131" => colours c -> { c.blocks = Fixed(131).normal(); });
495 test!(exa_hd: ls "", exa "hd=38;5;132" => colours c -> { c.header = Fixed(132).normal(); });
496 test!(exa_lp: ls "", exa "lp=38;5;133" => colours c -> { c.symlink_path = Fixed(133).normal(); });
497 test!(exa_cc: ls "", exa "cc=38;5;134" => colours c -> { c.control_char = Fixed(134).normal(); });
498 test!(exa_bo: ls "", exa "bO=4" => colours c -> { c.broken_path_overlay = Style::default().underline(); });
499
500 // All the while, LS_COLORS treats them as filenames:
501 test!(ls_uu: ls "uu=38;5;117", exa "" => exts [ ("uu", Fixed(117).normal()) ]);
502 test!(ls_un: ls "un=38;5;118", exa "" => exts [ ("un", Fixed(118).normal()) ]);
503 test!(ls_gu: ls "gu=38;5;119", exa "" => exts [ ("gu", Fixed(119).normal()) ]);
504 test!(ls_gn: ls "gn=38;5;120", exa "" => exts [ ("gn", Fixed(120).normal()) ]);
505
506 // Just like all other keys:
507 test!(ls_txt: ls "*.txt=31", exa "" => exts [ ("*.txt", Red.normal()) ]);
508 test!(ls_mp3: ls "*.mp3=38;5;135", exa "" => exts [ ("*.mp3", Fixed(135).normal()) ]);
509 test!(ls_mak: ls "Makefile=1;32;4", exa "" => exts [ ("Makefile", Green.bold().underline()) ]);
510 test!(exa_txt: ls "", exa "*.zip=31" => exts [ ("*.zip", Red.normal()) ]);
511 test!(exa_mp3: ls "", exa "lev.*=38;5;153" => exts [ ("lev.*", Fixed(153).normal()) ]);
512 test!(exa_mak: ls "", exa "Cargo.toml=4;32;1" => exts [ ("Cargo.toml", Green.bold().underline()) ]);
513
514 // Testing whether a glob from EXA_COLORS overrides a glob from LS_COLORS
515 // can’t be tested here, because they’ll both be added to the same vec
516
517 // Values get separated by colons:
518 test!(ls_multi: ls "*.txt=31:*.rtf=32", exa "" => exts [ ("*.txt", Red.normal()), ("*.rtf", Green.normal()) ]);
519 test!(exa_multi: ls "", exa "*.tmp=37:*.log=37" => exts [ ("*.tmp", White.normal()), ("*.log", White.normal()) ]);
520
521 test!(ls_five: ls "1*1=31:2*2=32:3*3=1;33:4*4=34;1:5*5=35;4", exa "" => exts [
522 ("1*1", Red.normal()), ("2*2", Green.normal()), ("3*3", Yellow.bold()), ("4*4", Blue.bold()), ("5*5", Purple.underline())
523 ]);
524
525 // Finally, colours get applied right-to-left:
526 test!(ls_overwrite: ls "pi=31:pi=32:pi=33", exa "" => colours c -> { c.filekinds.pipe = Yellow.normal(); });
527 test!(exa_overwrite: ls "", exa "da=36:da=35:da=34" => colours c -> { c.date = Blue.normal(); });
528 }
529