Prechádzať zdrojové kódy

feat: add icons=always,auto,never. dont display icons in a tty|piped

PThorpe92 2 rokov pred
rodič
commit
45857f0cf0

+ 38 - 9
src/options/file_name.rs

@@ -2,10 +2,15 @@ use crate::options::parser::MatchedFlags;
 use crate::options::vars::{self, Vars};
 use crate::options::{flags, NumberSource, OptionsError};
 
+use super::vars::EZA_ICON_SPACING;
 use crate::output::file_name::{Classify, EmbedHyperlinks, Options, QuoteStyle, ShowIcons};
 
 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 show_icons = ShowIcons::deduce(matches, vars)?;
 
@@ -17,6 +22,7 @@ impl Options {
             show_icons,
             quote_style,
             embed_hyperlinks,
+            is_a_tty,
         })
     }
 }
@@ -35,24 +41,47 @@ impl Classify {
 
 impl ShowIcons {
     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::NO_ICONS)? || (!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),
+                _ => return Err(OptionsError::BadArgument(&flags::COLOR, 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())
         {
             match columns.parse() {
-                Ok(width) => Ok(Self::On(width)),
+                Ok(width) => width,
                 Err(e) => {
                     let source = NumberSource::Env(
-                        vars.source(vars::EZA_ICON_SPACING, vars::EXA_ICON_SPACING)
+                        vars.source(vars::EXA_ICON_SPACING, EZA_ICON_SPACING)
                             .unwrap(),
                     );
-                    Err(OptionsError::FailedParse(columns, source, e))
+                    return Err(OptionsError::FailedParse(columns, source, e));
                 }
             }
         } else {
-            Ok(Self::On(1))
+            1
+        };
+
+        match mode {
+            AlwaysOrAuto::Always => Ok(Self::Always(width)),
+            AlwaysOrAuto::Automatic => Ok(Self::Automatic(width)),
         }
     }
 }

+ 18 - 17
src/options/flags.rs

@@ -40,23 +40,24 @@ const SORTS: Values = &[ "name", "Name", "size", "extension",
                          "created", "inode", "type", "none" ];
 
 // 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::Necessary(Some(ICONS_VALUES ))};
+const ICONS_VALUES: Values = &["always", "auto", "never"];
+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 };
 const TIMES: Values = &["modified", "changed", "accessed", "created"];
 const TIME_STYLES: Values = &["default", "long-iso", "full-iso", "iso", "relative"];

+ 2 - 2
src/options/view.rs

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

+ 22 - 13
src/output/file_name.rs

@@ -24,6 +24,9 @@ pub struct Options {
 
     /// Whether to make file names hyperlinks.
     pub embed_hyperlinks: EmbedHyperlinks,
+
+    /// Whether we are in a console or redirecting the output
+    pub is_a_tty: bool,
 }
 
 impl Options {
@@ -94,14 +97,17 @@ enum MountStyle {
 }
 
 /// Whether and how to show icons.
-#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+#[derive(PartialEq, Debug, Copy, Clone)]
 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.
@@ -176,13 +182,17 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
     pub fn paint(&self) -> TextCellContents {
         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 file_icon = icon_for_file(self.file).to_string();
-
             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() {
@@ -217,11 +227,10 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
                     if !target.name.is_empty() {
                         let target_options = Options {
                             classify: Classify::JustFilenames,
-                            show_icons: ShowIcons::Off,
-
                             quote_style: QuoteStyle::QuoteSpaces,
-
+                            show_icons: ShowIcons::Never,
                             embed_hyperlinks: EmbedHyperlinks::Off,
+                            is_a_tty: self.options.is_a_tty,
                         };
 
                         let target_name = FileName {

+ 8 - 4
src/output/grid.rs

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

+ 3 - 2
src/output/grid_details.rs

@@ -164,8 +164,9 @@ impl<'a> Render<'a> {
                 let contents = filename.paint();
                 #[rustfmt::skip]
                 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(),
                 };
 

+ 1 - 1
tests/cmd/icons_all.toml

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

+ 1 - 1
tests/cmd/long_icons_nix.toml

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

+ 1 - 0
tests/cmd/non_term_icons.stdout

@@ -0,0 +1 @@
+ a   b   c   d   e   exa   f   g   h   i   image.jpg.img.c.rs.log.png  󰕙 index.svg   j   k   l   m   n   o   p   q   vagrant

+ 2 - 0
tests/cmd/non_term_icons.toml

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