1use nu_ansi_term::Style;
8
9use std::collections::HashMap;
10
11use crate::fs::File;
12use crate::info::filetype::FileType;
13use crate::options::config::ThemeConfig;
14use crate::output::color_scale::ColorScaleOptions;
15use crate::output::file_name::Colours as FileNameColours;
16use crate::output::render;
17
18mod ui_styles;
19pub(crate) use self::ui_styles::FileType as ThemeFileType;
20pub use self::ui_styles::UiStyles;
21pub(crate) use self::ui_styles::*;
22
23mod lsc;
24pub use self::lsc::LSColors;
25
26mod default_theme;
27
28#[derive(PartialEq, Eq, Debug)]
29pub struct Options {
30 pub use_colours: UseColours,
31
32 pub colour_scale: ColorScaleOptions,
33
34 pub definitions: Definitions,
35
36 pub theme_config: Option<ThemeConfig>,
37}
38
39#[derive(PartialEq, Eq, Debug, Copy, Clone)]
47pub enum UseColours {
48 Always,
50
51 Automatic,
53
54 Never,
56}
57
58#[derive(PartialEq, Eq, Debug, Default)]
59pub struct Definitions {
60 pub ls: Option<String>,
61 pub exa: Option<String>,
62}
63
64pub struct Theme {
65 pub ui: UiStyles,
66 pub exts: Box<dyn FileStyle>,
67}
68
69impl Options {
70 #[must_use]
71 pub fn to_theme(&self, isatty: bool) -> Theme {
72 if self.use_colours == UseColours::Never
73 || (self.use_colours == UseColours::Automatic && !isatty)
74 {
75 let ui = UiStyles::plain();
76 let exts = Box::new(NoFileStyle);
77 return Theme { ui, exts };
78 }
79
80 #[cfg(windows)]
81 if nu_ansi_term::enable_ansi_support().is_err() {
82 if self.use_colours == UseColours::Always {
85 eprintln!("eza: Ignoring option color=always in legacy console.");
86 }
87 let ui = UiStyles::plain();
88 let exts = Box::new(NoFileStyle);
89 return Theme { ui, exts };
90 }
91
92 match self.theme_config {
93 Some(ref theme) => {
94 if let Some(mut ui) = theme.to_theme() {
95 let (exts, use_default_filetypes) = self.definitions.parse_color_vars(&mut ui);
96 let exts: Box<dyn FileStyle> =
97 match (exts.is_non_empty(), use_default_filetypes) {
98 (false, false) => Box::new(NoFileStyle),
99 (false, true) => Box::new(FileTypes),
100 (true, false) => Box::new(exts),
101 (true, true) => Box::new((exts, FileTypes)),
102 };
103 return Theme { ui, exts };
104 }
105 self.default_theme()
106 }
107 None => self.default_theme(),
108 }
109 }
110
111 fn default_theme(&self) -> Theme {
112 let mut ui = UiStyles::default_theme(self.colour_scale);
113 let (exts, use_default_filetypes) = self.definitions.parse_color_vars(&mut ui);
114 let exts: Box<dyn FileStyle> = match (exts.is_non_empty(), use_default_filetypes) {
115 (false, false) => Box::new(NoFileStyle),
116 (false, true) => Box::new(FileTypes),
117 (true, false) => Box::new(exts),
118 (true, true) => Box::new((exts, FileTypes)),
119 };
120 Theme { ui, exts }
121 }
122}
123
124impl Definitions {
125 fn parse_color_vars(&self, colours: &mut UiStyles) -> (ExtensionMappings, bool) {
132 use log::warn;
133
134 let mut exts = ExtensionMappings::default();
135
136 if let Some(lsc) = &self.ls {
137 LSColors(lsc).each_pair(|pair| {
138 if !colours.set_ls(&pair) {
139 match glob::Pattern::new(pair.key) {
140 Ok(pat) => {
141 exts.add(pat, pair.to_style());
142 }
143 Err(e) => {
144 warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
145 }
146 }
147 }
148 });
149 }
150
151 let mut use_default_filetypes = true;
152
153 if let Some(exa) = &self.exa {
154 if exa == "reset" || exa.starts_with("reset:") {
156 use_default_filetypes = false;
157 }
158
159 LSColors(exa).each_pair(|pair| {
160 if !colours.set_ls(&pair) && !colours.set_exa(&pair) {
161 match glob::Pattern::new(pair.key) {
162 Ok(pat) => {
163 exts.add(pat, pair.to_style());
164 }
165 Err(e) => {
166 warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
167 }
168 }
169 }
170 });
171 }
172
173 (exts, use_default_filetypes)
174 }
175}
176
177pub trait FileStyle: Sync {
179 fn get_style(&self, file: &File<'_>, theme: &Theme) -> Option<Style>;
182}
183
184#[derive(PartialEq, Debug)]
185struct NoFileStyle;
186
187impl FileStyle for NoFileStyle {
188 fn get_style(&self, _file: &File<'_>, _theme: &Theme) -> Option<Style> {
189 None
190 }
191}
192
193impl<A, B> FileStyle for (A, B)
198where
199 A: FileStyle,
200 B: FileStyle,
201{
202 fn get_style(&self, file: &File<'_>, theme: &Theme) -> Option<Style> {
203 self.0
204 .get_style(file, theme)
205 .or_else(|| self.1.get_style(file, theme))
206 }
207}
208
209#[derive(PartialEq, Debug, Default)]
210struct ExtensionMappings {
211 mappings: Vec<GlobPattern>,
212}
213
214#[derive(PartialEq, Debug)]
215enum GlobPattern {
226 Complex(glob::Pattern, Style),
227 Simple(HashMap<String, Style>),
228}
229
230impl ExtensionMappings {
231 fn is_non_empty(&self) -> bool {
232 !self.mappings.is_empty()
233 }
234
235 fn add(&mut self, pattern: glob::Pattern, style: Style) {
236 match (self.mappings.last_mut(), is_simple_pattern(pattern)) {
237 (Some(GlobPattern::Simple(h)), Ok(s)) => {
238 h.insert(s, style);
239 }
240 (_, Ok(s)) => {
241 self.mappings
242 .push(GlobPattern::Simple(HashMap::from([(s, style)])));
243 }
244 (_, Err(p)) => {
245 self.mappings.push(GlobPattern::Complex(p, style));
246 }
247 }
248 }
249}
250
251fn is_simple_pattern(pattern: glob::Pattern) -> Result<String, glob::Pattern> {
252 match pattern.as_str().strip_prefix("*.") {
253 None => Err(pattern),
260 Some(ext) if ext.contains(['?', '*', '[', ']', '.']) => Err(pattern),
261 Some(ext) => Ok(ext.to_string()),
262 }
263}
264
265impl FileStyle for ExtensionMappings {
269 fn get_style(&self, file: &File<'_>, _theme: &Theme) -> Option<Style> {
270 let maybe_ext = file.name.rsplit_once('.').map(|x| x.1);
271
272 for mapping in self.mappings.iter().rev() {
273 match mapping {
274 GlobPattern::Complex(pat, style) => {
275 if pat.matches(&file.name) {
276 return Some(*style);
277 }
278 }
279 GlobPattern::Simple(map) => {
280 if let Some(ext) = maybe_ext {
281 if let Some(style) = map.get(ext) {
282 return Some(*style);
283 }
284 }
285 }
286 }
287 }
288
289 None
290 }
291}
292
293#[derive(Debug)]
294struct FileTypes;
295
296impl FileStyle for FileTypes {
297 fn get_style(&self, file: &File<'_>, theme: &Theme) -> Option<Style> {
298 #[rustfmt::skip]
299 return match FileType::get_file_type(file) {
300 Some(FileType::Image) => theme.ui.file_type.unwrap_or_default().image,
301 Some(FileType::Video) => theme.ui.file_type.unwrap_or_default().video,
302 Some(FileType::Music) => theme.ui.file_type.unwrap_or_default().music,
303 Some(FileType::Lossless) => theme.ui.file_type.unwrap_or_default().lossless,
304 Some(FileType::Crypto) => theme.ui.file_type.unwrap_or_default().crypto,
305 Some(FileType::Document) => theme.ui.file_type.unwrap_or_default().document,
306 Some(FileType::Compressed) => theme.ui.file_type.unwrap_or_default().compressed,
307 Some(FileType::Temp) => theme.ui.file_type.unwrap_or_default().temp,
308 Some(FileType::Compiled) => theme.ui.file_type.unwrap_or_default().compiled,
309 Some(FileType::Build) => theme.ui.file_type.unwrap_or_default().build,
310 Some(FileType::Source) => theme.ui.file_type.unwrap_or_default().source,
311 None => None
312 };
313 }
314}
315
316#[cfg(unix)]
317impl render::BlocksColours for Theme {
318 fn blocksize(&self, prefix: Option<number_prefix::Prefix>) -> Style {
319 use number_prefix::Prefix::{Gibi, Giga, Kibi, Kilo, Mebi, Mega};
320
321 #[rustfmt::skip]
322 let style = match prefix {
323 Some(Kilo | Kibi) => self.ui.size.unwrap_or_default().number_kilo,
324 Some(Mega | Mebi) => self.ui.size.unwrap_or_default().number_mega,
325 Some(Giga | Gibi) => self.ui.size.unwrap_or_default().number_giga,
326 Some(_) => self.ui.size.unwrap_or_default().number_huge,
327 None => self.ui.size.unwrap_or_default().number_byte,
328 };
329 style.unwrap_or_default()
330 }
331
332 fn unit(&self, prefix: Option<number_prefix::Prefix>) -> Style {
333 use number_prefix::Prefix::{Gibi, Giga, Kibi, Kilo, Mebi, Mega};
334
335 #[rustfmt::skip]
336 let style = match prefix {
337 Some(Kilo | Kibi) => self.ui.size.unwrap_or_default().unit_kilo,
338 Some(Mega | Mebi) => self.ui.size.unwrap_or_default().unit_mega,
339 Some(Giga | Gibi) => self.ui.size.unwrap_or_default().unit_giga,
340 Some(_) => self.ui.size.unwrap_or_default().unit_huge,
341 None => self.ui.size.unwrap_or_default().unit_byte,
342 };
343 style.unwrap_or_default()
344 }
345
346 fn no_blocksize(&self) -> Style {
347 self.ui.punctuation.unwrap_or_default()
348 }
349}
350
351#[rustfmt::skip]
352impl render::FiletypeColours for Theme {
353 fn normal(&self) -> Style { self.ui.filekinds.unwrap_or_default().normal() }
354 fn directory(&self) -> Style { self.ui.filekinds.unwrap_or_default().directory() }
355 fn pipe(&self) -> Style { self.ui.filekinds.unwrap_or_default().pipe() }
356 fn symlink(&self) -> Style { self.ui.filekinds.unwrap_or_default().symlink() }
357 fn block_device(&self) -> Style { self.ui.filekinds.unwrap_or_default().block_device() }
358 fn char_device(&self) -> Style { self.ui.filekinds.unwrap_or_default().char_device() }
359 fn socket(&self) -> Style { self.ui.filekinds.unwrap_or_default().socket() }
360 fn special(&self) -> Style { self.ui.filekinds.unwrap_or_default().special() }
361}
362
363#[rustfmt::skip]
364impl render::GitColours for Theme {
365 fn not_modified(&self) -> Style { self.ui.punctuation() }
366 #[allow(clippy::new_ret_no_self)]
367 fn new(&self) -> Style { self.ui.git.unwrap_or_default().new() }
368 fn modified(&self) -> Style { self.ui.git.unwrap_or_default().modified() }
369 fn deleted(&self) -> Style { self.ui.git.unwrap_or_default().deleted() }
370 fn renamed(&self) -> Style { self.ui.git.unwrap_or_default().renamed() }
371 fn type_change(&self) -> Style { self.ui.git.unwrap_or_default().typechange() }
372 fn ignored(&self) -> Style { self.ui.git.unwrap_or_default().ignored() }
373 fn conflicted(&self) -> Style { self.ui.git.unwrap_or_default().conflicted() }
374}
375
376#[rustfmt::skip]
377impl render::GitRepoColours for Theme {
378 fn branch_main(&self) -> Style { self.ui.git_repo.unwrap_or_default().branch_main() }
379 fn branch_other(&self) -> Style { self.ui.git_repo.unwrap_or_default().branch_other() }
380 fn no_repo(&self) -> Style { self.ui.punctuation() }
381 fn git_clean(&self) -> Style { self.ui.git_repo.unwrap_or_default().git_clean() }
382 fn git_dirty(&self) -> Style { self.ui.git_repo.unwrap_or_default().git_dirty() }
383}
384
385#[rustfmt::skip]
386#[cfg(unix)]
387impl render::GroupColours for Theme {
388 fn yours(&self) -> Style { self.ui.users.unwrap_or_default().group_yours() }
389 fn not_yours(&self) -> Style { self.ui.users.unwrap_or_default().group_other() }
390 fn root_group(&self) -> Style { self.ui.users.unwrap_or_default().group_root() }
391 fn no_group(&self) -> Style { self.ui.punctuation() }
392}
393
394#[rustfmt::skip]
395impl render::LinksColours for Theme {
396 fn normal(&self) -> Style { self.ui.links.unwrap_or_default().normal() }
397 fn multi_link_file(&self) -> Style { self.ui.links.unwrap_or_default().multi_link_file() }
398}
399
400#[rustfmt::skip]
401impl render::PermissionsColours for Theme {
402 fn dash(&self) -> Style { self.ui.punctuation() }
403 fn user_read(&self) -> Style { self.ui.perms.unwrap_or_default().user_read() }
404 fn user_write(&self) -> Style { self.ui.perms.unwrap_or_default().user_write() }
405 fn user_execute_file(&self) -> Style { self.ui.perms.unwrap_or_default().user_execute_file() }
406 fn user_execute_other(&self) -> Style { self.ui.perms.unwrap_or_default().user_execute_other() }
407 fn group_read(&self) -> Style { self.ui.perms.unwrap_or_default().group_read() }
408 fn group_write(&self) -> Style { self.ui.perms.unwrap_or_default().group_write() }
409 fn group_execute(&self) -> Style { self.ui.perms.unwrap_or_default().group_execute() }
410 fn other_read(&self) -> Style { self.ui.perms.unwrap_or_default().other_read() }
411 fn other_write(&self) -> Style { self.ui.perms.unwrap_or_default().other_write() }
412 fn other_execute(&self) -> Style { self.ui.perms.unwrap_or_default().other_execute() }
413 fn special_user_file(&self) -> Style { self.ui.perms.unwrap_or_default().special_user_file() }
414 fn special_other(&self) -> Style { self.ui.perms.unwrap_or_default().special_other() }
415 fn attribute(&self) -> Style { self.ui.perms.unwrap_or_default().attribute() }
416}
417
418impl render::SizeColours for Theme {
419 fn size(&self, prefix: Option<number_prefix::Prefix>) -> Style {
420 use number_prefix::Prefix::{Gibi, Giga, Kibi, Kilo, Mebi, Mega};
421
422 #[rustfmt::skip]
423 return match prefix {
424 Some(Kilo | Kibi) => self.ui.size.unwrap_or_default().number_kilo(),
425 Some(Mega | Mebi) => self.ui.size.unwrap_or_default().number_mega(),
426 Some(Giga | Gibi) => self.ui.size.unwrap_or_default().number_giga(),
427 Some(_) => self.ui.size.unwrap_or_default().number_huge(),
428 None => self.ui.size.unwrap_or_default().number_byte(),
429 };
430 }
431
432 fn unit(&self, prefix: Option<number_prefix::Prefix>) -> Style {
433 use number_prefix::Prefix::{Gibi, Giga, Kibi, Kilo, Mebi, Mega};
434
435 #[rustfmt::skip]
436 return match prefix {
437 Some(Kilo | Kibi) => self.ui.size.unwrap_or_default().unit_kilo(),
438 Some(Mega | Mebi) => self.ui.size.unwrap_or_default().unit_mega(),
439 Some(Giga | Gibi) => self.ui.size.unwrap_or_default().unit_giga(),
440 Some(_) => self.ui.size.unwrap_or_default().unit_huge(),
441 None => self.ui.size.unwrap_or_default().unit_byte(),
442 };
443 }
444
445 #[rustfmt::skip]
446 fn no_size(&self) -> Style { self.ui.punctuation() }
447 #[rustfmt::skip]
448 fn major(&self) -> Style { self.ui.size.unwrap_or_default().major() }
449 #[rustfmt::skip]
450 fn comma(&self) -> Style { self.ui.punctuation() }
451 #[rustfmt::skip]
452 fn minor(&self) -> Style { self.ui.size.unwrap_or_default().minor() }
453}
454
455#[rustfmt::skip]
456#[cfg(unix)]
457impl render::UserColours for Theme {
458 fn you(&self) -> Style { self.ui.users.unwrap_or_default().user_you() }
459 fn other(&self) -> Style { self.ui.users.unwrap_or_default().user_other() }
460 fn root(&self) -> Style { self.ui.users.unwrap_or_default().user_root() }
461 fn no_user(&self) -> Style { self.ui.punctuation() }
462}
463
464#[rustfmt::skip]
465impl FileNameColours for Theme {
466 fn symlink_path(&self) -> Style { self.ui.symlink_path() }
467 fn normal_arrow(&self) -> Style { self.ui.punctuation() }
468 fn broken_symlink(&self) -> Style { self.ui.broken_symlink() }
469 fn broken_filename(&self) -> Style { apply_overlay(self.ui.broken_symlink(), self.ui.broken_path_overlay()) }
470 fn control_char(&self) -> Style { self.ui.control_char() }
471 fn broken_control_char(&self) -> Style { apply_overlay(self.ui.control_char(), self.ui.broken_path_overlay()) }
472 fn executable_file(&self) -> Style { self.ui.filekinds.unwrap_or_default().executable() }
473 fn mount_point(&self) -> Style { self.ui.filekinds.unwrap_or_default().mount_point() }
474
475 fn colour_file(&self, file: &File<'_>) -> Style {
476 self.exts
477 .get_style(file, self)
478 .unwrap_or(self.ui.filekinds.unwrap_or_default().normal())
479 }
480
481 fn style_override(&self, file: &File<'_>) -> Option<FileNameStyle> {
482 if let Some(ref name_overrides) = self.ui.filenames {
483 if let Some(file_override) = name_overrides.get(&file.name) {
484 return Some(*file_override);
485 }
486 }
487
488 if let Some(ref ext_overrides) = self.ui.extensions {
489 if let Some(ext) = file.ext.clone() {
490 if let Some(file_override) = ext_overrides.get(&ext) {
491 return Some(*file_override);
492 }
493 }
494 }
495
496 None
497 }
498}
499
500#[rustfmt::skip]
501impl render::SecurityCtxColours for Theme {
502 fn none(&self) -> Style { self.ui.security_context.unwrap_or_default().none() }
503 fn selinux_colon(&self) -> Style { self.ui.security_context.unwrap_or_default().selinux().colon() }
504 fn selinux_user(&self) -> Style { self.ui.security_context.unwrap_or_default().selinux().user() }
505 fn selinux_role(&self) -> Style { self.ui.security_context.unwrap_or_default().selinux().role() }
506 fn selinux_type(&self) -> Style { self.ui.security_context.unwrap_or_default().selinux().typ() }
507 fn selinux_range(&self) -> Style { self.ui.security_context.unwrap_or_default().selinux().range() }
508}
509
510#[rustfmt::skip]
523fn apply_overlay(mut base: Style, overlay: Style) -> Style {
524 if let Some(fg) = overlay.foreground { base.foreground = Some(fg); }
525 if let Some(bg) = overlay.background { base.background = Some(bg); }
526
527 if overlay.is_bold { base.is_bold = true; }
528 if overlay.is_dimmed { base.is_dimmed = true; }
529 if overlay.is_italic { base.is_italic = true; }
530 if overlay.is_underline { base.is_underline = true; }
531 if overlay.is_blink { base.is_blink = true; }
532 if overlay.is_reverse { base.is_reverse = true; }
533 if overlay.is_hidden { base.is_hidden = true; }
534 if overlay.is_strikethrough { base.is_strikethrough = true; }
535
536 base
537}
538
539#[cfg(test)]
540#[cfg(unix)]
541mod customs_test {
542 use super::*;
543 use crate::theme::ui_styles::UiStyles;
544 use nu_ansi_term::Color::*;
545
546 impl ExtensionMappings {
547 fn to_vec_pat_style(&self) -> Vec<(glob::Pattern, Style)> {
549 let mut out = Vec::new();
550 for map in &self.mappings {
551 match map {
552 GlobPattern::Complex(p, s) => {
553 out.push((p.clone(), *s));
554 }
555 GlobPattern::Simple(h) => {
556 let mut simple_pats = h
557 .iter()
558 .map(|(k, v)| (glob::Pattern::new(&format!("*.{k}")).unwrap(), *v))
559 .collect::<Vec<(glob::Pattern, Style)>>();
560
561 simple_pats.sort_by_key(|x| x.0.clone());
562
563 out.extend(simple_pats);
564 }
565 }
566 }
567 out
568 }
569 }
570
571 macro_rules! test {
572 ($name:ident: ls $ls:expr, exa $exa:expr => colours $expected:ident -> $process_expected:expr) => {
573 #[allow(non_snake_case)]
574 #[test]
575 fn $name() {
576 let mut $expected = UiStyles::default();
577 $process_expected();
578
579 let definitions = Definitions {
580 ls: Some($ls.into()),
581 exa: Some($exa.into()),
582 };
583
584 let mut result = UiStyles::default();
585 let (_, _) = definitions.parse_color_vars(&mut result);
586 assert_eq!($expected, result);
587 }
588 };
589 ($name:ident: ls $ls:expr, exa $exa:expr => exts $mappings:expr) => {
590 #[test]
591 fn $name() {
592 let mappings: Vec<(glob::Pattern, Style)> = $mappings
593 .iter()
594 .map(|t| (glob::Pattern::new(t.0).unwrap(), t.1))
595 .collect();
596
597 let definitions = Definitions {
598 ls: Some($ls.into()),
599 exa: Some($exa.into()),
600 };
601
602 let (result, _) = definitions.parse_color_vars(&mut UiStyles::default());
603 assert_eq!(mappings, result.to_vec_pat_style());
604 }
605 };
606 ($name:ident: ls $ls:expr, exa $exa:expr => colours $expected:ident -> $process_expected:expr, exts $mappings:expr) => {
607 #[test]
608 fn $name() {
609 let mut $expected = UiStyles::default();
610 $process_expected();
611
612 let mappings: Vec<(glob::Pattern, Style)> = $mappings
613 .iter()
614 .map(|t| (glob::Pattern::new(t.0).unwrap(), t.1))
615 .collect();
616
617 let definitions = Definitions {
618 ls: Some($ls.into()),
619 exa: Some($exa.into()),
620 };
621
622 let mut result = UiStyles::default();
623 let (exts, _) = definitions.parse_color_vars(&mut result);
624
625 assert_eq!(mappings, exts.to_vec_pat_style());
626 assert_eq!($expected, result);
627 }
628 };
629 }
630
631 test!(ls_di: ls "di=31", exa "" => colours c -> { c.filekinds().directory = Some(Red.normal()); });
633 test!(ls_ex: ls "ex=32", exa "" => colours c -> { c.filekinds().executable = Some(Green.normal()); });
634 test!(ls_fi: ls "fi=33", exa "" => colours c -> { c.filekinds().normal = Some(Yellow.normal()); });
635 test!(ls_pi: ls "pi=34", exa "" => colours c -> { c.filekinds().pipe = Some(Blue.normal()); });
636 test!(ls_so: ls "so=35", exa "" => colours c -> { c.filekinds().socket = Some(Purple.normal()); });
637 test!(ls_bd: ls "bd=36", exa "" => colours c -> { c.filekinds().block_device = Some(Cyan.normal()); });
638 test!(ls_cd: ls "cd=35", exa "" => colours c -> { c.filekinds().char_device = Some(Purple.normal()); });
639 test!(ls_ln: ls "ln=34", exa "" => colours c -> { c.filekinds().symlink = Some(Blue.normal()); });
640 test!(ls_or: ls "or=33", exa "" => colours c -> { c.broken_symlink = Some(Yellow.normal()); });
641
642 test!(exa_di: ls "", exa "di=32" => colours c -> { c.filekinds().directory = Some(Green.normal()); });
644 test!(exa_ex: ls "", exa "ex=33" => colours c -> { c.filekinds().executable = Some(Yellow.normal()); });
645 test!(exa_fi: ls "", exa "fi=34" => colours c -> { c.filekinds().normal = Some(Blue.normal()); });
646 test!(exa_pi: ls "", exa "pi=35" => colours c -> { c.filekinds().pipe = Some(Purple.normal()); });
647 test!(exa_so: ls "", exa "so=36" => colours c -> { c.filekinds().socket = Some(Cyan.normal()); });
648 test!(exa_bd: ls "", exa "bd=35" => colours c -> { c.filekinds().block_device = Some(Purple.normal()); });
649 test!(exa_cd: ls "", exa "cd=34" => colours c -> { c.filekinds().char_device = Some(Blue.normal()); });
650 test!(exa_ln: ls "", exa "ln=33" => colours c -> { c.filekinds().symlink = Some(Yellow.normal()); });
651 test!(exa_or: ls "", exa "or=32" => colours c -> { c.broken_symlink = Some(Green.normal()); });
652
653 test!(ls_exa_di: ls "di=31", exa "di=32" => colours c -> { c.filekinds().directory = Some(Green.normal()); });
655 test!(ls_exa_ex: ls "ex=32", exa "ex=33" => colours c -> { c.filekinds().executable = Some(Yellow.normal()); });
656 test!(ls_exa_fi: ls "fi=33", exa "fi=34" => colours c -> { c.filekinds().normal = Some(Blue.normal()); });
657
658 test!(exa_ur: ls "", exa "ur=38;5;100" => colours c -> { c.perms().user_read = Some(Fixed(100).normal()); });
660 test!(exa_uw: ls "", exa "uw=38;5;101" => colours c -> { c.perms().user_write = Some(Fixed(101).normal()); });
661 test!(exa_ux: ls "", exa "ux=38;5;102" => colours c -> { c.perms().user_execute_file = Some(Fixed(102).normal()); });
662 test!(exa_ue: ls "", exa "ue=38;5;103" => colours c -> { c.perms().user_execute_other = Some(Fixed(103).normal()); });
663 test!(exa_gr: ls "", exa "gr=38;5;104" => colours c -> { c.perms().group_read = Some(Fixed(104).normal()); });
664 test!(exa_gw: ls "", exa "gw=38;5;105" => colours c -> { c.perms().group_write = Some(Fixed(105).normal()); });
665 test!(exa_gx: ls "", exa "gx=38;5;106" => colours c -> { c.perms().group_execute = Some(Fixed(106).normal()); });
666 test!(exa_tr: ls "", exa "tr=38;5;107" => colours c -> { c.perms().other_read = Some(Fixed(107).normal()); });
667 test!(exa_tw: ls "", exa "tw=38;5;108" => colours c -> { c.perms().other_write = Some(Fixed(108).normal()); });
668 test!(exa_tx: ls "", exa "tx=38;5;109" => colours c -> { c.perms().other_execute = Some(Fixed(109).normal()); });
669 test!(exa_su: ls "", exa "su=38;5;110" => colours c -> { c.perms().special_user_file = Some(Fixed(110).normal()); });
670 test!(exa_sf: ls "", exa "sf=38;5;111" => colours c -> { c.perms().special_other = Some(Fixed(111).normal()); });
671 test!(exa_xa: ls "", exa "xa=38;5;112" => colours c -> { c.perms().attribute = Some(Fixed(112).normal()); });
672
673 test!(exa_sn: ls "", exa "sn=38;5;113" => colours c -> {
674 c.size().number_byte = Some(Fixed(113).normal());
675 c.size().number_kilo = Some(Fixed(113).normal());
676 c.size().number_mega = Some(Fixed(113).normal());
677 c.size().number_giga = Some(Fixed(113).normal());
678 c.size().number_huge = Some(Fixed(113).normal());
679 });
680 test!(exa_sb: ls "", exa "sb=38;5;114" => colours c -> {
681 c.size().unit_byte = Some(Fixed(114).normal());
682 c.size().unit_kilo = Some(Fixed(114).normal());
683 c.size().unit_mega = Some(Fixed(114).normal());
684 c.size().unit_giga = Some(Fixed(114).normal());
685 c.size().unit_huge = Some(Fixed(114).normal());
686 });
687
688 test!(exa_nb: ls "", exa "nb=38;5;115" => colours c -> { c.size().number_byte = Some(Fixed(115).normal()); });
689 test!(exa_nk: ls "", exa "nk=38;5;116" => colours c -> { c.size().number_kilo = Some(Fixed(116).normal()); });
690 test!(exa_nm: ls "", exa "nm=38;5;117" => colours c -> { c.size().number_mega = Some(Fixed(117).normal()); });
691 test!(exa_ng: ls "", exa "ng=38;5;118" => colours c -> { c.size().number_giga = Some(Fixed(118).normal()); });
692 test!(exa_nt: ls "", exa "nt=38;5;119" => colours c -> { c.size().number_huge = Some(Fixed(119).normal()); });
693
694 test!(exa_ub: ls "", exa "ub=38;5;115" => colours c -> { c.size().unit_byte = Some(Fixed(115).normal()); });
695 test!(exa_uk: ls "", exa "uk=38;5;116" => colours c -> { c.size().unit_kilo = Some(Fixed(116).normal()); });
696 test!(exa_um: ls "", exa "um=38;5;117" => colours c -> { c.size().unit_mega = Some(Fixed(117).normal()); });
697 test!(exa_ug: ls "", exa "ug=38;5;118" => colours c -> { c.size().unit_giga = Some(Fixed(118).normal()); });
698 test!(exa_ut: ls "", exa "ut=38;5;119" => colours c -> { c.size().unit_huge = Some(Fixed(119).normal()); });
699
700 test!(exa_df: ls "", exa "df=38;5;115" => colours c -> { c.size().major = Some(Fixed(115).normal()); });
701 test!(exa_ds: ls "", exa "ds=38;5;116" => colours c -> { c.size().minor = Some(Fixed(116).normal()); });
702
703 test!(exa_uu: ls "", exa "uu=38;5;117" => colours c -> { c.users().user_you = Some(Fixed(117).normal()); });
704 test!(exa_un: ls "", exa "un=38;5;118" => colours c -> { c.users().user_other = Some(Fixed(118).normal()); });
705 test!(exa_gu: ls "", exa "gu=38;5;119" => colours c -> { c.users().group_yours = Some(Fixed(119).normal()); });
706 test!(exa_gn: ls "", exa "gn=38;5;120" => colours c -> { c.users().group_other = Some(Fixed(120).normal()); });
707
708 test!(exa_lc: ls "", exa "lc=38;5;121" => colours c -> { c.links().normal = Some(Fixed(121).normal()); });
709 test!(exa_lm: ls "", exa "lm=38;5;122" => colours c -> { c.links().multi_link_file = Some(Fixed(122).normal()); });
710
711 test!(exa_ga: ls "", exa "ga=38;5;123" => colours c -> { c.git().new = Some(Fixed(123).normal()); });
712 test!(exa_gm: ls "", exa "gm=38;5;124" => colours c -> { c.git().modified = Some(Fixed(124).normal()); });
713 test!(exa_gd: ls "", exa "gd=38;5;125" => colours c -> { c.git().deleted = Some(Fixed(125).normal()); });
714 test!(exa_gv: ls "", exa "gv=38;5;126" => colours c -> { c.git().renamed = Some(Fixed(126).normal()); });
715 test!(exa_gt: ls "", exa "gt=38;5;127" => colours c -> { c.git().typechange = Some(Fixed(127).normal()); });
716 test!(exa_gi: ls "", exa "gi=38;5;128" => colours c -> { c.git().ignored = Some(Fixed(128).normal()); });
717 test!(exa_gc: ls "", exa "gc=38;5;129" => colours c -> { c.git().conflicted = Some(Fixed(129).normal()); });
718
719 test!(exa_xx: ls "", exa "xx=38;5;128" => colours c -> { c.punctuation = Some(Fixed(128).normal()); });
720 test!(exa_da: ls "", exa "da=38;5;129" => colours c -> { c.date = Some(Fixed(129).normal()); });
721 test!(exa_in: ls "", exa "in=38;5;130" => colours c -> { c.inode = Some(Fixed(130).normal()); });
722 test!(exa_bl: ls "", exa "bl=38;5;131" => colours c -> { c.blocks = Some(Fixed(131).normal()); });
723 test!(exa_hd: ls "", exa "hd=38;5;132" => colours c -> { c.header = Some(Fixed(132).normal()); });
724 test!(exa_lp: ls "", exa "lp=38;5;133" => colours c -> { c.symlink_path = Some(Fixed(133).normal()); });
725 test!(exa_cc: ls "", exa "cc=38;5;134" => colours c -> { c.control_char = Some(Fixed(134).normal()); });
726 test!(exa_oc: ls "", exa "oc=38;5;135" => colours c -> { c.octal = Some(Fixed(135).normal()); });
727 test!(exa_ff: ls "", exa "ff=38;5;136" => colours c -> { c.flags = Some(Fixed(136).normal()); });
728 test!(exa_bo: ls "", exa "bO=4" => colours c -> { c.broken_path_overlay = Some(Style::default().underline()); });
729
730 test!(exa_mp: ls "", exa "mp=1;34;4" => colours c -> { c.filekinds().mount_point = Some(Blue.bold().underline()); });
731 test!(exa_sp: ls "", exa "sp=1;35;4" => colours c -> { c.filekinds().special = Some(Purple.bold().underline()); });
732
733 test!(exa_im: ls "", exa "im=38;5;128" => colours c -> { c.file_type().image = Some(Fixed(128).normal()); });
734 test!(exa_vi: ls "", exa "vi=38;5;129" => colours c -> { c.file_type().video = Some(Fixed(129).normal()); });
735 test!(exa_mu: ls "", exa "mu=38;5;130" => colours c -> { c.file_type().music = Some(Fixed(130).normal()); });
736 test!(exa_lo: ls "", exa "lo=38;5;131" => colours c -> { c.file_type().lossless = Some(Fixed(131).normal()); });
737 test!(exa_cr: ls "", exa "cr=38;5;132" => colours c -> { c.file_type().crypto = Some(Fixed(132).normal()); });
738 test!(exa_do: ls "", exa "do=38;5;133" => colours c -> { c.file_type().document = Some(Fixed(133).normal()); });
739 test!(exa_co: ls "", exa "co=38;5;134" => colours c -> { c.file_type().compressed = Some(Fixed(134).normal()); });
740 test!(exa_tm: ls "", exa "tm=38;5;135" => colours c -> { c.file_type().temp = Some(Fixed(135).normal()); });
741 test!(exa_cm: ls "", exa "cm=38;5;136" => colours c -> { c.file_type().compiled = Some(Fixed(136).normal()); });
742 test!(exa_ie: ls "", exa "bu=38;5;137" => colours c -> { c.file_type().build = Some(Fixed(137).normal()); });
743 test!(exa_bu: ls "", exa "bu=38;5;137" => colours c -> { c.file_type().build = Some(Fixed(137).normal()); });
744 test!(exa_sc: ls "", exa "sc=38;5;138" => colours c -> { c.file_type().source = Some(Fixed(138).normal()); });
745
746 test!(exa_Sn: ls "", exa "Sn=38;5;128" => colours c -> { c.security_context().none = Some(Fixed(128).normal()); });
747 test!(exa_Su: ls "", exa "Su=38;5;129" => colours c -> { c.security_context().selinux().user = Some(Fixed(129).normal()); });
748 test!(exa_Sr: ls "", exa "Sr=38;5;130" => colours c -> { c.security_context().selinux().role = Some(Fixed(130).normal()); });
749 test!(exa_St: ls "", exa "St=38;5;131" => colours c -> { c.security_context().selinux().typ = Some(Fixed(131).normal()); });
750 test!(exa_Sl: ls "", exa "Sl=38;5;132" => colours c -> { c.security_context().selinux().range = Some(Fixed(132).normal()); });
751
752 test!(ls_uu: ls "uu=38;5;117", exa "" => exts [ ("uu", Fixed(117).normal()) ]);
754 test!(ls_un: ls "un=38;5;118", exa "" => exts [ ("un", Fixed(118).normal()) ]);
755 test!(ls_gu: ls "gu=38;5;119", exa "" => exts [ ("gu", Fixed(119).normal()) ]);
756 test!(ls_gn: ls "gn=38;5;120", exa "" => exts [ ("gn", Fixed(120).normal()) ]);
757
758 test!(ls_txt: ls "*.txt=31", exa "" => exts [ ("*.txt", Red.normal()) ]);
760 test!(ls_mp3: ls "*.mp3=38;5;135", exa "" => exts [ ("*.mp3", Fixed(135).normal()) ]);
761 test!(ls_mak: ls "Makefile=1;32;4", exa "" => exts [ ("Makefile", Green.bold().underline()) ]);
762 test!(exa_txt: ls "", exa "*.zip=31" => exts [ ("*.zip", Red.normal()) ]);
763 test!(exa_mp3: ls "", exa "lev.*=38;5;153" => exts [ ("lev.*", Fixed(153).normal()) ]);
764 test!(exa_mak: ls "", exa "Cargo.toml=4;32;1" => exts [ ("Cargo.toml", Green.bold().underline()) ]);
765
766 test!(ls_multi: ls "*.txt=31:*.rtf=32", exa "" => exts [ ("*.rtf", Green.normal()), ("*.txt", Red.normal()) ]);
771 test!(exa_multi: ls "", exa "*.tmp=37:*.log=37" => exts [ ("*.log", White.normal()), ("*.tmp", White.normal()) ]);
772 test!(ls_exa_multi: ls "*.txt=31", exa "*.rtf=32" => exts [ ("*.rtf", Green.normal()), ("*.txt", Red.normal())]);
773
774 test!(ls_five: ls "1*1=31:2*2=32:3*3=1;33:4*4=34;1:5*5=35;4", exa "" => exts [
775 ("1*1", Red.normal()), ("2*2", Green.normal()), ("3*3", Yellow.bold()), ("4*4", Blue.bold()), ("5*5", Purple.underline())
776 ]);
777
778 test!(ls_overwrite: ls "pi=31:pi=32:pi=33", exa "" => colours c -> { c.filekinds().pipe = Some(Yellow.normal()); });
780 test!(exa_overwrite: ls "", exa "da=36:da=35:da=34" => colours c -> { c.date = Some(Blue.normal()); });
781
782 test!(ls_fi_ls_txt: ls "fi=33:*.txt=31", exa "" => colours c -> { c.filekinds().normal = Some(Yellow.normal()); }, exts [ ("*.txt", Red.normal()) ]);
784 test!(ls_fi_exa_txt: ls "fi=33", exa "*.txt=31" => colours c -> { c.filekinds().normal = Some(Yellow.normal()); }, exts [ ("*.txt", Red.normal()) ]);
785 test!(ls_txt_exa_fi: ls "*.txt=31", exa "fi=33" => colours c -> { c.filekinds().normal = Some(Yellow.normal()); }, exts [ ("*.txt", Red.normal()) ]);
786 test!(eza_fi_exa_txt: ls "", exa "fi=33:*.txt=31" => colours c -> { c.filekinds().normal = Some(Yellow.normal()); }, exts [ ("*.txt", Red.normal()) ]);
787}