use ansi_term::Style; use crate::fs::File; use crate::output::file_name::Colours as FileNameColours; use crate::output::render; mod ui_styles; pub use self::ui_styles::UiStyles; pub use self::ui_styles::Size as SizeColours; mod lsc; pub use self::lsc::LSColors; mod default_theme; #[derive(PartialEq, Eq, Debug)] pub struct Options { pub use_colours: UseColours, pub colour_scale: ColourScale, pub definitions: Definitions, } /// Under what circumstances we should display coloured, rather than plain, /// output to the terminal. /// /// By default, we want to display the colours when stdout can display them. /// Turning them on when output is going to, say, a pipe, would make programs /// such as `grep` or `more` not work properly. So the `Automatic` mode does /// this check and only displays colours when they can be truly appreciated. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum UseColours { /// Display them even when output isn’t going to a terminal. Always, /// Display them when output is going to a terminal, but not otherwise. Automatic, /// Never display them, even when output is going to a terminal. Never, } #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum ColourScale { Fixed, Gradient, } #[derive(PartialEq, Eq, Debug, Default)] pub struct Definitions { pub ls: Option, pub exa: Option, } pub struct Theme { pub ui: UiStyles, pub exts: Box, } impl Options { #[allow(trivial_casts)] // the `as Box<_>` stuff below warns about this for some reason pub fn to_theme(&self, isatty: bool) -> Theme { use crate::info::filetype::FileExtensions; if self.use_colours == UseColours::Never || (self.use_colours == UseColours::Automatic && ! isatty) { let ui = UiStyles::plain(); let exts = Box::new(NoFileColours); return Theme { ui, exts }; } // Parse the environment variables into colours and extension mappings let mut ui = UiStyles::default_theme(self.colour_scale); let (exts, use_default_filetypes) = self.definitions.parse_color_vars(&mut ui); // Use between 0 and 2 file name highlighters let exts = match (exts.is_non_empty(), use_default_filetypes) { (false, false) => Box::new(NoFileColours) as Box<_>, (false, true) => Box::new(FileExtensions) as Box<_>, ( true, false) => Box::new(exts) as Box<_>, ( true, true) => Box::new((exts, FileExtensions)) as Box<_>, }; Theme { ui, exts } } } impl Definitions { /// Parse the environment variables into `LS_COLORS` pairs, putting file glob /// colours into the `ExtensionMappings` that gets returned, and using the /// two-character UI codes to modify the mutable `Colours`. /// /// Also returns if the `EXA_COLORS` variable should reset the existing file /// type mappings or not. The `reset` code needs to be the first one. fn parse_color_vars(&self, colours: &mut UiStyles) -> (ExtensionMappings, bool) { use log::*; let mut exts = ExtensionMappings::default(); if let Some(lsc) = &self.ls { LSColors(lsc).each_pair(|pair| { if ! colours.set_ls(&pair) { match glob::Pattern::new(pair.key) { Ok(pat) => { exts.add(pat, pair.to_style()); } Err(e) => { warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e); } } } }); } let mut use_default_filetypes = true; if let Some(exa) = &self.exa { // Is this hacky? Yes. if exa == "reset" || exa.starts_with("reset:") { use_default_filetypes = false; } LSColors(exa).each_pair(|pair| { if ! colours.set_ls(&pair) && ! colours.set_exa(&pair) { match glob::Pattern::new(pair.key) { Ok(pat) => { exts.add(pat, pair.to_style()); } Err(e) => { warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e); } } }; }); } (exts, use_default_filetypes) } } pub trait FileColours: std::marker::Sync { fn colour_file(&self, file: &File<'_>) -> Option