Parcourir la source

Merge pull request #534 from eza-community/p_icons

feat: do not show icons when piped or in a tty. enable optional flags (icons. color)
Christina Sørensen il y a 2 ans
Parent
commit
562ad25f86

+ 2 - 3
README.md

@@ -310,10 +310,9 @@ eza’s options are almost, but not quite, entirely unlike `ls`’s.
 - **-T**, **--tree**: recurse into directories as a tree
 - **-T**, **--tree**: recurse into directories as a tree
 - **-x**, **--across**: sort the grid across, rather than downwards
 - **-x**, **--across**: sort the grid across, rather than downwards
 - **-F**, **--classify**: display type indicator by file names
 - **-F**, **--classify**: display type indicator by file names
-- **--colo[u]r**: when to use terminal colours
+- **--colo[u]r=(when)**: when to use terminal colours (always, auto, never)
 - **--colo[u]r-scale**: highlight levels of file sizes distinctly
 - **--colo[u]r-scale**: highlight levels of file sizes distinctly
-- **--icons**: display icons
-- **--no-icons**: don't display icons (always overrides --icons)
+- **--icons=(when)**: when to display icons (always, auto, never)
 - **--hyperlink**: display entries as hyperlinks
 - **--hyperlink**: display entries as hyperlinks
 - **-w**, **--width=(columns)**: set screen width in columns
 - **-w**, **--width=(columns)**: set screen width in columns
 
 

+ 5 - 0
completions/bash/eza

@@ -13,6 +13,11 @@ _eza() {
             return
             return
             ;;
             ;;
 
 
+        --icons)
+            mapfile -t COMPREPLY < <(compgen -W 'always automatic auto never' -- "$cur")
+            return
+            ;;
+
         -L|--level)
         -L|--level)
             mapfile -t COMPREPLY < <(compgen -W '{0..9}' -- "$cur")
             mapfile -t COMPREPLY < <(compgen -W '{0..9}' -- "$cur")
             return
             return

+ 6 - 2
completions/fish/eza.fish

@@ -20,8 +20,12 @@ complete -c eza -l color \
 "
 "
 complete -c eza -l color-scale \
 complete -c eza -l color-scale \
     -l colour-scale -d "Highlight levels of file sizes distinctly"
     -l colour-scale -d "Highlight levels of file sizes distinctly"
-complete -c eza -l icons -d "Display icons"
-complete -c eza -l no-icons -d "Don't display icons"
+complete -c eza -l icons -d "When to display icons" -x -a "
+  always\t'Always display icons'
+  auto\t'Display icons if standard output is a terminal'
+  automatic\t'Display icons if standard output is a terminal'
+  never\t'Never display icons'
+"
 complete -c eza -l no-quotes -d "Don't quote file names with spaces"
 complete -c eza -l no-quotes -d "Don't quote file names with spaces"
 complete -c eza -l hyperlink -d "Display entries as hyperlinks"
 complete -c eza -l hyperlink -d "Display entries as hyperlinks"
 complete -c eza -l smart-group -d "Only show group if it has a different name from owner"
 complete -c eza -l smart-group -d "Only show group if it has a different name from owner"

+ 1 - 2
completions/nush/eza.nu

@@ -13,8 +13,7 @@ export extern "eza" [
     --colour                   # When to use terminal colours
     --colour                   # When to use terminal colours
     --color-scale              # Highlight levels of file sizes distinctly
     --color-scale              # Highlight levels of file sizes distinctly
     --colour-scale             # Highlight levels of file sizes distinctly
     --colour-scale             # Highlight levels of file sizes distinctly
-    --icons                    # Display icons
-    --no-icons                 # Don't display icons
+    --icons                    # When to display icons
     --no-quotes                # Don't quote file names with spaces
     --no-quotes                # Don't quote file names with spaces
     --hyperlink                # Display entries as hyperlinks
     --hyperlink                # Display entries as hyperlinks
     --group-directories-first  # Sort directories before other files
     --group-directories-first  # Sort directories before other files

+ 1 - 2
completions/zsh/_eza

@@ -22,8 +22,7 @@ __eza() {
         {-F,--classify}"[Display type indicator by file names]" \
         {-F,--classify}"[Display type indicator by file names]" \
         --colo{,u}r="[When to use terminal colours]:(when):(always auto automatic never)" \
         --colo{,u}r="[When to use terminal colours]:(when):(always auto automatic never)" \
         --colo{,u}r-scale"[Highlight levels of file sizes distinctly]" \
         --colo{,u}r-scale"[Highlight levels of file sizes distinctly]" \
-        --icons"[Display icons]" \
-        --no-icons"[Hide icons]" \
+        --icons="[When to display icons]:(when):(always auto automatic never)" \
         --no-quotes"[Don't quote filenames with spaces]" \
         --no-quotes"[Don't quote filenames with spaces]" \
         --hyperlink"[Display entries as hyperlinks]" \
         --hyperlink"[Display entries as hyperlinks]" \
         --group-directories-first"[Sort directories before other files]" \
         --group-directories-first"[Sort directories before other files]" \

+ 5 - 3
man/eza.1.md

@@ -88,11 +88,13 @@ Manually setting this option overrides `NO_COLOR` environment.
 `--color-scale`, `--colour-scale`
 `--color-scale`, `--colour-scale`
 : Colour file sizes on a scale.
 : Colour file sizes on a scale.
 
 
-`--icons`
+`--icons=WHEN`
 : Display icons next to file names.
 : Display icons next to file names.
 
 
-`--no-icons`
-: Don't display icons. (Always overrides --icons)
+Valid settings are ‘`always`’, ‘`automatic`’ (‘`auto`’ for short), and ‘`never`’.
+The default value is ‘`automatic`’.
+
+`automatic` or `auto` will display icons only when the standard output is connected to a real terminal. If `eza` is ran while in a `tty`, or the output of `eza` is either redirected to a file or piped into another program, icons will not be used. Setting this option to ‘`always`’ causes `eza` to always display icons, while ‘`never`’ disables the use of icons.
 
 
 `--no-quotes`
 `--no-quotes`
 : Don't quote file names with spaces.
 : Don't quote file names with spaces.

+ 38 - 9
src/options/file_name.rs

@@ -5,7 +5,11 @@ use crate::options::{flags, NumberSource, OptionsError};
 use crate::output::file_name::{Classify, EmbedHyperlinks, Options, QuoteStyle, ShowIcons};
 use crate::output::file_name::{Classify, EmbedHyperlinks, Options, QuoteStyle, ShowIcons};
 
 
 impl Options {
 impl Options {
-    pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
+    pub fn deduce<V: Vars>(
+        matches: &MatchedFlags<'_>,
+        vars: &V,
+        is_a_tty: bool,
+    ) -> Result<Self, OptionsError> {
         let classify = Classify::deduce(matches)?;
         let classify = Classify::deduce(matches)?;
         let show_icons = ShowIcons::deduce(matches, vars)?;
         let show_icons = ShowIcons::deduce(matches, vars)?;
 
 
@@ -17,6 +21,7 @@ impl Options {
             show_icons,
             show_icons,
             quote_style,
             quote_style,
             embed_hyperlinks,
             embed_hyperlinks,
+            is_a_tty,
         })
         })
     }
     }
 }
 }
@@ -35,24 +40,48 @@ impl Classify {
 
 
 impl ShowIcons {
 impl ShowIcons {
     pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
     pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
-        if matches.has(&flags::NO_ICONS)? || !matches.has(&flags::ICONS)? {
-            Ok(Self::Off)
-        } else if let Some(columns) = vars
-            .get_with_fallback(vars::EZA_ICON_SPACING, vars::EXA_ICON_SPACING)
+        enum AlwaysOrAuto {
+            Always,
+            Automatic,
+        }
+
+        let mode_opt = matches.get(&flags::ICONS)?;
+        if !matches.has(&flags::ICONS)? && mode_opt.is_none() {
+            return Ok(Self::Never);
+        }
+
+        let mode = match mode_opt {
+            Some(word) => match word.to_str() {
+                Some("always") => AlwaysOrAuto::Always,
+                Some("auto" | "automatic") => AlwaysOrAuto::Automatic,
+                Some("never") => return Ok(Self::Never),
+                None => AlwaysOrAuto::Automatic,
+                _ => return Err(OptionsError::BadArgument(&flags::ICONS, word.into())),
+            },
+            None => AlwaysOrAuto::Automatic,
+        };
+
+        let width = if let Some(columns) = vars
+            .get_with_fallback(vars::EXA_ICON_SPACING, vars::EZA_ICON_SPACING)
             .and_then(|s| s.into_string().ok())
             .and_then(|s| s.into_string().ok())
         {
         {
             match columns.parse() {
             match columns.parse() {
-                Ok(width) => Ok(Self::On(width)),
+                Ok(width) => width,
                 Err(e) => {
                 Err(e) => {
                     let source = NumberSource::Env(
                     let source = NumberSource::Env(
-                        vars.source(vars::EZA_ICON_SPACING, vars::EXA_ICON_SPACING)
+                        vars.source(vars::EXA_ICON_SPACING, vars::EZA_ICON_SPACING)
                             .unwrap(),
                             .unwrap(),
                     );
                     );
-                    Err(OptionsError::FailedParse(columns, source, e))
+                    return Err(OptionsError::FailedParse(columns, source, e));
                 }
                 }
             }
             }
         } else {
         } else {
-            Ok(Self::On(1))
+            1
+        };
+
+        match mode {
+            AlwaysOrAuto::Always => Ok(Self::Always(width)),
+            AlwaysOrAuto::Automatic => Ok(Self::Automatic(width)),
         }
         }
     }
     }
 }
 }

+ 21 - 22
src/options/flags.rs

@@ -16,9 +16,9 @@ pub static DEREF_LINKS: Arg = Arg { short: Some(b'X'), long: "dereference", take
 pub static WIDTH:       Arg = Arg { short: Some(b'w'), long: "width",       takes_value: TakesValue::Necessary(None) };
 pub static WIDTH:       Arg = Arg { short: Some(b'w'), long: "width",       takes_value: TakesValue::Necessary(None) };
 pub static NO_QUOTES:Arg = Arg { short: None,          long: "no-quotes",takes_value: TakesValue::Forbidden };
 pub static NO_QUOTES:Arg = Arg { short: None,          long: "no-quotes",takes_value: TakesValue::Forbidden };
 
 
-pub static COLOR:  Arg = Arg { short: None, long: "color",  takes_value: TakesValue::Necessary(Some(COLOURS)) };
-pub static COLOUR: Arg = Arg { short: None, long: "colour", takes_value: TakesValue::Necessary(Some(COLOURS)) };
-const COLOURS: &[&str] = &["always", "auto", "never"];
+pub static COLOR:  Arg = Arg { short: None, long: "color",  takes_value: TakesValue::Optional(Some(WHEN)) };
+pub static COLOUR: Arg = Arg { short: None, long: "colour", takes_value: TakesValue::Optional(Some(WHEN)) };
+const WHEN: &[&str] = &["always", "auto", "never"];
 
 
 pub static COLOR_SCALE:  Arg = Arg { short: None, long: "color-scale",  takes_value: TakesValue::Forbidden };
 pub static COLOR_SCALE:  Arg = Arg { short: None, long: "color-scale",  takes_value: TakesValue::Forbidden };
 pub static COLOUR_SCALE: Arg = Arg { short: None, long: "colour-scale", takes_value: TakesValue::Forbidden };
 pub static COLOUR_SCALE: Arg = Arg { short: None, long: "colour-scale", takes_value: TakesValue::Forbidden };
@@ -40,23 +40,23 @@ const SORTS: Values = &[ "name", "Name", "size", "extension",
                          "created", "inode", "type", "none" ];
                          "created", "inode", "type", "none" ];
 
 
 // display options
 // display options
-pub static BINARY:      Arg = Arg { short: Some(b'b'), long: "binary",      takes_value: TakesValue::Forbidden };
-pub static BYTES:       Arg = Arg { short: Some(b'B'), long: "bytes",       takes_value: TakesValue::Forbidden };
-pub static GROUP:       Arg = Arg { short: Some(b'g'), long: "group",       takes_value: TakesValue::Forbidden };
-pub static NUMERIC:     Arg = Arg { short: Some(b'n'), long: "numeric",     takes_value: TakesValue::Forbidden };
-pub static HEADER:      Arg = Arg { short: Some(b'h'), long: "header",      takes_value: TakesValue::Forbidden };
-pub static ICONS:       Arg = Arg { short: None,       long: "icons",       takes_value: TakesValue::Forbidden };
-pub static INODE:       Arg = Arg { short: Some(b'i'), long: "inode",       takes_value: TakesValue::Forbidden };
-pub static LINKS:       Arg = Arg { short: Some(b'H'), long: "links",       takes_value: TakesValue::Forbidden };
-pub static MODIFIED:    Arg = Arg { short: Some(b'm'), long: "modified",    takes_value: TakesValue::Forbidden };
-pub static CHANGED:     Arg = Arg { short: None,       long: "changed",     takes_value: TakesValue::Forbidden };
-pub static BLOCKSIZE:   Arg = Arg { short: Some(b'S'), long: "blocksize",   takes_value: TakesValue::Forbidden };
-pub static TIME:        Arg = Arg { short: Some(b't'), long: "time",        takes_value: TakesValue::Necessary(Some(TIMES)) };
-pub static ACCESSED:    Arg = Arg { short: Some(b'u'), long: "accessed",    takes_value: TakesValue::Forbidden };
-pub static CREATED:     Arg = Arg { short: Some(b'U'), long: "created",     takes_value: TakesValue::Forbidden };
-pub static TIME_STYLE:  Arg = Arg { short: None,       long: "time-style",  takes_value: TakesValue::Necessary(Some(TIME_STYLES)) };
-pub static HYPERLINK:   Arg = Arg { short: None,       long: "hyperlink",   takes_value: TakesValue::Forbidden };
-pub static MOUNTS:      Arg = Arg { short: Some(b'M'), long: "mounts",      takes_value: TakesValue::Forbidden };
+pub static BINARY:     Arg = Arg { short: Some(b'b'), long: "binary",     takes_value: TakesValue::Forbidden };
+pub static BYTES:      Arg = Arg { short: Some(b'B'), long: "bytes",      takes_value: TakesValue::Forbidden };
+pub static GROUP:      Arg = Arg { short: Some(b'g'), long: "group",      takes_value: TakesValue::Forbidden };
+pub static NUMERIC:    Arg = Arg { short: Some(b'n'), long: "numeric",    takes_value: TakesValue::Forbidden };
+pub static HEADER:     Arg = Arg { short: Some(b'h'), long: "header",     takes_value: TakesValue::Forbidden };
+pub static ICONS:      Arg = Arg { short: None,       long: "icons",      takes_value: TakesValue::Optional(Some(WHEN))};
+pub static INODE:      Arg = Arg { short: Some(b'i'), long: "inode",      takes_value: TakesValue::Forbidden };
+pub static LINKS:      Arg = Arg { short: Some(b'H'), long: "links",      takes_value: TakesValue::Forbidden };
+pub static MODIFIED:   Arg = Arg { short: Some(b'm'), long: "modified",   takes_value: TakesValue::Forbidden };
+pub static CHANGED:    Arg = Arg { short: None,       long: "changed",    takes_value: TakesValue::Forbidden };
+pub static BLOCKSIZE:  Arg = Arg { short: Some(b'S'), long: "blocksize",  takes_value: TakesValue::Forbidden };
+pub static TIME:       Arg = Arg { short: Some(b't'), long: "time",       takes_value: TakesValue::Necessary(Some(TIMES)) };
+pub static ACCESSED:   Arg = Arg { short: Some(b'u'), long: "accessed",   takes_value: TakesValue::Forbidden };
+pub static CREATED:    Arg = Arg { short: Some(b'U'), long: "created",    takes_value: TakesValue::Forbidden };
+pub static TIME_STYLE: Arg = Arg { short: None,       long: "time-style", takes_value: TakesValue::Necessary(Some(TIME_STYLES)) };
+pub static HYPERLINK:  Arg = Arg { short: None,       long: "hyperlink",  takes_value: TakesValue::Forbidden };
+pub static MOUNTS:     Arg = Arg { short: Some(b'M'), long: "mounts",     takes_value: TakesValue::Forbidden };
 pub static SMART_GROUP: Arg = Arg { short: None,       long: "smart-group", takes_value: TakesValue::Forbidden };
 pub static SMART_GROUP: Arg = Arg { short: None,       long: "smart-group", takes_value: TakesValue::Forbidden };
 const TIMES: Values = &["modified", "changed", "accessed", "created"];
 const TIMES: Values = &["modified", "changed", "accessed", "created"];
 const TIME_STYLES: Values = &["default", "long-iso", "full-iso", "iso", "relative"];
 const TIME_STYLES: Values = &["default", "long-iso", "full-iso", "iso", "relative"];
@@ -66,7 +66,6 @@ pub static NO_PERMISSIONS: Arg = Arg { short: None, long: "no-permissions", take
 pub static NO_FILESIZE: Arg = Arg { short: None, long: "no-filesize", takes_value: TakesValue::Forbidden };
 pub static NO_FILESIZE: Arg = Arg { short: None, long: "no-filesize", takes_value: TakesValue::Forbidden };
 pub static NO_USER: Arg = Arg { short: None, long: "no-user", takes_value: TakesValue::Forbidden };
 pub static NO_USER: Arg = Arg { short: None, long: "no-user", takes_value: TakesValue::Forbidden };
 pub static NO_TIME: Arg = Arg { short: None, long: "no-time", takes_value: TakesValue::Forbidden };
 pub static NO_TIME: Arg = Arg { short: None, long: "no-time", takes_value: TakesValue::Forbidden };
-pub static NO_ICONS: Arg = Arg { short: None, long: "no-icons", takes_value: TakesValue::Forbidden };
 
 
 // optional feature options
 // optional feature options
 pub static GIT:               Arg = Arg { short: None,       long: "git",                  takes_value: TakesValue::Forbidden };
 pub static GIT:               Arg = Arg { short: None,       long: "git",                  takes_value: TakesValue::Forbidden };
@@ -88,7 +87,7 @@ pub static ALL_ARGS: Args = Args(&[
 
 
     &BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
     &BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
     &BLOCKSIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK, &MOUNTS,
     &BLOCKSIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK, &MOUNTS,
-    &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &NO_ICONS, &SMART_GROUP,
+    &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP,
 
 
     &GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
     &GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
     &EXTENDED, &OCTAL, &SECURITY_CONTEXT
     &EXTENDED, &OCTAL, &SECURITY_CONTEXT

+ 2 - 3
src/options/help.rs

@@ -22,8 +22,7 @@ DISPLAY OPTIONS
   -F, --classify     display type indicator by file names
   -F, --classify     display type indicator by file names
   --colo[u]r=WHEN    when to use terminal colours (always, auto, never)
   --colo[u]r=WHEN    when to use terminal colours (always, auto, never)
   --colo[u]r-scale   highlight levels of file sizes distinctly
   --colo[u]r-scale   highlight levels of file sizes distinctly
-  --icons            display icons
-  --no-icons         don't display icons (always overrides --icons)
+  --icons=WHEN       when to display icons (always, auto, never)
   --no-quotes        don't quote file names with spaces
   --no-quotes        don't quote file names with spaces
   --hyperlink        display entries as hyperlinks
   --hyperlink        display entries as hyperlinks
   -w, --width COLS   set screen width in columns
   -w, --width COLS   set screen width in columns
@@ -152,6 +151,6 @@ mod test {
     fn unhelpful() {
     fn unhelpful() {
         let args = vec![];
         let args = vec![];
         let opts = Options::parse(args, &None);
         let opts = Options::parse(args, &None);
-        assert!(!matches!(opts, OptionsResult::Help(_))) // no help when --help isn’t passed
+        assert!(!matches!(opts, OptionsResult::Help(_))); // no help when --help isn’t passed
     }
     }
 }
 }

+ 15 - 7
src/options/parser.rs

@@ -152,7 +152,7 @@ impl Args {
         // Iterate over the inputs with “while let” because we need to advance
         // Iterate over the inputs with “while let” because we need to advance
         // the iterator manually whenever an argument that takes a value
         // the iterator manually whenever an argument that takes a value
         // doesn’t have one in its string so it needs the next one.
         // doesn’t have one in its string so it needs the next one.
-        let mut inputs = inputs.into_iter();
+        let mut inputs = inputs.into_iter().peekable();
         while let Some(arg) = inputs.next() {
         while let Some(arg) = inputs.next() {
             let bytes = os_str_to_bytes(arg);
             let bytes = os_str_to_bytes(arg);
 
 
@@ -198,13 +198,12 @@ impl Args {
                                 return Err(ParseError::NeedsValue { flag, values });
                                 return Err(ParseError::NeedsValue { flag, values });
                             }
                             }
                         }
                         }
-                        TakesValue::Optional(_) => {
-                            if let Some(next_arg) = inputs.next() {
-                                result_flags.push((flag, Some(next_arg)));
-                            } else {
-                                result_flags.push((flag, None));
+                        TakesValue::Optional(_) => match inputs.peek() {
+                            Some(next_arg) if is_optional_arg(next_arg) => {
+                                result_flags.push((flag, Some(inputs.next().unwrap())));
                             }
                             }
-                        }
+                            _ => result_flags.push((flag, None)),
+                        },
                     }
                     }
                 }
                 }
             }
             }
@@ -332,6 +331,15 @@ impl Args {
     }
     }
 }
 }
 
 
+fn is_optional_arg(arg: &OsStr) -> bool {
+    let bytes = os_str_to_bytes(arg);
+    match bytes {
+        // The only optional arguments allowed
+        b"always" | b"auto" | b"automatic" | b"never" => true,
+        _ => false,
+    }
+}
+
 /// The **matches** are the result of parsing the user’s command-line strings.
 /// The **matches** are the result of parsing the user’s command-line strings.
 #[derive(PartialEq, Eq, Debug)]
 #[derive(PartialEq, Eq, Debug)]
 pub struct Matches<'args> {
 pub struct Matches<'args> {

+ 2 - 2
src/options/view.rs

@@ -12,9 +12,9 @@ use crate::output::{details, grid, Mode, TerminalWidth, View};
 impl View {
 impl View {
     pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
     pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
         let mode = Mode::deduce(matches, vars)?;
         let mode = Mode::deduce(matches, vars)?;
-        let width = TerminalWidth::deduce(matches, vars)?;
-        let file_style = FileStyle::deduce(matches, vars)?;
         let deref_links = matches.has(&flags::DEREF_LINKS)?;
         let deref_links = matches.has(&flags::DEREF_LINKS)?;
+        let width = TerminalWidth::deduce(matches, vars)?;
+        let file_style = FileStyle::deduce(matches, vars, width.actual_terminal_width().is_some())?;
         Ok(Self {
         Ok(Self {
             mode,
             mode,
             width,
             width,

+ 22 - 13
src/output/file_name.rs

@@ -24,6 +24,9 @@ pub struct Options {
 
 
     /// Whether to make file names hyperlinks.
     /// Whether to make file names hyperlinks.
     pub embed_hyperlinks: EmbedHyperlinks,
     pub embed_hyperlinks: EmbedHyperlinks,
+
+    /// Whether we are in a console or redirecting the output
+    pub is_a_tty: bool,
 }
 }
 
 
 impl Options {
 impl Options {
@@ -94,14 +97,17 @@ enum MountStyle {
 }
 }
 
 
 /// Whether and how to show icons.
 /// Whether and how to show icons.
-#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+#[derive(PartialEq, Debug, Copy, Clone)]
 pub enum ShowIcons {
 pub enum ShowIcons {
-    /// Don’t show icons at all.
-    Off,
+    /// Display icons next to file names, with the given number of spaces between
+    /// the icon and the file name, even when output isn’t going to a terminal.
+    Always(u32),
+
+    /// Same as Always, but only when output is going to a terminal, not otherwise.
+    Automatic(u32),
 
 
-    /// Show icons next to file names, with the given number of spaces between
-    /// the icon and the file name.
-    On(usize),
+    /// Never display them, even when output is going to a terminal.
+    Never,
 }
 }
 
 
 /// Whether to embed hyperlinks.
 /// Whether to embed hyperlinks.
@@ -176,13 +182,17 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
     pub fn paint(&self) -> TextCellContents {
     pub fn paint(&self) -> TextCellContents {
         let mut bits = Vec::new();
         let mut bits = Vec::new();
 
 
-        if let ShowIcons::On(spaces_count) = self.options.show_icons {
+        let spaces_count_opt = match self.options.show_icons {
+            ShowIcons::Always(spaces_count) => Some(spaces_count),
+            ShowIcons::Automatic(spaces_count) if self.options.is_a_tty => Some(spaces_count),
+            _ => None,
+        };
+
+        if let Some(spaces_count) = spaces_count_opt {
             let style = iconify_style(self.style());
             let style = iconify_style(self.style());
             let file_icon = icon_for_file(self.file).to_string();
             let file_icon = icon_for_file(self.file).to_string();
-
             bits.push(style.paint(file_icon));
             bits.push(style.paint(file_icon));
-
-            bits.push(style.paint(" ".repeat(spaces_count)));
+            bits.push(style.paint(" ".repeat(spaces_count as usize)));
         }
         }
 
 
         if self.file.parent_dir.is_none() {
         if self.file.parent_dir.is_none() {
@@ -217,11 +227,10 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
                     if !target.name.is_empty() {
                     if !target.name.is_empty() {
                         let target_options = Options {
                         let target_options = Options {
                             classify: Classify::JustFilenames,
                             classify: Classify::JustFilenames,
-                            show_icons: ShowIcons::Off,
-
                             quote_style: QuoteStyle::QuoteSpaces,
                             quote_style: QuoteStyle::QuoteSpaces,
-
+                            show_icons: ShowIcons::Never,
                             embed_hyperlinks: EmbedHyperlinks::Off,
                             embed_hyperlinks: EmbedHyperlinks::Off,
+                            is_a_tty: self.options.is_a_tty,
                         };
                         };
 
 
                         let target_name = FileName {
                         let target_name = FileName {

+ 8 - 4
src/output/grid.rs

@@ -61,12 +61,16 @@ impl<'a> Render<'a> {
                 filename.options.embed_hyperlinks,
                 filename.options.embed_hyperlinks,
                 filename.options.show_icons,
                 filename.options.show_icons,
             ) {
             ) {
-                (EmbedHyperlinks::On, ShowIcons::On(spacing)) => {
-                    filename.bare_width() + classification_width + 1 + spacing
-                }
-                (EmbedHyperlinks::On, ShowIcons::Off) => {
+                #[rustfmt::skip]
+                (EmbedHyperlinks::On, ShowIcons::Always(spacing)
+                | ShowIcons::Automatic(spacing))                  => filename.bare_width() + classification_width + 1 + (spacing as usize),
+                (EmbedHyperlinks::On, ShowIcons::Never) => {
                     filename.bare_width() + classification_width
                     filename.bare_width() + classification_width
                 }
                 }
+                (
+                    EmbedHyperlinks::Off,
+                    ShowIcons::Always(spacing) | ShowIcons::Automatic(spacing),
+                ) => filename.bare_width() + 1 + (spacing as usize),
                 (EmbedHyperlinks::Off, _) => *contents.width(),
                 (EmbedHyperlinks::Off, _) => *contents.width(),
             };
             };
 
 

+ 3 - 2
src/output/grid_details.rs

@@ -164,8 +164,9 @@ impl<'a> Render<'a> {
                 let contents = filename.paint();
                 let contents = filename.paint();
                 #[rustfmt::skip]
                 #[rustfmt::skip]
                 let width = match (filename.options.embed_hyperlinks, filename.options.show_icons) {
                 let width = match (filename.options.embed_hyperlinks, filename.options.show_icons) {
-                    (EmbedHyperlinks::On, ShowIcons::On(spacing)) => filename.bare_width() + 1 + spacing,
-                    (EmbedHyperlinks::On, ShowIcons::Off) => filename.bare_width(),
+                    (EmbedHyperlinks::On, ShowIcons::Automatic(spacing)) => filename.bare_width() + 1 + (spacing as usize),
+                    (EmbedHyperlinks::On, ShowIcons::Always(spacing)) => filename.bare_width() + 1 + (spacing as usize),
+                    (EmbedHyperlinks::On, ShowIcons::Never) => filename.bare_width(),
                     (EmbedHyperlinks::Off, _) => *contents.width(),
                     (EmbedHyperlinks::Off, _) => *contents.width(),
                 };
                 };
 
 

+ 1 - 1
tests/cmd/icons_all.toml

@@ -1,2 +1,2 @@
 bin.name = "eza"
 bin.name = "eza"
-args = "tests/itest --icons"
+args = "tests/itest --icons=always"

+ 0 - 0
tests/cmd/long_icons_always.stderr


+ 21 - 0
tests/cmd/long_icons_always.stdout

@@ -0,0 +1,21 @@
+.rw-r--r--  0 nixbld  1 Jan  1970  a
+.rw-r--r--  0 nixbld  1 Jan  1970  b
+.rw-r--r--  0 nixbld  1 Jan  1970  c
+.rw-r--r--  0 nixbld  1 Jan  1970  d
+.rw-r--r--  0 nixbld  1 Jan  1970  e
+drwxr-xr-x  - nixbld  1 Jan  1970  exa
+.rw-r--r--  0 nixbld  1 Jan  1970  f
+.rw-r--r--  0 nixbld  1 Jan  1970  g
+.rw-r--r--  0 nixbld  1 Jan  1970  h
+.rw-r--r--  0 nixbld  1 Jan  1970  i
+.rw-r--r--  0 nixbld  1 Jan  1970  image.jpg.img.c.rs.log.png
+.rw-r--r-- 19 nixbld  1 Jan  1970 󰕙 index.svg
+.rw-r--r--  0 nixbld  1 Jan  1970  j
+.rw-r--r--  0 nixbld  1 Jan  1970  k
+.rw-r--r--  0 nixbld  1 Jan  1970  l
+.rw-r--r--  0 nixbld  1 Jan  1970  m
+.rw-r--r--  0 nixbld  1 Jan  1970  n
+.rw-r--r--  0 nixbld  1 Jan  1970  o
+.rw-r--r--  0 nixbld  1 Jan  1970  p
+.rw-r--r--  0 nixbld  1 Jan  1970  q
+drwxr-xr-x  - nixbld  1 Jan  1970  vagrant

+ 2 - 0
tests/cmd/long_icons_always.toml

@@ -0,0 +1,2 @@
+bin.name = "eza"
+args = "tests/itest --long --icons=always"

+ 21 - 21
tests/cmd/long_icons_nix.stdout

@@ -1,21 +1,21 @@
-.rw-r--r--  0 nixbld  1 Jan  1970 a
-.rw-r--r--  0 nixbld  1 Jan  1970 b
-.rw-r--r--  0 nixbld  1 Jan  1970 c
-.rw-r--r--  0 nixbld  1 Jan  1970 d
-.rw-r--r--  0 nixbld  1 Jan  1970 e
-drwxr-xr-x  - nixbld  1 Jan  1970 exa
-.rw-r--r--  0 nixbld  1 Jan  1970 f
-.rw-r--r--  0 nixbld  1 Jan  1970 g
-.rw-r--r--  0 nixbld  1 Jan  1970 h
-.rw-r--r--  0 nixbld  1 Jan  1970 i
-.rw-r--r--  0 nixbld  1 Jan  1970 image.jpg.img.c.rs.log.png
-.rw-r--r-- 19 nixbld  1 Jan  1970 󰕙 index.svg
-.rw-r--r--  0 nixbld  1 Jan  1970 j
-.rw-r--r--  0 nixbld  1 Jan  1970 k
-.rw-r--r--  0 nixbld  1 Jan  1970 l
-.rw-r--r--  0 nixbld  1 Jan  1970 m
-.rw-r--r--  0 nixbld  1 Jan  1970 n
-.rw-r--r--  0 nixbld  1 Jan  1970 o
-.rw-r--r--  0 nixbld  1 Jan  1970 p
-.rw-r--r--  0 nixbld  1 Jan  1970 q
-drwxr-xr-x  - nixbld  1 Jan  1970 vagrant
+.rw-r--r--  0 nixbld  1 Jan  1970 a
+.rw-r--r--  0 nixbld  1 Jan  1970 b
+.rw-r--r--  0 nixbld  1 Jan  1970 c
+.rw-r--r--  0 nixbld  1 Jan  1970 d
+.rw-r--r--  0 nixbld  1 Jan  1970 e
+drwxr-xr-x  - nixbld  1 Jan  1970 exa
+.rw-r--r--  0 nixbld  1 Jan  1970 f
+.rw-r--r--  0 nixbld  1 Jan  1970 g
+.rw-r--r--  0 nixbld  1 Jan  1970 h
+.rw-r--r--  0 nixbld  1 Jan  1970 i
+.rw-r--r--  0 nixbld  1 Jan  1970 image.jpg.img.c.rs.log.png
+.rw-r--r-- 19 nixbld  1 Jan  1970 index.svg
+.rw-r--r--  0 nixbld  1 Jan  1970 j
+.rw-r--r--  0 nixbld  1 Jan  1970 k
+.rw-r--r--  0 nixbld  1 Jan  1970 l
+.rw-r--r--  0 nixbld  1 Jan  1970 m
+.rw-r--r--  0 nixbld  1 Jan  1970 n
+.rw-r--r--  0 nixbld  1 Jan  1970 o
+.rw-r--r--  0 nixbld  1 Jan  1970 p
+.rw-r--r--  0 nixbld  1 Jan  1970 q
+drwxr-xr-x  - nixbld  1 Jan  1970 vagrant

+ 1 - 1
tests/cmd/long_icons_nix.toml

@@ -1,2 +1,2 @@
 bin.name = "eza"
 bin.name = "eza"
-args = "tests/itest --long --icons"
+args = "tests/itest --long --icons=auto"