use ansi_term::Style; use glob; use fs::File; use options::{flags, Vars, Misfire}; use options::parser::MatchedFlags; use output::file_name::{FileStyle, Classify}; use style::Colours; /// 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, Debug)] enum TerminalColours { /// 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, } impl Default for TerminalColours { fn default() -> TerminalColours { TerminalColours::Automatic } } impl TerminalColours { /// Determine which terminal colour conditions to use. fn deduce(matches: &MatchedFlags) -> Result { let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? { Some(w) => w, None => return Ok(TerminalColours::default()), }; if word == "always" { Ok(TerminalColours::Always) } else if word == "auto" || word == "automatic" { Ok(TerminalColours::Automatic) } else if word == "never" { Ok(TerminalColours::Never) } else { Err(Misfire::BadArgument(&flags::COLOR, word.into())) } } } /// **Styles**, which is already an overloaded term, is a pair of view option /// sets that happen to both be affected by `LS_COLORS` and `EXA_COLORS`. /// Because it’s better to only iterate through that once, the two are deduced /// together. pub struct Styles { /// The colours to paint user interface elements, like the date column, /// and file kinds, such as directories. pub colours: Colours, /// The colours to paint the names of files that match glob patterns /// (and the classify option). pub style: FileStyle, } impl Styles { #[allow(trivial_casts)] // the "as Box<_>" stuff below warns about this for some reason pub fn deduce(matches: &MatchedFlags, vars: &V, widther: TW) -> Result where TW: Fn() -> Option, V: Vars { use self::TerminalColours::*; use info::filetype::FileExtensions; use output::file_name::NoFileColours; let classify = Classify::deduce(matches)?; // Before we do anything else, figure out if we need to consider // custom colours at all let tc = TerminalColours::deduce(matches)?; if tc == Never || (tc == Automatic && widther().is_none()) { return Ok(Styles { colours: Colours::plain(), style: FileStyle { classify, exts: Box::new(NoFileColours) }, }); } // Parse the environment variables into colours and extension mappings let scale = matches.has_where(|f| f.matches(&flags::COLOR_SCALE) || f.matches(&flags::COLOUR_SCALE))?; let mut colours = Colours::colourful(scale.is_some()); let (exts, use_default_filetypes) = parse_color_vars(vars, &mut colours); // 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<_>, }; let style = FileStyle { classify, exts }; Ok(Styles { colours, style }) } } /// 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(vars: &V, colours: &mut Colours) -> (ExtensionMappings, bool) { use options::vars; use style::LSColors; let mut exts = ExtensionMappings::default(); if let Some(lsc) = vars.get(vars::LS_COLORS) { let lsc = lsc.to_string_lossy(); LSColors(lsc.as_ref()).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) = vars.get(vars::EXA_COLORS) { let exa = exa.to_string_lossy(); // Is this hacky? Yes. if exa == "reset" || exa.starts_with("reset:") { use_default_filetypes = false; } LSColors(exa.as_ref()).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) } #[derive(PartialEq, Debug, Default)] struct ExtensionMappings { mappings: Vec<(glob::Pattern, Style)> } // Loop through backwards so that colours specified later in the list override // colours specified earlier, like we do with options and strict mode use output::file_name::FileColours; impl FileColours for ExtensionMappings { fn colour_file(&self, file: &File) -> Option