mod.rs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. use ansiterm::Style;
  2. use crate::fs::File;
  3. use crate::info::filetype::FileType;
  4. use crate::output::file_name::Colours as FileNameColours;
  5. use crate::output::render;
  6. mod ui_styles;
  7. pub use self::ui_styles::UiStyles;
  8. mod lsc;
  9. pub use self::lsc::LSColors;
  10. mod default_theme;
  11. #[derive(PartialEq, Eq, Debug)]
  12. pub struct Options {
  13. pub use_colours: UseColours,
  14. pub colour_scale: ColourScale,
  15. pub definitions: Definitions,
  16. }
  17. /// Under what circumstances we should display coloured, rather than plain,
  18. /// output to the terminal.
  19. ///
  20. /// By default, we want to display the colours when stdout can display them.
  21. /// Turning them on when output is going to, say, a pipe, would make programs
  22. /// such as `grep` or `more` not work properly. So the `Automatic` mode does
  23. /// this check and only displays colours when they can be truly appreciated.
  24. #[derive(PartialEq, Eq, Debug, Copy, Clone)]
  25. pub enum UseColours {
  26. /// Display them even when output isn’t going to a terminal.
  27. Always,
  28. /// Display them when output is going to a terminal, but not otherwise.
  29. Automatic,
  30. /// Never display them, even when output is going to a terminal.
  31. Never,
  32. }
  33. #[derive(PartialEq, Eq, Debug, Copy, Clone)]
  34. pub enum ColourScale {
  35. Fixed,
  36. Gradient,
  37. }
  38. #[derive(PartialEq, Eq, Debug, Default)]
  39. pub struct Definitions {
  40. pub ls: Option<String>,
  41. pub exa: Option<String>,
  42. }
  43. pub struct Theme {
  44. pub ui: UiStyles,
  45. pub exts: Box<dyn FileStyle>,
  46. }
  47. impl Options {
  48. #[allow(trivial_casts)] // the `as Box<_>` stuff below warns about this for some reason
  49. pub fn to_theme(&self, isatty: bool) -> Theme {
  50. if self.use_colours == UseColours::Never
  51. || (self.use_colours == UseColours::Automatic && !isatty)
  52. {
  53. let ui = UiStyles::plain();
  54. let exts = Box::new(NoFileStyle);
  55. return Theme { ui, exts };
  56. }
  57. // Parse the environment variables into colours and extension mappings
  58. let mut ui = UiStyles::default_theme(self.colour_scale);
  59. let (exts, use_default_filetypes) = self.definitions.parse_color_vars(&mut ui);
  60. // Use between 0 and 2 file name highlighters
  61. #[rustfmt::skip]
  62. let exts = match (exts.is_non_empty(), use_default_filetypes) {
  63. (false, false) => Box::new(NoFileStyle) as Box<_>,
  64. (false, true) => Box::new(FileTypes) as Box<_>,
  65. ( true, false) => Box::new(exts) as Box<_>,
  66. ( true, true) => Box::new((exts, FileTypes)) as Box<_>,
  67. };
  68. Theme { ui, exts }
  69. }
  70. }
  71. impl Definitions {
  72. /// Parse the environment variables into `LS_COLORS` pairs, putting file glob
  73. /// colours into the `ExtensionMappings` that gets returned, and using the
  74. /// two-character UI codes to modify the mutable `Colours`.
  75. ///
  76. /// Also returns if the `EZA_COLORS` variable should reset the existing file
  77. /// type mappings or not. The `reset` code needs to be the first one.
  78. fn parse_color_vars(&self, colours: &mut UiStyles) -> (ExtensionMappings, bool) {
  79. use log::*;
  80. let mut exts = ExtensionMappings::default();
  81. if let Some(lsc) = &self.ls {
  82. LSColors(lsc).each_pair(|pair| {
  83. if !colours.set_ls(&pair) {
  84. match glob::Pattern::new(pair.key) {
  85. Ok(pat) => {
  86. exts.add(pat, pair.to_style());
  87. }
  88. Err(e) => {
  89. warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
  90. }
  91. }
  92. }
  93. });
  94. }
  95. let mut use_default_filetypes = true;
  96. if let Some(exa) = &self.exa {
  97. // Is this hacky? Yes.
  98. if exa == "reset" || exa.starts_with("reset:") {
  99. use_default_filetypes = false;
  100. }
  101. LSColors(exa).each_pair(|pair| {
  102. if !colours.set_ls(&pair) && !colours.set_exa(&pair) {
  103. match glob::Pattern::new(pair.key) {
  104. Ok(pat) => {
  105. exts.add(pat, pair.to_style());
  106. }
  107. Err(e) => {
  108. warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
  109. }
  110. }
  111. };
  112. });
  113. }
  114. (exts, use_default_filetypes)
  115. }
  116. }
  117. /// Determine the style to paint the text for the filename part of the output.
  118. pub trait FileStyle: Sync {
  119. /// Return the style to paint the filename text for `file` from the given
  120. /// `theme`.
  121. fn get_style(&self, file: &File<'_>, theme: &Theme) -> Option<Style>;
  122. }
  123. #[derive(PartialEq, Debug)]
  124. struct NoFileStyle;
  125. impl FileStyle for NoFileStyle {
  126. fn get_style(&self, _file: &File<'_>, _theme: &Theme) -> Option<Style> {
  127. None
  128. }
  129. }
  130. // When getting the colour of a file from a *pair* of colourisers, try the
  131. // first one then try the second one. This lets the user provide their own
  132. // file type associations, while falling back to the default set if not set
  133. // explicitly.
  134. impl<A, B> FileStyle for (A, B)
  135. where
  136. A: FileStyle,
  137. B: FileStyle,
  138. {
  139. fn get_style(&self, file: &File<'_>, theme: &Theme) -> Option<Style> {
  140. self.0
  141. .get_style(file, theme)
  142. .or_else(|| self.1.get_style(file, theme))
  143. }
  144. }
  145. #[derive(PartialEq, Debug, Default)]
  146. struct ExtensionMappings {
  147. mappings: Vec<(glob::Pattern, Style)>,
  148. }
  149. impl ExtensionMappings {
  150. fn is_non_empty(&self) -> bool {
  151. !self.mappings.is_empty()
  152. }
  153. fn add(&mut self, pattern: glob::Pattern, style: Style) {
  154. self.mappings.push((pattern, style));
  155. }
  156. }
  157. // Loop through backwards so that colours specified later in the list override
  158. // colours specified earlier, like we do with options and strict mode
  159. impl FileStyle for ExtensionMappings {
  160. fn get_style(&self, file: &File<'_>, _theme: &Theme) -> Option<Style> {
  161. self.mappings
  162. .iter()
  163. .rev()
  164. .find(|t| t.0.matches(&file.name))
  165. .map(|t| t.1)
  166. }
  167. }
  168. #[derive(Debug)]
  169. struct FileTypes;
  170. impl FileStyle for FileTypes {
  171. fn get_style(&self, file: &File<'_>, theme: &Theme) -> Option<Style> {
  172. #[rustfmt::skip]
  173. return match FileType::get_file_type(file) {
  174. Some(FileType::Image) => Some(theme.ui.file_type.image),
  175. Some(FileType::Video) => Some(theme.ui.file_type.video),
  176. Some(FileType::Music) => Some(theme.ui.file_type.music),
  177. Some(FileType::Lossless) => Some(theme.ui.file_type.lossless),
  178. Some(FileType::Crypto) => Some(theme.ui.file_type.crypto),
  179. Some(FileType::Document) => Some(theme.ui.file_type.document),
  180. Some(FileType::Compressed) => Some(theme.ui.file_type.compressed),
  181. Some(FileType::Temp) => Some(theme.ui.file_type.temp),
  182. Some(FileType::Compiled) => Some(theme.ui.file_type.compiled),
  183. Some(FileType::Build) => Some(theme.ui.file_type.build),
  184. None => None
  185. };
  186. }
  187. }
  188. #[cfg(unix)]
  189. impl render::BlocksColours for Theme {
  190. fn blocksize(&self, prefix: Option<number_prefix::Prefix>) -> Style {
  191. use number_prefix::Prefix::*;
  192. #[rustfmt::skip]
  193. return match prefix {
  194. Some(Kilo | Kibi) => self.ui.size.number_kilo,
  195. Some(Mega | Mebi) => self.ui.size.number_mega,
  196. Some(Giga | Gibi) => self.ui.size.number_giga,
  197. Some(_) => self.ui.size.number_huge,
  198. None => self.ui.size.number_byte,
  199. };
  200. }
  201. fn unit(&self, prefix: Option<number_prefix::Prefix>) -> Style {
  202. use number_prefix::Prefix::*;
  203. #[rustfmt::skip]
  204. return match prefix {
  205. Some(Kilo | Kibi) => self.ui.size.unit_kilo,
  206. Some(Mega | Mebi) => self.ui.size.unit_mega,
  207. Some(Giga | Gibi) => self.ui.size.unit_giga,
  208. Some(_) => self.ui.size.unit_huge,
  209. None => self.ui.size.unit_byte,
  210. };
  211. }
  212. fn no_blocksize(&self) -> Style {
  213. self.ui.punctuation
  214. }
  215. }
  216. #[rustfmt::skip]
  217. impl render::FiletypeColours for Theme {
  218. fn normal(&self) -> Style { self.ui.filekinds.normal }
  219. fn directory(&self) -> Style { self.ui.filekinds.directory }
  220. fn pipe(&self) -> Style { self.ui.filekinds.pipe }
  221. fn symlink(&self) -> Style { self.ui.filekinds.symlink }
  222. fn block_device(&self) -> Style { self.ui.filekinds.block_device }
  223. fn char_device(&self) -> Style { self.ui.filekinds.char_device }
  224. fn socket(&self) -> Style { self.ui.filekinds.socket }
  225. fn special(&self) -> Style { self.ui.filekinds.special }
  226. }
  227. #[rustfmt::skip]
  228. impl render::GitColours for Theme {
  229. fn not_modified(&self) -> Style { self.ui.punctuation }
  230. #[allow(clippy::new_ret_no_self)]
  231. fn new(&self) -> Style { self.ui.git.new }
  232. fn modified(&self) -> Style { self.ui.git.modified }
  233. fn deleted(&self) -> Style { self.ui.git.deleted }
  234. fn renamed(&self) -> Style { self.ui.git.renamed }
  235. fn type_change(&self) -> Style { self.ui.git.typechange }
  236. fn ignored(&self) -> Style { self.ui.git.ignored }
  237. fn conflicted(&self) -> Style { self.ui.git.conflicted }
  238. }
  239. #[rustfmt::skip]
  240. impl render::GitRepoColours for Theme {
  241. fn branch_main(&self) -> Style { self.ui.git_repo.branch_main }
  242. fn branch_other(&self) -> Style { self.ui.git_repo.branch_other }
  243. fn no_repo(&self) -> Style { self.ui.punctuation }
  244. fn git_clean(&self) -> Style { self.ui.git_repo.git_clean }
  245. fn git_dirty(&self) -> Style { self.ui.git_repo.git_dirty }
  246. }
  247. #[rustfmt::skip]
  248. #[cfg(unix)]
  249. impl render::GroupColours for Theme {
  250. fn yours(&self) -> Style { self.ui.users.group_yours }
  251. fn not_yours(&self) -> Style { self.ui.users.group_not_yours }
  252. fn no_group(&self) -> Style { self.ui.punctuation }
  253. }
  254. #[rustfmt::skip]
  255. impl render::LinksColours for Theme {
  256. fn normal(&self) -> Style { self.ui.links.normal }
  257. fn multi_link_file(&self) -> Style { self.ui.links.multi_link_file }
  258. }
  259. #[rustfmt::skip]
  260. impl render::PermissionsColours for Theme {
  261. fn dash(&self) -> Style { self.ui.punctuation }
  262. fn user_read(&self) -> Style { self.ui.perms.user_read }
  263. fn user_write(&self) -> Style { self.ui.perms.user_write }
  264. fn user_execute_file(&self) -> Style { self.ui.perms.user_execute_file }
  265. fn user_execute_other(&self) -> Style { self.ui.perms.user_execute_other }
  266. fn group_read(&self) -> Style { self.ui.perms.group_read }
  267. fn group_write(&self) -> Style { self.ui.perms.group_write }
  268. fn group_execute(&self) -> Style { self.ui.perms.group_execute }
  269. fn other_read(&self) -> Style { self.ui.perms.other_read }
  270. fn other_write(&self) -> Style { self.ui.perms.other_write }
  271. fn other_execute(&self) -> Style { self.ui.perms.other_execute }
  272. fn special_user_file(&self) -> Style { self.ui.perms.special_user_file }
  273. fn special_other(&self) -> Style { self.ui.perms.special_other }
  274. fn attribute(&self) -> Style { self.ui.perms.attribute }
  275. }
  276. impl render::SizeColours for Theme {
  277. fn size(&self, prefix: Option<number_prefix::Prefix>) -> Style {
  278. use number_prefix::Prefix::*;
  279. #[rustfmt::skip]
  280. return match prefix {
  281. Some(Kilo | Kibi) => self.ui.size.number_kilo,
  282. Some(Mega | Mebi) => self.ui.size.number_mega,
  283. Some(Giga | Gibi) => self.ui.size.number_giga,
  284. Some(_) => self.ui.size.number_huge,
  285. None => self.ui.size.number_byte,
  286. };
  287. }
  288. fn unit(&self, prefix: Option<number_prefix::Prefix>) -> Style {
  289. use number_prefix::Prefix::*;
  290. #[rustfmt::skip]
  291. return match prefix {
  292. Some(Kilo | Kibi) => self.ui.size.unit_kilo,
  293. Some(Mega | Mebi) => self.ui.size.unit_mega,
  294. Some(Giga | Gibi) => self.ui.size.unit_giga,
  295. Some(_) => self.ui.size.unit_huge,
  296. None => self.ui.size.unit_byte,
  297. };
  298. }
  299. #[rustfmt::skip]
  300. fn no_size(&self) -> Style { self.ui.punctuation }
  301. #[rustfmt::skip]
  302. fn major(&self) -> Style { self.ui.size.major }
  303. #[rustfmt::skip]
  304. fn comma(&self) -> Style { self.ui.punctuation }
  305. #[rustfmt::skip]
  306. fn minor(&self) -> Style { self.ui.size.minor }
  307. }
  308. #[rustfmt::skip]
  309. #[cfg(unix)]
  310. impl render::UserColours for Theme {
  311. fn you(&self) -> Style { self.ui.users.user_you }
  312. fn someone_else(&self) -> Style { self.ui.users.user_someone_else }
  313. fn no_user(&self) -> Style { self.ui.punctuation }
  314. }
  315. #[rustfmt::skip]
  316. impl FileNameColours for Theme {
  317. fn symlink_path(&self) -> Style { self.ui.symlink_path }
  318. fn normal_arrow(&self) -> Style { self.ui.punctuation }
  319. fn broken_symlink(&self) -> Style { self.ui.broken_symlink }
  320. fn broken_filename(&self) -> Style { apply_overlay(self.ui.broken_symlink, self.ui.broken_path_overlay) }
  321. fn control_char(&self) -> Style { self.ui.control_char }
  322. fn broken_control_char(&self) -> Style { apply_overlay(self.ui.control_char, self.ui.broken_path_overlay) }
  323. fn executable_file(&self) -> Style { self.ui.filekinds.executable }
  324. fn mount_point(&self) -> Style { self.ui.filekinds.mount_point }
  325. fn colour_file(&self, file: &File<'_>) -> Style {
  326. self.exts
  327. .get_style(file, self)
  328. .unwrap_or(self.ui.filekinds.normal)
  329. }
  330. }
  331. #[rustfmt::skip]
  332. impl render::SecurityCtxColours for Theme {
  333. fn none(&self) -> Style { self.ui.security_context.none }
  334. fn selinux_colon(&self) -> Style { self.ui.security_context.selinux.colon }
  335. fn selinux_user(&self) -> Style { self.ui.security_context.selinux.user }
  336. fn selinux_role(&self) -> Style { self.ui.security_context.selinux.role }
  337. fn selinux_type(&self) -> Style { self.ui.security_context.selinux.typ }
  338. fn selinux_range(&self) -> Style { self.ui.security_context.selinux.range }
  339. }
  340. /// Some of the styles are **overlays**: although they have the same attribute
  341. /// set as regular styles (foreground and background colours, bold, underline,
  342. /// etc), they’re intended to be used to *amend* existing styles.
  343. ///
  344. /// For example, the target path of a broken symlink is displayed in a red,
  345. /// underlined style by default. Paths can contain control characters, so
  346. /// these control characters need to be underlined too, otherwise it looks
  347. /// weird. So instead of having four separate configurable styles for “link
  348. /// path”, “broken link path”, “control character” and “broken control
  349. /// character”, there are styles for “link path”, “control character”, and
  350. /// “broken link overlay”, the latter of which is just set to override the
  351. /// underline attribute on the other two.
  352. #[rustfmt::skip]
  353. fn apply_overlay(mut base: Style, overlay: Style) -> Style {
  354. if let Some(fg) = overlay.foreground { base.foreground = Some(fg); }
  355. if let Some(bg) = overlay.background { base.background = Some(bg); }
  356. if overlay.is_bold { base.is_bold = true; }
  357. if overlay.is_dimmed { base.is_dimmed = true; }
  358. if overlay.is_italic { base.is_italic = true; }
  359. if overlay.is_underline { base.is_underline = true; }
  360. if overlay.is_blink { base.is_blink = true; }
  361. if overlay.is_reverse { base.is_reverse = true; }
  362. if overlay.is_hidden { base.is_hidden = true; }
  363. if overlay.is_strikethrough { base.is_strikethrough = true; }
  364. base
  365. }
  366. // TODO: move this function to the ansiterm crate
  367. #[cfg(test)]
  368. #[cfg(unix)]
  369. mod customs_test {
  370. use super::*;
  371. use crate::theme::ui_styles::UiStyles;
  372. use ansiterm::Colour::*;
  373. macro_rules! test {
  374. ($name:ident: ls $ls:expr, exa $exa:expr => colours $expected:ident -> $process_expected:expr) => {
  375. #[allow(non_snake_case)]
  376. #[test]
  377. fn $name() {
  378. let mut $expected = UiStyles::default();
  379. $process_expected();
  380. let definitions = Definitions {
  381. ls: Some($ls.into()),
  382. exa: Some($exa.into()),
  383. };
  384. let mut result = UiStyles::default();
  385. let (_, _) = definitions.parse_color_vars(&mut result);
  386. assert_eq!($expected, result);
  387. }
  388. };
  389. ($name:ident: ls $ls:expr, exa $exa:expr => exts $mappings:expr) => {
  390. #[test]
  391. fn $name() {
  392. let mappings: Vec<(glob::Pattern, Style)> = $mappings
  393. .iter()
  394. .map(|t| (glob::Pattern::new(t.0).unwrap(), t.1))
  395. .collect();
  396. let definitions = Definitions {
  397. ls: Some($ls.into()),
  398. exa: Some($exa.into()),
  399. };
  400. let (result, _) = definitions.parse_color_vars(&mut UiStyles::default());
  401. assert_eq!(ExtensionMappings { mappings }, result);
  402. }
  403. };
  404. ($name:ident: ls $ls:expr, exa $exa:expr => colours $expected:ident -> $process_expected:expr, exts $mappings:expr) => {
  405. #[test]
  406. fn $name() {
  407. let mut $expected = UiStyles::default();
  408. $process_expected();
  409. let mappings: Vec<(glob::Pattern, Style)> = $mappings
  410. .iter()
  411. .map(|t| (glob::Pattern::new(t.0).unwrap(), t.1))
  412. .collect();
  413. let definitions = Definitions {
  414. ls: Some($ls.into()),
  415. exa: Some($exa.into()),
  416. };
  417. let mut result = UiStyles::default();
  418. let (exts, _) = definitions.parse_color_vars(&mut result);
  419. assert_eq!(ExtensionMappings { mappings }, exts);
  420. assert_eq!($expected, result);
  421. }
  422. };
  423. }
  424. // LS_COLORS can affect all of these colours:
  425. test!(ls_di: ls "di=31", exa "" => colours c -> { c.filekinds.directory = Red.normal(); });
  426. test!(ls_ex: ls "ex=32", exa "" => colours c -> { c.filekinds.executable = Green.normal(); });
  427. test!(ls_fi: ls "fi=33", exa "" => colours c -> { c.filekinds.normal = Yellow.normal(); });
  428. test!(ls_pi: ls "pi=34", exa "" => colours c -> { c.filekinds.pipe = Blue.normal(); });
  429. test!(ls_so: ls "so=35", exa "" => colours c -> { c.filekinds.socket = Purple.normal(); });
  430. test!(ls_bd: ls "bd=36", exa "" => colours c -> { c.filekinds.block_device = Cyan.normal(); });
  431. test!(ls_cd: ls "cd=35", exa "" => colours c -> { c.filekinds.char_device = Purple.normal(); });
  432. test!(ls_ln: ls "ln=34", exa "" => colours c -> { c.filekinds.symlink = Blue.normal(); });
  433. test!(ls_or: ls "or=33", exa "" => colours c -> { c.broken_symlink = Yellow.normal(); });
  434. // EZA_COLORS can affect all those colours too:
  435. test!(exa_di: ls "", exa "di=32" => colours c -> { c.filekinds.directory = Green.normal(); });
  436. test!(exa_ex: ls "", exa "ex=33" => colours c -> { c.filekinds.executable = Yellow.normal(); });
  437. test!(exa_fi: ls "", exa "fi=34" => colours c -> { c.filekinds.normal = Blue.normal(); });
  438. test!(exa_pi: ls "", exa "pi=35" => colours c -> { c.filekinds.pipe = Purple.normal(); });
  439. test!(exa_so: ls "", exa "so=36" => colours c -> { c.filekinds.socket = Cyan.normal(); });
  440. test!(exa_bd: ls "", exa "bd=35" => colours c -> { c.filekinds.block_device = Purple.normal(); });
  441. test!(exa_cd: ls "", exa "cd=34" => colours c -> { c.filekinds.char_device = Blue.normal(); });
  442. test!(exa_ln: ls "", exa "ln=33" => colours c -> { c.filekinds.symlink = Yellow.normal(); });
  443. test!(exa_or: ls "", exa "or=32" => colours c -> { c.broken_symlink = Green.normal(); });
  444. // EZA_COLORS will even override options from LS_COLORS:
  445. test!(ls_exa_di: ls "di=31", exa "di=32" => colours c -> { c.filekinds.directory = Green.normal(); });
  446. test!(ls_exa_ex: ls "ex=32", exa "ex=33" => colours c -> { c.filekinds.executable = Yellow.normal(); });
  447. test!(ls_exa_fi: ls "fi=33", exa "fi=34" => colours c -> { c.filekinds.normal = Blue.normal(); });
  448. // But more importantly, EZA_COLORS has its own, special list of colours:
  449. test!(exa_ur: ls "", exa "ur=38;5;100" => colours c -> { c.perms.user_read = Fixed(100).normal(); });
  450. test!(exa_uw: ls "", exa "uw=38;5;101" => colours c -> { c.perms.user_write = Fixed(101).normal(); });
  451. test!(exa_ux: ls "", exa "ux=38;5;102" => colours c -> { c.perms.user_execute_file = Fixed(102).normal(); });
  452. test!(exa_ue: ls "", exa "ue=38;5;103" => colours c -> { c.perms.user_execute_other = Fixed(103).normal(); });
  453. test!(exa_gr: ls "", exa "gr=38;5;104" => colours c -> { c.perms.group_read = Fixed(104).normal(); });
  454. test!(exa_gw: ls "", exa "gw=38;5;105" => colours c -> { c.perms.group_write = Fixed(105).normal(); });
  455. test!(exa_gx: ls "", exa "gx=38;5;106" => colours c -> { c.perms.group_execute = Fixed(106).normal(); });
  456. test!(exa_tr: ls "", exa "tr=38;5;107" => colours c -> { c.perms.other_read = Fixed(107).normal(); });
  457. test!(exa_tw: ls "", exa "tw=38;5;108" => colours c -> { c.perms.other_write = Fixed(108).normal(); });
  458. test!(exa_tx: ls "", exa "tx=38;5;109" => colours c -> { c.perms.other_execute = Fixed(109).normal(); });
  459. test!(exa_su: ls "", exa "su=38;5;110" => colours c -> { c.perms.special_user_file = Fixed(110).normal(); });
  460. test!(exa_sf: ls "", exa "sf=38;5;111" => colours c -> { c.perms.special_other = Fixed(111).normal(); });
  461. test!(exa_xa: ls "", exa "xa=38;5;112" => colours c -> { c.perms.attribute = Fixed(112).normal(); });
  462. test!(exa_sn: ls "", exa "sn=38;5;113" => colours c -> {
  463. c.size.number_byte = Fixed(113).normal();
  464. c.size.number_kilo = Fixed(113).normal();
  465. c.size.number_mega = Fixed(113).normal();
  466. c.size.number_giga = Fixed(113).normal();
  467. c.size.number_huge = Fixed(113).normal();
  468. });
  469. test!(exa_sb: ls "", exa "sb=38;5;114" => colours c -> {
  470. c.size.unit_byte = Fixed(114).normal();
  471. c.size.unit_kilo = Fixed(114).normal();
  472. c.size.unit_mega = Fixed(114).normal();
  473. c.size.unit_giga = Fixed(114).normal();
  474. c.size.unit_huge = Fixed(114).normal();
  475. });
  476. test!(exa_nb: ls "", exa "nb=38;5;115" => colours c -> { c.size.number_byte = Fixed(115).normal(); });
  477. test!(exa_nk: ls "", exa "nk=38;5;116" => colours c -> { c.size.number_kilo = Fixed(116).normal(); });
  478. test!(exa_nm: ls "", exa "nm=38;5;117" => colours c -> { c.size.number_mega = Fixed(117).normal(); });
  479. test!(exa_ng: ls "", exa "ng=38;5;118" => colours c -> { c.size.number_giga = Fixed(118).normal(); });
  480. test!(exa_nt: ls "", exa "nt=38;5;119" => colours c -> { c.size.number_huge = Fixed(119).normal(); });
  481. test!(exa_ub: ls "", exa "ub=38;5;115" => colours c -> { c.size.unit_byte = Fixed(115).normal(); });
  482. test!(exa_uk: ls "", exa "uk=38;5;116" => colours c -> { c.size.unit_kilo = Fixed(116).normal(); });
  483. test!(exa_um: ls "", exa "um=38;5;117" => colours c -> { c.size.unit_mega = Fixed(117).normal(); });
  484. test!(exa_ug: ls "", exa "ug=38;5;118" => colours c -> { c.size.unit_giga = Fixed(118).normal(); });
  485. test!(exa_ut: ls "", exa "ut=38;5;119" => colours c -> { c.size.unit_huge = Fixed(119).normal(); });
  486. test!(exa_df: ls "", exa "df=38;5;115" => colours c -> { c.size.major = Fixed(115).normal(); });
  487. test!(exa_ds: ls "", exa "ds=38;5;116" => colours c -> { c.size.minor = Fixed(116).normal(); });
  488. test!(exa_uu: ls "", exa "uu=38;5;117" => colours c -> { c.users.user_you = Fixed(117).normal(); });
  489. test!(exa_un: ls "", exa "un=38;5;118" => colours c -> { c.users.user_someone_else = Fixed(118).normal(); });
  490. test!(exa_gu: ls "", exa "gu=38;5;119" => colours c -> { c.users.group_yours = Fixed(119).normal(); });
  491. test!(exa_gn: ls "", exa "gn=38;5;120" => colours c -> { c.users.group_not_yours = Fixed(120).normal(); });
  492. test!(exa_lc: ls "", exa "lc=38;5;121" => colours c -> { c.links.normal = Fixed(121).normal(); });
  493. test!(exa_lm: ls "", exa "lm=38;5;122" => colours c -> { c.links.multi_link_file = Fixed(122).normal(); });
  494. test!(exa_ga: ls "", exa "ga=38;5;123" => colours c -> { c.git.new = Fixed(123).normal(); });
  495. test!(exa_gm: ls "", exa "gm=38;5;124" => colours c -> { c.git.modified = Fixed(124).normal(); });
  496. test!(exa_gd: ls "", exa "gd=38;5;125" => colours c -> { c.git.deleted = Fixed(125).normal(); });
  497. test!(exa_gv: ls "", exa "gv=38;5;126" => colours c -> { c.git.renamed = Fixed(126).normal(); });
  498. test!(exa_gt: ls "", exa "gt=38;5;127" => colours c -> { c.git.typechange = Fixed(127).normal(); });
  499. test!(exa_gi: ls "", exa "gi=38;5;128" => colours c -> { c.git.ignored = Fixed(128).normal(); });
  500. test!(exa_gc: ls "", exa "gc=38;5;129" => colours c -> { c.git.conflicted = Fixed(129).normal(); });
  501. test!(exa_xx: ls "", exa "xx=38;5;128" => colours c -> { c.punctuation = Fixed(128).normal(); });
  502. test!(exa_da: ls "", exa "da=38;5;129" => colours c -> { c.date = Fixed(129).normal(); });
  503. test!(exa_in: ls "", exa "in=38;5;130" => colours c -> { c.inode = Fixed(130).normal(); });
  504. test!(exa_bl: ls "", exa "bl=38;5;131" => colours c -> { c.blocks = Fixed(131).normal(); });
  505. test!(exa_hd: ls "", exa "hd=38;5;132" => colours c -> { c.header = Fixed(132).normal(); });
  506. test!(exa_lp: ls "", exa "lp=38;5;133" => colours c -> { c.symlink_path = Fixed(133).normal(); });
  507. test!(exa_cc: ls "", exa "cc=38;5;134" => colours c -> { c.control_char = Fixed(134).normal(); });
  508. test!(exa_oc: ls "", exa "oc=38;5;135" => colours c -> { c.octal = Fixed(135).normal(); });
  509. test!(exa_bo: ls "", exa "bO=4" => colours c -> { c.broken_path_overlay = Style::default().underline(); });
  510. test!(exa_mp: ls "", exa "mp=1;34;4" => colours c -> { c.filekinds.mount_point = Blue.bold().underline(); });
  511. test!(exa_sp: ls "", exa "sp=1;35;4" => colours c -> { c.filekinds.special = Purple.bold().underline(); });
  512. test!(exa_im: ls "", exa "im=38;5;128" => colours c -> { c.file_type.image = Fixed(128).normal(); });
  513. test!(exa_vi: ls "", exa "vi=38;5;129" => colours c -> { c.file_type.video = Fixed(129).normal(); });
  514. test!(exa_mu: ls "", exa "mu=38;5;130" => colours c -> { c.file_type.music = Fixed(130).normal(); });
  515. test!(exa_lo: ls "", exa "lo=38;5;131" => colours c -> { c.file_type.lossless = Fixed(131).normal(); });
  516. test!(exa_cr: ls "", exa "cr=38;5;132" => colours c -> { c.file_type.crypto = Fixed(132).normal(); });
  517. test!(exa_do: ls "", exa "do=38;5;133" => colours c -> { c.file_type.document = Fixed(133).normal(); });
  518. test!(exa_co: ls "", exa "co=38;5;134" => colours c -> { c.file_type.compressed = Fixed(134).normal(); });
  519. test!(exa_tm: ls "", exa "tm=38;5;135" => colours c -> { c.file_type.temp = Fixed(135).normal(); });
  520. test!(exa_cm: ls "", exa "cm=38;5;136" => colours c -> { c.file_type.compiled = Fixed(136).normal(); });
  521. test!(exa_ie: ls "", exa "bu=38;5;137" => colours c -> { c.file_type.build = Fixed(137).normal(); });
  522. test!(exa_Sn: ls "", exa "Sn=38;5;128" => colours c -> { c.security_context.none = Fixed(128).normal(); });
  523. test!(exa_Su: ls "", exa "Su=38;5;129" => colours c -> { c.security_context.selinux.user = Fixed(129).normal(); });
  524. test!(exa_Sr: ls "", exa "Sr=38;5;130" => colours c -> { c.security_context.selinux.role = Fixed(130).normal(); });
  525. test!(exa_St: ls "", exa "St=38;5;131" => colours c -> { c.security_context.selinux.typ = Fixed(131).normal(); });
  526. test!(exa_Sl: ls "", exa "Sl=38;5;132" => colours c -> { c.security_context.selinux.range = Fixed(132).normal(); });
  527. // All the while, LS_COLORS treats them as filenames:
  528. test!(ls_uu: ls "uu=38;5;117", exa "" => exts [ ("uu", Fixed(117).normal()) ]);
  529. test!(ls_un: ls "un=38;5;118", exa "" => exts [ ("un", Fixed(118).normal()) ]);
  530. test!(ls_gu: ls "gu=38;5;119", exa "" => exts [ ("gu", Fixed(119).normal()) ]);
  531. test!(ls_gn: ls "gn=38;5;120", exa "" => exts [ ("gn", Fixed(120).normal()) ]);
  532. // Just like all other keys:
  533. test!(ls_txt: ls "*.txt=31", exa "" => exts [ ("*.txt", Red.normal()) ]);
  534. test!(ls_mp3: ls "*.mp3=38;5;135", exa "" => exts [ ("*.mp3", Fixed(135).normal()) ]);
  535. test!(ls_mak: ls "Makefile=1;32;4", exa "" => exts [ ("Makefile", Green.bold().underline()) ]);
  536. test!(exa_txt: ls "", exa "*.zip=31" => exts [ ("*.zip", Red.normal()) ]);
  537. test!(exa_mp3: ls "", exa "lev.*=38;5;153" => exts [ ("lev.*", Fixed(153).normal()) ]);
  538. test!(exa_mak: ls "", exa "Cargo.toml=4;32;1" => exts [ ("Cargo.toml", Green.bold().underline()) ]);
  539. // Testing whether a glob from EZA_COLORS overrides a glob from LS_COLORS
  540. // can’t be tested here, because they’ll both be added to the same vec
  541. // Values get separated by colons:
  542. test!(ls_multi: ls "*.txt=31:*.rtf=32", exa "" => exts [ ("*.txt", Red.normal()), ("*.rtf", Green.normal()) ]);
  543. test!(exa_multi: ls "", exa "*.tmp=37:*.log=37" => exts [ ("*.tmp", White.normal()), ("*.log", White.normal()) ]);
  544. test!(ls_exa_multi: ls "*.txt=31", exa "*.rtf=32" => exts [ ("*.txt", Red.normal()), ("*.rtf", Green.normal())]);
  545. test!(ls_five: ls "1*1=31:2*2=32:3*3=1;33:4*4=34;1:5*5=35;4", exa "" => exts [
  546. ("1*1", Red.normal()), ("2*2", Green.normal()), ("3*3", Yellow.bold()), ("4*4", Blue.bold()), ("5*5", Purple.underline())
  547. ]);
  548. // Finally, colours get applied right-to-left:
  549. test!(ls_overwrite: ls "pi=31:pi=32:pi=33", exa "" => colours c -> { c.filekinds.pipe = Yellow.normal(); });
  550. test!(exa_overwrite: ls "", exa "da=36:da=35:da=34" => colours c -> { c.date = Blue.normal(); });
  551. // Parse keys and extensions
  552. test!(ls_fi_ls_txt: ls "fi=33:*.txt=31", exa "" => colours c -> { c.filekinds.normal = Yellow.normal(); }, exts [ ("*.txt", Red.normal()) ]);
  553. test!(ls_fi_exa_txt: ls "fi=33", exa "*.txt=31" => colours c -> { c.filekinds.normal = Yellow.normal(); }, exts [ ("*.txt", Red.normal()) ]);
  554. test!(ls_txt_exa_fi: ls "*.txt=31", exa "fi=33" => colours c -> { c.filekinds.normal = Yellow.normal(); }, exts [ ("*.txt", Red.normal()) ]);
  555. test!(eza_fi_exa_txt: ls "", exa "fi=33:*.txt=31" => colours c -> { c.filekinds.normal = Yellow.normal(); }, exts [ ("*.txt", Red.normal()) ]);
  556. }