Przeglądaj źródła

Replace “mi” colour with “bO” overlay

Fixes #288, but more-or-less as a side-effect.

The “mi” key in LS_COLORS was meant to be used for a missing link path, but it wasn’t really used like that. There was also a bug where control characters in a broken symlink’s path were assumed to be underlined, because that’s what happened in the default colour scheme, but this assumption doesn’t hold when colours were disabled.

The solution to these was not to introduce another configurable colour code, but to start using _overlays_ to alter a bunch of colours at once. The “mi” code will have to be added back later.
Benjamin Sago 8 lat temu
rodzic
commit
59d9e90f20
7 zmienionych plików z 106 dodań i 39 usunięć
  1. 2 2
      contrib/man/exa.1
  2. 3 4
      src/options/style.rs
  3. 24 3
      src/output/file_name.rs
  4. 66 24
      src/style/colours.rs
  5. 4 0
      xtests/links_bw
  6. 4 3
      xtests/run.sh
  7. 3 3
      xtests/themed_links

+ 2 - 2
contrib/man/exa.1

@@ -272,8 +272,6 @@ glob, including keys that happen to be two letters long.
 \f[B]ln\f[], symlinks
 \f[B]ln\f[], symlinks
 .IP \[bu] 2
 .IP \[bu] 2
 \f[B]or\f[], symlinks with no target
 \f[B]or\f[], symlinks with no target
-.IP \[bu] 2
-\f[B]mi\f[], a missing symlink target
 .PP
 .PP
 \f[C]EXA_COLORS\f[] can use many more:
 \f[C]EXA_COLORS\f[] can use many more:
 .IP \[bu] 2
 .IP \[bu] 2
@@ -346,6 +344,8 @@ glob, including keys that happen to be two letters long.
 \f[B]lp\f[], the path of a symlink
 \f[B]lp\f[], the path of a symlink
 .IP \[bu] 2
 .IP \[bu] 2
 \f[B]cc\f[], an escaped character in a filename
 \f[B]cc\f[], an escaped character in a filename
+.IP \[bu] 2
+\f[B]bO\f[], the overlay style for broken symlink paths
 .PP
 .PP
 Values in \f[C]EXA_COLORS\f[] override those given in
 Values in \f[C]EXA_COLORS\f[] override those given in
 \f[C]LS_COLORS\f[], so you don\[aq]t need to re\-write an existing
 \f[C]LS_COLORS\f[], so you don\[aq]t need to re\-write an existing

+ 3 - 4
src/options/style.rs

@@ -428,8 +428,7 @@ mod customs_test {
     test!(ls_bd:   ls "bd=36", exa ""  =>  colours c -> { c.filekinds.block_device = Cyan.normal();   });
     test!(ls_bd:   ls "bd=36", exa ""  =>  colours c -> { c.filekinds.block_device = Cyan.normal();   });
     test!(ls_cd:   ls "cd=35", exa ""  =>  colours c -> { c.filekinds.char_device  = Purple.normal(); });
     test!(ls_cd:   ls "cd=35", exa ""  =>  colours c -> { c.filekinds.char_device  = Purple.normal(); });
     test!(ls_ln:   ls "ln=34", exa ""  =>  colours c -> { c.filekinds.symlink      = Blue.normal();   });
     test!(ls_ln:   ls "ln=34", exa ""  =>  colours c -> { c.filekinds.symlink      = Blue.normal();   });
-    test!(ls_or:   ls "or=33", exa ""  =>  colours c -> { c.broken_arrow           = Yellow.normal(); });
-    test!(ls_mi:   ls "mi=32", exa ""  =>  colours c -> { c.broken_filename        = Green.normal();  });
+    test!(ls_or:   ls "or=33", exa ""  =>  colours c -> { c.broken_symlink         = Yellow.normal(); });
 
 
     // EXA_COLORS can affect all those colours too:
     // EXA_COLORS can affect all those colours too:
     test!(exa_di:  ls "", exa "di=32"  =>  colours c -> { c.filekinds.directory    = Green.normal();  });
     test!(exa_di:  ls "", exa "di=32"  =>  colours c -> { c.filekinds.directory    = Green.normal();  });
@@ -440,8 +439,7 @@ mod customs_test {
     test!(exa_bd:  ls "", exa "bd=35"  =>  colours c -> { c.filekinds.block_device = Purple.normal(); });
     test!(exa_bd:  ls "", exa "bd=35"  =>  colours c -> { c.filekinds.block_device = Purple.normal(); });
     test!(exa_cd:  ls "", exa "cd=34"  =>  colours c -> { c.filekinds.char_device  = Blue.normal();   });
     test!(exa_cd:  ls "", exa "cd=34"  =>  colours c -> { c.filekinds.char_device  = Blue.normal();   });
     test!(exa_ln:  ls "", exa "ln=33"  =>  colours c -> { c.filekinds.symlink      = Yellow.normal(); });
     test!(exa_ln:  ls "", exa "ln=33"  =>  colours c -> { c.filekinds.symlink      = Yellow.normal(); });
-    test!(exa_or:  ls "", exa "or=32"  =>  colours c -> { c.broken_arrow           = Green.normal();  });
-    test!(exa_mi:  ls "", exa "mi=31"  =>  colours c -> { c.broken_filename        = Red.normal();    });
+    test!(exa_or:  ls "", exa "or=32"  =>  colours c -> { c.broken_symlink         = Green.normal();  });
 
 
     // EXA_COLORS will even override options from LS_COLORS:
     // EXA_COLORS will even override options from LS_COLORS:
     test!(ls_exa_di: ls "di=31", exa "di=32"  =>  colours c -> { c.filekinds.directory  = Green.normal();  });
     test!(ls_exa_di: ls "di=31", exa "di=32"  =>  colours c -> { c.filekinds.directory  = Green.normal();  });
@@ -489,6 +487,7 @@ mod customs_test {
     test!(exa_hd:  ls "", exa "hd=38;5;132"  =>  colours c -> { c.header                    = Fixed(132).normal(); });
     test!(exa_hd:  ls "", exa "hd=38;5;132"  =>  colours c -> { c.header                    = Fixed(132).normal(); });
     test!(exa_lp:  ls "", exa "lp=38;5;133"  =>  colours c -> { c.symlink_path              = Fixed(133).normal(); });
     test!(exa_lp:  ls "", exa "lp=38;5;133"  =>  colours c -> { c.symlink_path              = Fixed(133).normal(); });
     test!(exa_cc:  ls "", exa "cc=38;5;134"  =>  colours c -> { c.control_char              = Fixed(134).normal(); });
     test!(exa_cc:  ls "", exa "cc=38;5;134"  =>  colours c -> { c.control_char              = Fixed(134).normal(); });
+    test!(exa_bo:  ls "", exa "bO=4"         =>  colours c -> { c.broken_path_overlay       = Style::default().underline(); });
 
 
     // All the while, LS_COLORS treats them as filenames:
     // All the while, LS_COLORS treats them as filenames:
     test!(ls_uu:   ls "uu=38;5;117", exa ""  =>  exts [ ("uu", Fixed(117).normal()) ]);
     test!(ls_uu:   ls "uu=38;5;117", exa ""  =>  exts [ ("uu", Fixed(117).normal()) ]);

+ 24 - 3
src/output/file_name.rs

@@ -157,7 +157,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
                     bits.push(Style::default().paint(" "));
                     bits.push(Style::default().paint(" "));
                     bits.push(self.colours.broken_arrow().paint("->"));
                     bits.push(self.colours.broken_arrow().paint("->"));
                     bits.push(Style::default().paint(" "));
                     bits.push(Style::default().paint(" "));
-                    escape(broken_path.display().to_string(), &mut bits, self.colours.broken_filename(), self.colours.control_char().underline());
+                    escape(broken_path.display().to_string(), &mut bits, self.colours.broken_filename(), self.colours.broken_control_char());
                 },
                 },
 
 
                 FileTarget::Err(_) => {
                 FileTarget::Err(_) => {
@@ -262,15 +262,36 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
     }
     }
 }
 }
 
 
+
+/// The set of colours that are needed to paint a file name.
 pub trait Colours: FiletypeColours {
 pub trait Colours: FiletypeColours {
+
+    /// The style to paint the path of a symlink’s target, up to but not
+    /// including the file’s name.
+    fn symlink_path(&self) -> Style;
+
+    /// The style to paint the arrow between a link and its target.
+    fn normal_arrow(&self) -> Style;
+
+    /// The style to paint the arrow between a link and its target, when the
+    /// link is broken.
     fn broken_arrow(&self) -> Style;
     fn broken_arrow(&self) -> Style;
+
+    /// The style to paint the entire filename of a broken link.
     fn broken_filename(&self) -> Style;
     fn broken_filename(&self) -> Style;
-    fn normal_arrow(&self) -> Style;
+
+    /// The style to paint a non-displayable control character in a filename.
     fn control_char(&self) -> Style;
     fn control_char(&self) -> Style;
-    fn symlink_path(&self) -> Style;
+
+    /// The style to paint a non-displayable control character in a filename,
+    /// when the filename is being displayed as a broken link target.
+    fn broken_control_char(&self) -> Style;
+
+    /// The style to paint a file that has its executable bit set.
     fn executable_file(&self) -> Style;
     fn executable_file(&self) -> Style;
 }
 }
 
 
+
 // needs Debug because FileStyle derives it
 // needs Debug because FileStyle derives it
 use std::fmt::Debug;
 use std::fmt::Debug;
 use std::marker::Sync;
 use std::marker::Sync;

+ 66 - 24
src/style/colours.rs

@@ -25,10 +25,10 @@ pub struct Colours {
     pub blocks:       Style,
     pub blocks:       Style,
     pub header:       Style,
     pub header:       Style,
 
 
-    pub symlink_path:     Style,
-    pub broken_arrow:     Style,
-    pub broken_filename:  Style,
-    pub control_char:     Style,
+    pub symlink_path:         Style,
+    pub control_char:         Style,
+    pub broken_symlink:       Style,
+    pub broken_path_overlay:  Style,
 }
 }
 
 
 #[derive(Clone, Copy, Debug, Default, PartialEq)]
 #[derive(Clone, Copy, Debug, Default, PartialEq)]
@@ -185,33 +185,73 @@ impl Colours {
             blocks:       Cyan.normal(),
             blocks:       Cyan.normal(),
             header:       Style::default().underline(),
             header:       Style::default().underline(),
 
 
-            symlink_path:     Cyan.normal(),
-            broken_arrow:     Red.normal(),
-            broken_filename:  Red.underline(),
-            control_char:     Red.normal(),
+            symlink_path:         Cyan.normal(),
+            control_char:         Red.normal(),
+            broken_symlink:       Red.normal(),
+            broken_path_overlay:  Style::default().underline(),
         }
         }
     }
     }
 }
 }
 
 
 
 
+/// Some of the styles are **overlays**: although they have the same attribute
+/// set as regular styles (foreground and background colours, bold, underline,
+/// etc), they’re intended to be used to *amend* existing styles.
+///
+/// For example, the target path of a broken symlink is displayed in a red,
+/// underlined style by default. Paths can contain control characters, so
+/// these control characters need to be underlined too, otherwise it looks
+/// weird. So instead of having four separate configurable styles for “link
+/// path”, “broken link path”, “control character” and “broken control
+/// character”, there are styles for “link path”, “control character”, and
+/// “broken link overlay”, the latter of which is just set to override the
+/// underline attribute on the other two.
+fn apply_overlay(mut base: Style, overlay: Style) -> Style {
+    if let Some(fg) = overlay.foreground { base.foreground = Some(fg); }
+    if let Some(bg) = overlay.background { base.background = Some(bg); }
+
+    if overlay.is_bold          { base.is_bold          = true; }
+    if overlay.is_dimmed        { base.is_dimmed        = true; }
+    if overlay.is_italic        { base.is_italic        = true; }
+    if overlay.is_underline     { base.is_underline     = true; }
+    if overlay.is_blink         { base.is_blink         = true; }
+    if overlay.is_reverse       { base.is_reverse       = true; }
+    if overlay.is_hidden        { base.is_hidden        = true; }
+    if overlay.is_strikethrough { base.is_strikethrough = true; }
+
+    base
+}
+// TODO: move this function to the ansi_term crate
+
+
 impl Colours {
 impl Colours {
+
+    /// Sets a value on this set of colours using one of the keys understood
+    /// by the `LS_COLORS` environment variable. Invalid keys set nothing, but
+    /// return false.
     pub fn set_ls(&mut self, pair: &Pair) -> bool {
     pub fn set_ls(&mut self, pair: &Pair) -> bool {
         match pair.key {
         match pair.key {
-            "di" => self.filekinds.directory    = pair.to_style(),
-            "ex" => self.filekinds.executable   = pair.to_style(),
-            "fi" => self.filekinds.normal       = pair.to_style(),
-            "pi" => self.filekinds.pipe         = pair.to_style(),
-            "so" => self.filekinds.socket       = pair.to_style(),
-            "bd" => self.filekinds.block_device = pair.to_style(),
-            "cd" => self.filekinds.char_device  = pair.to_style(),
-            "ln" => self.filekinds.symlink      = pair.to_style(),
-            "or" => self.broken_arrow           = pair.to_style(),
-            "mi" => self.broken_filename        = pair.to_style(),
+            "di" => self.filekinds.directory    = pair.to_style(),  // DIR
+            "ex" => self.filekinds.executable   = pair.to_style(),  // EXEC
+            "fi" => self.filekinds.normal       = pair.to_style(),  // FILE
+            "pi" => self.filekinds.pipe         = pair.to_style(),  // FIFO
+            "so" => self.filekinds.socket       = pair.to_style(),  // SOCK
+            "bd" => self.filekinds.block_device = pair.to_style(),  // BLK
+            "cd" => self.filekinds.char_device  = pair.to_style(),  // CHR
+            "ln" => self.filekinds.symlink      = pair.to_style(),  // LINK
+            "or" => self.broken_symlink         = pair.to_style(),  // ORPHAN
              _   => return false,
              _   => return false,
+             // Codes we don’t do anything with:
+             // MULTIHARDLINK, DOOR, SETUID, SETGID, CAPABILITY,
+             // STICKY_OTHER_WRITABLE, OTHER_WRITABLE, STICKY, MISSING
         }
         }
         true
         true
     }
     }
 
 
+    /// Sets a value on this set of colours using one of the keys understood
+    /// by the `EXA_COLORS` environment variable. Invalid keys set nothing,
+    /// but return false. This doesn’t take the `LS_COLORS` keys into account,
+    /// so `set_ls` should have been run first.
     pub fn set_exa(&mut self, pair: &Pair) -> bool {
     pub fn set_exa(&mut self, pair: &Pair) -> bool {
         match pair.key {
         match pair.key {
             "ur" => self.perms.user_read          = pair.to_style(),
             "ur" => self.perms.user_read          = pair.to_style(),
@@ -254,6 +294,7 @@ impl Colours {
             "hd" => self.header                   = pair.to_style(),
             "hd" => self.header                   = pair.to_style(),
             "lp" => self.symlink_path             = pair.to_style(),
             "lp" => self.symlink_path             = pair.to_style(),
             "cc" => self.control_char             = pair.to_style(),
             "cc" => self.control_char             = pair.to_style(),
+            "bO" => self.broken_path_overlay      = pair.to_style(),
 
 
              _   => return false,
              _   => return false,
         }
         }
@@ -351,11 +392,12 @@ impl render::UserColours for Colours {
 }
 }
 
 
 impl FileNameColours for Colours {
 impl FileNameColours for Colours {
-    fn broken_arrow(&self)    -> Style { self.broken_arrow }
-    fn broken_filename(&self) -> Style { self.broken_filename }
-    fn normal_arrow(&self)    -> Style { self.punctuation }
-    fn control_char(&self)    -> Style { self.control_char }
-    fn symlink_path(&self)    -> Style { self.symlink_path }
-    fn executable_file(&self) -> Style { self.filekinds.executable }
+    fn normal_arrow(&self)        -> Style { self.punctuation }
+    fn broken_arrow(&self)        -> Style { self.broken_symlink }
+    fn broken_filename(&self)     -> Style { apply_overlay(self.broken_symlink, self.broken_path_overlay) }
+    fn broken_control_char(&self) -> Style { apply_overlay(self.control_char,   self.broken_path_overlay) }
+    fn control_char(&self)        -> Style { self.control_char }
+    fn symlink_path(&self)        -> Style { self.symlink_path }
+    fn executable_file(&self)     -> Style { self.filekinds.executable }
 }
 }
 
 

+ 4 - 0
xtests/links_bw

@@ -0,0 +1,4 @@
+/testcases/file-names/links
+├── another: [\n] -> /testcases/file-names/new-line-dir: [\n]/another: [\n]
+├── broken -> /testcases/file-names/new-line-dir: [\n]/broken
+└── subfile -> /testcases/file-names/new-line-dir: [\n]/subfile

+ 4 - 3
xtests/run.sh

@@ -186,8 +186,9 @@ COLUMNS=80 $exa_binary --colour=never     $testcases/files -l | diff -q - $resul
 COLUMNS=80 $exa_binary --colour=automatic $testcases/files -l | diff -q - $results/files_l_bw  || exit 1
 COLUMNS=80 $exa_binary --colour=automatic $testcases/files -l | diff -q - $results/files_l_bw  || exit 1
 
 
 # Switching colour off
 # Switching colour off
-COLUMNS=80 $exa_binary --colour=never     $testcases/file-names      | diff -q - $results/file_names_bw       || exit 1
-COLUMNS=80 $exa_binary --colour=never     $testcases/file-names-exts | diff -q - $results/file-names-exts-bw  || exit 1
+COLUMNS=80 $exa_binary --colour=never    $testcases/file-names       | diff -q - $results/file_names_bw       || exit 1
+COLUMNS=80 $exa_binary --colour=never    $testcases/file-names-exts  | diff -q - $results/file-names-exts-bw  || exit 1
+COLUMNS=80 $exa_binary --colour=never -T $testcases/file-names/links | diff -q - $results/links_bw            || exit 1
 
 
 
 
 # Git
 # Git
@@ -249,7 +250,7 @@ EXA_COLORS="*.deb=1;37" LS_COLORS="*.tar.*=1;37" $exa -1 $testcases/file-names-e
  LS_COLORS="reset:*.deb=1;37:*.tar.*=1;37"       $exa -1 $testcases/file-names-exts/compressed.* 2>&1 | diff -q - $results/themed_compresseds    || exit 1
  LS_COLORS="reset:*.deb=1;37:*.tar.*=1;37"       $exa -1 $testcases/file-names-exts/compressed.* 2>&1 | diff -q - $results/themed_compresseds    || exit 1
 EXA_COLORS="reset:*.deb=1;37:*.tar.*=1;37"       $exa -1 $testcases/file-names-exts/compressed.* 2>&1 | diff -q - $results/themed_compresseds_r  || exit 1
 EXA_COLORS="reset:*.deb=1;37:*.tar.*=1;37"       $exa -1 $testcases/file-names-exts/compressed.* 2>&1 | diff -q - $results/themed_compresseds_r  || exit 1
 
 
-EXA_COLORS="or=32:mi=32;1;4:cc=34;1:ln=34:lp=36;4:xx=32" $exa -1 $testcases/file-names/links 2>&1     | diff -q - $results/themed_links  || exit 1
+EXA_COLORS="or=32:bO=1:cc=35:ln=31:xx=33"        $exa -1 $testcases/file-names/links 2>&1     | diff -q - $results/themed_links  || exit 1
 
 
 # EXA_COLORS overrides LS_COLORS
 # EXA_COLORS overrides LS_COLORS
 LS_COLORS="bd=32:cd=34:pi=31" EXA_COLORS="bd=31:cd=32:pi=34" $exa -1 $testcases/specials 2>&1 | diff -q - $results/themed_specials  || exit 1
 LS_COLORS="bd=32:cd=34:pi=31" EXA_COLORS="bd=31:cd=32:pi=34" $exa -1 $testcases/specials 2>&1 | diff -q - $results/themed_specials  || exit 1

+ 3 - 3
xtests/themed_links

@@ -1,3 +1,3 @@
-another: [\n] -> /testcases/file-names/new-line-dir: [\n]/another: [\n]
-broken -> /testcases/file-names/new-line-dir: [\n]/broken
-subfile -> /testcases/file-names/new-line-dir: [\n]/subfile
+another: [\n] -> /testcases/file-names/new-line-dir: [\n]/another: [\n]
+broken -> /testcases/file-names/new-line-dir: [\n]/broken
+subfile -> /testcases/file-names/new-line-dir: [\n]/subfile