Просмотр исходного кода

Merge branch 'main' into fix-git-repos-column-always-showing

Signed-off-by: MartinFillon <114775771+MartinFillon@users.noreply.github.com>
MartinFillon 2 лет назад
Родитель
Сommit
c45951a714

+ 50 - 0
CHANGELOG.md

@@ -1,17 +1,67 @@
 # Changelog
 # Changelog
 
 
+## [0.15.3] - 2023-11-09
+
+### Bug Fixes
+
+- Reformat `help.rs`
+- Allow unused macro rule arms
+
+### Documentation
+
+- Improve CONTRIBUTING.md, README.md
+- Improve README.md
+- Introduce INSTALL.md
+
+### Features
+
+- Create EZA_ICONS_AUTO environment variable
+- Create EZA_ICONS_AUTO environment variable
+- Demo gif and gif generation recipe
+- Add ocaml icon filetypes
+- Add PRQL
+- Add `--color-scale`
+
+### Miscellaneous Tasks
+
+- Add to CODEOWNERS file to make sure I get ping'd on files being touched
+- Add myself to codeowners to watch modifications on parsing
+- Improve the PR template
+
+### Refactor
+
+- Remove commented out test code
+- Finalize `decay` -> `color_scale`
+
+### Build
+
+- Refactor flake
+- Bump DeterminateSystems/nix-installer-action from 4 to 7
+- Bump libc from 0.2.149 to 0.2.150
+- Bump rustix from 0.38.13 to 0.38.21
+
+### Ci
+
+- Refactor pre-commit-hooks
+- Refactor publish workflow
+
 ## [0.15.2] - 2023-11-02
 ## [0.15.2] - 2023-11-02
 
 
 ### Bug Fixes
 ### Bug Fixes
 
 
 - Correct width when --no-quotes is used
 - Correct width when --no-quotes is used
 - Clippy lint and add option to grid-details
 - Clippy lint and add option to grid-details
+- Changed quote in --almost-all completion
 - --smart-group only works for current user
 - --smart-group only works for current user
 
 
 ### Features
 ### Features
 
 
 - Add Typst to the recognized files
 - Add Typst to the recognized files
 
 
+### Miscellaneous Tasks
+
+- Release eza v0.15.2
+
 ### Refactor
 ### Refactor
 
 
 - Replace `lazy_static` with `once_cell`
 - Replace `lazy_static` with `once_cell`

+ 50 - 5
Cargo.lock

@@ -32,12 +32,19 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 
+[[package]]
+name = "ansi_colours"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a1558bd2075d341b9ca698ec8eb6fcc55a746b1fc4255585aad5b141d918a80"
+
 [[package]]
 [[package]]
 name = "ansiterm"
 name = "ansiterm"
 version = "0.12.2"
 version = "0.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4ab587f5395da16dd2e6939adf53dede583221b320cadfb94e02b5b7b9bf24cc"
 checksum = "4ab587f5395da16dd2e6939adf53dede583221b320cadfb94e02b5b7b9bf24cc"
 dependencies = [
 dependencies = [
+ "ansi_colours",
  "winapi",
  "winapi",
 ]
 ]
 
 
@@ -89,6 +96,15 @@ dependencies = [
  "windows-sys",
  "windows-sys",
 ]
 ]
 
 
+[[package]]
+name = "approx"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
+dependencies = [
+ "num-traits",
+]
+
 [[package]]
 [[package]]
 name = "autocfg"
 name = "autocfg"
 version = "1.1.0"
 version = "1.1.0"
@@ -356,7 +372,7 @@ dependencies = [
 
 
 [[package]]
 [[package]]
 name = "eza"
 name = "eza"
-version = "0.15.2"
+version = "0.15.3"
 dependencies = [
 dependencies = [
  "ansiterm",
  "ansiterm",
  "chrono",
  "chrono",
@@ -370,6 +386,7 @@ dependencies = [
  "num_cpus",
  "num_cpus",
  "number_prefix",
  "number_prefix",
  "once_cell",
  "once_cell",
+ "palette",
  "percent-encoding",
  "percent-encoding",
  "phf",
  "phf",
  "proc-mounts",
  "proc-mounts",
@@ -384,6 +401,12 @@ dependencies = [
  "zoneinfo_compiled",
  "zoneinfo_compiled",
 ]
 ]
 
 
+[[package]]
+name = "fast-srgb8"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
+
 [[package]]
 [[package]]
 name = "fastrand"
 name = "fastrand"
 version = "2.0.0"
 version = "2.0.0"
@@ -587,9 +610,9 @@ dependencies = [
 
 
 [[package]]
 [[package]]
 name = "linux-raw-sys"
 name = "linux-raw-sys"
-version = "0.4.7"
+version = "0.4.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
 
 
 [[package]]
 [[package]]
 name = "locale"
 name = "locale"
@@ -709,6 +732,28 @@ dependencies = [
  "windows-sys",
  "windows-sys",
 ]
 ]
 
 
+[[package]]
+name = "palette"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2e2f34147767aa758aa649415b50a69eeb46a67f9dc7db8011eeb3d84b351dc"
+dependencies = [
+ "approx",
+ "fast-srgb8",
+ "palette_derive",
+]
+
+[[package]]
+name = "palette_derive"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7db010ec5ff3d4385e4f133916faacd9dad0f6a09394c92d825b3aed310fa0a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 [[package]]
 name = "partition-identity"
 name = "partition-identity"
 version = "0.3.0"
 version = "0.3.0"
@@ -910,9 +955,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
 
 
 [[package]]
 [[package]]
 name = "rustix"
 name = "rustix"
-version = "0.38.13"
+version = "0.38.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662"
+checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
 dependencies = [
 dependencies = [
  "bitflags 2.4.0",
  "bitflags 2.4.0",
  "errno",
  "errno",

+ 3 - 2
Cargo.toml

@@ -16,7 +16,7 @@ readme = "README.md"
 homepage = "https://github.com/eza-community/eza"
 homepage = "https://github.com/eza-community/eza"
 license = "MIT"
 license = "MIT"
 repository = "https://github.com/eza-community/eza"
 repository = "https://github.com/eza-community/eza"
-version = "0.15.2"
+version = "0.15.3"
 
 
 
 
 [package.metadata.deb]
 [package.metadata.deb]
@@ -71,7 +71,7 @@ name = "eza"
 
 
 
 
 [dependencies]
 [dependencies]
-ansiterm = "0.12.2"
+ansiterm = { version = "0.12.2", features = ["ansi_colours"] }
 chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
 chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
 glob = "0.3"
 glob = "0.3"
 libc = "0.2"
 libc = "0.2"
@@ -80,6 +80,7 @@ log = "0.4"
 natord = "1.0"
 natord = "1.0"
 num_cpus = "1.16"
 num_cpus = "1.16"
 number_prefix = "0.4"
 number_prefix = "0.4"
+palette = { version = "0.7.3", default-features = false, features = ["std"] }
 once_cell = "1.18.0"
 once_cell = "1.18.0"
 percent-encoding = "2.3.0"
 percent-encoding = "2.3.0"
 phf = { version = "0.11.2", features = ["macros"] }
 phf = { version = "0.11.2", features = ["macros"] }

+ 2 - 1
README.md

@@ -90,7 +90,8 @@ eza’s options are almost, but not quite, entirely unlike `ls`’s.
 - **-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)**: 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=(field)**: highlight levels of `field` distinctly(all, age, size)
+- **--color-scale-mode=(mode)**: use gradient or fixed colors in --color-scale. valid options are `fixed` or `gradient`
 - **--icons=(when)**: when to display icons (always, auto, never)
 - **--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

+ 10 - 0
completions/bash/eza

@@ -37,6 +37,16 @@ _eza() {
             mapfile -t COMPREPLY < <(compgen -W 'default iso long-iso full-iso relative --' -- "$cur")
             mapfile -t COMPREPLY < <(compgen -W 'default iso long-iso full-iso relative --' -- "$cur")
             return
             return
             ;;
             ;;
+
+        --color-scale)
+            mapfile -t COMPREPLY < <(compgen -W 'all age size --' -- "$cur")
+            return
+            ;;
+
+        --color-scale-mode)
+            mapfile -t COMPREPLY < <(compgen -W 'fixed gradient --' -- "$cur")
+            return
+            ;;
     esac
     esac
 
 
     case "$cur" in
     case "$cur" in

+ 10 - 1
completions/fish/eza.fish

@@ -19,7 +19,16 @@ complete -c eza -l color \
     never\t'Never use colour'
     never\t'Never use colour'
 "
 "
 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 'field' distinctly" -x -a "
+    all\t''
+    age\t''
+    size\t''
+"
+complete -c eza -l color-scale-mode \
+    -d "Use gradient or fixed colors in --color-scale" -x -a "
+    fixed\t'Highlight based on fixed colors'
+    gradient\t'Highlight based \'field\' in relation to other files'
+"
 complete -c eza -l icons -d "When to display icons" -x -a "
 complete -c eza -l icons -d "When to display icons" -x -a "
   always\t'Always display icons'
   always\t'Always display icons'
   auto\t'Display icons if standard output is a terminal'
   auto\t'Display icons if standard output is a terminal'

+ 2 - 0
completions/nush/eza.nu

@@ -13,6 +13,8 @@ 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
+    --color-scale-mode         # Use gradient or fixed colors in --color-scale
+    --colour-scale-mode        # Use gradient or fixed colors in --colour-scale
     --icons                    # When to 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

+ 2 - 1
completions/zsh/_eza

@@ -21,7 +21,8 @@ __eza() {
         {-X,--dereference}"[Dereference symbolic links when displaying information]" \
         {-X,--dereference}"[Dereference symbolic links when displaying information]" \
         {-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 'field' distinctly]:(fields):(all age size)" \
+        --colo{,u}r-scale-mode"[Use gradient or fixed colors in --color-scale]:(mode):(fixed gradient)" \
         --icons="[When to display icons]:(when):(always auto automatic never)" \
         --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]" \

+ 17 - 2
man/eza.1.md

@@ -86,7 +86,14 @@ The default behavior (‘`automatic`’ or ‘`auto`’) is to colorize the outp
 Manually setting this option overrides `NO_COLOR` environment.
 Manually setting this option overrides `NO_COLOR` environment.
 
 
 `--color-scale`, `--colour-scale`
 `--color-scale`, `--colour-scale`
-: Colour file sizes on a scale.
+: highlight levels of `field` distinctly.
+Use comma(,) separated list of all, age, size
+
+`--color-scale-mode`, `--colour-scale-mode`
+: Use gradient or fixed colors in `--color-scale`.
+
+Valid options are `fixed` or `gradient`.
+The default value is `gradient`.
 
 
 `--icons=WHEN`
 `--icons=WHEN`
 : Display icons next to file names.
 : Display icons next to file names.
@@ -105,10 +112,15 @@ The default value is ‘`automatic`’.
 `-w`, `--width=COLS`
 `-w`, `--width=COLS`
 : Set screen width in columns.
 : Set screen width in columns.
 
 
+Valid options are `none`, `absolute` or `relative`.
+The default value is `none`
+
+`absolute` mode highlights based on file modification time relative to the past year.
+`relative` mode highlights based on file modification time in relation to other files. `none` disables highlighting.
+
 `--smart-group`
 `--smart-group`
 : Only show group if it has a different name from owner
 : Only show group if it has a different name from owner
 
 
-
 FILTERING AND SORTING OPTIONS
 FILTERING AND SORTING OPTIONS
 =============================
 =============================
 
 
@@ -302,6 +314,9 @@ For more information on the format of these environment variables, see the [eza_
 
 
 Overrides any `--git` or `--git-repos` argument
 Overrides any `--git` or `--git-repos` argument
 
 
+## `EZA_MIN_LUMINANCE`
+Specifies the minimum luminance to use when decay is active. It's value can be between -100 to 100.
+
 ## `EZA_ICONS_AUTO`
 ## `EZA_ICONS_AUTO`
 
 
 If set, automates the same behavior as using `--icons` or `--icons=auto`. Useful for if you always want to have icons enabled.
 If set, automates the same behavior as using `--icons` or `--icons=auto`. Useful for if you always want to have icons enabled.

+ 1 - 0
src/info/filetype.rs

@@ -344,6 +344,7 @@ const EXTENSION_TYPES: Map<&'static str, FileType> = phf_map! {
     "pm"         => FileType::Source, // Perl
     "pm"         => FileType::Source, // Perl
     "pod"        => FileType::Source, // Perl
     "pod"        => FileType::Source, // Perl
     "pp"         => FileType::Source, // Puppet
     "pp"         => FileType::Source, // Puppet
+    "prql"       => FileType::Source, // PRQL
     "ps1"        => FileType::Source, // PowerShell
     "ps1"        => FileType::Source, // PowerShell
     "psd1"       => FileType::Source, // PowerShell
     "psd1"       => FileType::Source, // PowerShell
     "psm1"       => FileType::Source, // PowerShell
     "psm1"       => FileType::Source, // PowerShell

+ 79 - 356
src/options/flags.rs

@@ -1,377 +1,100 @@
 use crate::options::parser::{Arg, Args, TakesValue, Values};
 use crate::options::parser::{Arg, Args, TakesValue, Values};
 
 
 // exa options
 // exa options
-pub static VERSION: Arg = Arg {
-    short: Some(b'v'),
-    long: "version",
-    takes_value: TakesValue::Forbidden,
-};
-pub static HELP: Arg = Arg {
-    short: Some(b'?'),
-    long: "help",
-    takes_value: TakesValue::Forbidden,
-};
+pub static VERSION: Arg = Arg { short: Some(b'v'), long: "version",  takes_value: TakesValue::Forbidden };
+pub static HELP:    Arg = Arg { short: Some(b'?'), long: "help",     takes_value: TakesValue::Forbidden };
 
 
 // display options
 // display options
-pub static ONE_LINE: Arg = Arg {
-    short: Some(b'1'),
-    long: "oneline",
-    takes_value: TakesValue::Forbidden,
-};
-pub static LONG: Arg = Arg {
-    short: Some(b'l'),
-    long: "long",
-    takes_value: TakesValue::Forbidden,
-};
-pub static GRID: Arg = Arg {
-    short: Some(b'G'),
-    long: "grid",
-    takes_value: TakesValue::Forbidden,
-};
-pub static ACROSS: Arg = Arg {
-    short: Some(b'x'),
-    long: "across",
-    takes_value: TakesValue::Forbidden,
-};
-pub static RECURSE: Arg = Arg {
-    short: Some(b'R'),
-    long: "recurse",
-    takes_value: TakesValue::Forbidden,
-};
-pub static TREE: Arg = Arg {
-    short: Some(b'T'),
-    long: "tree",
-    takes_value: TakesValue::Forbidden,
-};
-pub static CLASSIFY: Arg = Arg {
-    short: Some(b'F'),
-    long: "classify",
-    takes_value: TakesValue::Forbidden,
-};
-pub static DEREF_LINKS: Arg = Arg {
-    short: Some(b'X'),
-    long: "dereference",
-    takes_value: TakesValue::Forbidden,
-};
-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 ONE_LINE:    Arg = Arg { short: Some(b'1'), long: "oneline",     takes_value: TakesValue::Forbidden };
+pub static LONG:        Arg = Arg { short: Some(b'l'), long: "long",        takes_value: TakesValue::Forbidden };
+pub static GRID:        Arg = Arg { short: Some(b'G'), long: "grid",        takes_value: TakesValue::Forbidden };
+pub static ACROSS:      Arg = Arg { short: Some(b'x'), long: "across",      takes_value: TakesValue::Forbidden };
+pub static RECURSE:     Arg = Arg { short: Some(b'R'), long: "recurse",     takes_value: TakesValue::Forbidden };
+pub static TREE:        Arg = Arg { short: Some(b'T'), long: "tree",        takes_value: TakesValue::Forbidden };
+pub static CLASSIFY:    Arg = Arg { short: Some(b'F'), long: "classify",    takes_value: TakesValue::Forbidden };
+pub static DEREF_LINKS: Arg = Arg { short: Some(b'X'), long: "dereference", takes_value: TakesValue::Forbidden };
+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 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)),
-};
+pub static COLOR:  Arg = Arg { short: None, long: "color",  takes_value: TakesValue::Optional(Some(WHEN), "auto") };
+pub static COLOUR: Arg = Arg { short: None, long: "colour", takes_value: TakesValue::Optional(Some(WHEN), "auto") };
 const WHEN: &[&str] = &["always", "auto", "never"];
 const WHEN: &[&str] = &["always", "auto", "never"];
 
 
-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 COLOR_SCALE:  Arg = Arg { short: None, long: "color-scale",  takes_value: TakesValue::Necessary(Some(SCALES)) };
+pub static COLOUR_SCALE: Arg = Arg { short: None, long: "colour-scale", takes_value: TakesValue::Necessary(Some(SCALES)) };
+pub static COLOR_SCALE_MODE:  Arg = Arg { short: None, long: "color-scale-mode",  takes_value: TakesValue::Necessary(Some(COLOR_SCALE_MODES))};
+pub static COLOUR_SCALE_MODE: Arg = Arg { short: None, long: "colour-scale-mode", takes_value: TakesValue::Necessary(Some(COLOR_SCALE_MODES))};
+const SCALES: Values = &["all", "size", "age"];
+const COLOR_SCALE_MODES: Values = &["fixed", "gradient"];
 
 
 // filtering and sorting options
 // filtering and sorting options
-pub static ALL: Arg = Arg {
-    short: Some(b'a'),
-    long: "all",
-    takes_value: TakesValue::Forbidden,
-};
-pub static ALMOST_ALL: Arg = Arg {
-    short: Some(b'A'),
-    long: "almost-all",
-    takes_value: TakesValue::Forbidden,
-};
-pub static LIST_DIRS: Arg = Arg {
-    short: Some(b'd'),
-    long: "list-dirs",
-    takes_value: TakesValue::Forbidden,
-};
-pub static LEVEL: Arg = Arg {
-    short: Some(b'L'),
-    long: "level",
-    takes_value: TakesValue::Necessary(None),
-};
-pub static REVERSE: Arg = Arg {
-    short: Some(b'r'),
-    long: "reverse",
-    takes_value: TakesValue::Forbidden,
-};
-pub static SORT: Arg = Arg {
-    short: Some(b's'),
-    long: "sort",
-    takes_value: TakesValue::Necessary(Some(SORTS)),
-};
-pub static IGNORE_GLOB: Arg = Arg {
-    short: Some(b'I'),
-    long: "ignore-glob",
-    takes_value: TakesValue::Necessary(None),
-};
-pub static GIT_IGNORE: Arg = Arg {
-    short: None,
-    long: "git-ignore",
-    takes_value: TakesValue::Forbidden,
-};
-pub static DIRS_FIRST: Arg = Arg {
-    short: None,
-    long: "group-directories-first",
-    takes_value: TakesValue::Forbidden,
-};
-pub static ONLY_DIRS: Arg = Arg {
-    short: Some(b'D'),
-    long: "only-dirs",
-    takes_value: TakesValue::Forbidden,
-};
-pub static ONLY_FILES: Arg = Arg {
-    short: Some(b'f'),
-    long: "only-files",
-    takes_value: TakesValue::Forbidden,
-};
-const SORTS: Values = &[
-    "name",
-    "Name",
-    "size",
-    "extension",
-    "Extension",
-    "modified",
-    "changed",
-    "accessed",
-    "created",
-    "inode",
-    "type",
-    "none",
-];
+pub static ALL:         Arg = Arg { short: Some(b'a'), long: "all",         takes_value: TakesValue::Forbidden };
+pub static ALMOST_ALL:  Arg = Arg { short: Some(b'A'), long: "almost-all",  takes_value: TakesValue::Forbidden };
+pub static LIST_DIRS:   Arg = Arg { short: Some(b'd'), long: "list-dirs",   takes_value: TakesValue::Forbidden };
+pub static LEVEL:       Arg = Arg { short: Some(b'L'), long: "level",       takes_value: TakesValue::Necessary(None) };
+pub static REVERSE:     Arg = Arg { short: Some(b'r'), long: "reverse",     takes_value: TakesValue::Forbidden };
+pub static SORT:        Arg = Arg { short: Some(b's'), long: "sort",        takes_value: TakesValue::Necessary(Some(SORTS)) };
+pub static IGNORE_GLOB: Arg = Arg { short: Some(b'I'), long: "ignore-glob", takes_value: TakesValue::Necessary(None) };
+pub static GIT_IGNORE:  Arg = Arg { short: None, long: "git-ignore",           takes_value: TakesValue::Forbidden };
+pub static DIRS_FIRST:  Arg = Arg { short: None, long: "group-directories-first",  takes_value: TakesValue::Forbidden };
+pub static ONLY_DIRS:   Arg = Arg { short: Some(b'D'), long: "only-dirs", takes_value: TakesValue::Forbidden };
+pub static ONLY_FILES:  Arg = Arg { short: Some(b'f'), long: "only-files", takes_value: TakesValue::Forbidden };
+const SORTS: Values = &[ "name", "Name", "size", "extension",
+                         "Extension", "modified", "changed", "accessed",
+                         "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::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 TOTAL_SIZE: Arg = Arg {
-    short: None,
-    long: "total-size",
-    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 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), "auto")};
+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 TOTAL_SIZE:  Arg = Arg { short: None,       long: "total-size",  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 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"];
 
 
 // suppressing columns
 // suppressing columns
-pub static NO_PERMISSIONS: Arg = Arg {
-    short: None,
-    long: "no-permissions",
-    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_TIME: Arg = Arg {
-    short: None,
-    long: "no-time",
-    takes_value: TakesValue::Forbidden,
-};
+pub static NO_PERMISSIONS: Arg = Arg { short: None, long: "no-permissions", 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_TIME: Arg = Arg { short: None, long: "no-time", 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 NO_GIT: Arg = Arg {
-    short: None,
-    long: "no-git",
-    takes_value: TakesValue::Forbidden,
-};
-pub static GIT_REPOS: Arg = Arg {
-    short: None,
-    long: "git-repos",
-    takes_value: TakesValue::Forbidden,
-};
-pub static GIT_REPOS_NO_STAT: Arg = Arg {
-    short: None,
-    long: "git-repos-no-status",
-    takes_value: TakesValue::Forbidden,
-};
-pub static EXTENDED: Arg = Arg {
-    short: Some(b'@'),
-    long: "extended",
-    takes_value: TakesValue::Forbidden,
-};
-pub static OCTAL: Arg = Arg {
-    short: Some(b'o'),
-    long: "octal-permissions",
-    takes_value: TakesValue::Forbidden,
-};
-pub static SECURITY_CONTEXT: Arg = Arg {
-    short: Some(b'Z'),
-    long: "context",
-    takes_value: TakesValue::Forbidden,
-};
+pub static GIT:               Arg = Arg { short: None,       long: "git",                  takes_value: TakesValue::Forbidden };
+pub static NO_GIT:            Arg = Arg { short: None,       long: "no-git",               takes_value: TakesValue::Forbidden };
+pub static GIT_REPOS:         Arg = Arg { short: None,       long: "git-repos",            takes_value: TakesValue::Forbidden };
+pub static GIT_REPOS_NO_STAT: Arg = Arg { short: None,       long: "git-repos-no-status",  takes_value: TakesValue::Forbidden };
+pub static EXTENDED:          Arg = Arg { short: Some(b'@'), long: "extended",             takes_value: TakesValue::Forbidden };
+pub static OCTAL:             Arg = Arg { short: Some(b'o'), long: "octal-permissions",    takes_value: TakesValue::Forbidden };
+pub static SECURITY_CONTEXT:  Arg = Arg { short: Some(b'Z'), long: "context",              takes_value: TakesValue::Forbidden };
 
 
 pub static ALL_ARGS: Args = Args(&[
 pub static ALL_ARGS: Args = Args(&[
-    &VERSION,
-    &HELP,
-    &ONE_LINE,
-    &LONG,
-    &GRID,
-    &ACROSS,
-    &RECURSE,
-    &TREE,
-    &CLASSIFY,
-    &DEREF_LINKS,
-    &COLOR,
-    &COLOUR,
-    &COLOR_SCALE,
-    &COLOUR_SCALE,
-    &WIDTH,
-    &NO_QUOTES,
-    &ALL,
-    &ALMOST_ALL,
-    &LIST_DIRS,
-    &LEVEL,
-    &REVERSE,
-    &SORT,
-    &DIRS_FIRST,
-    &IGNORE_GLOB,
-    &GIT_IGNORE,
-    &ONLY_DIRS,
-    &ONLY_FILES,
-    &BINARY,
-    &BYTES,
-    &GROUP,
-    &NUMERIC,
-    &HEADER,
-    &ICONS,
-    &INODE,
-    &LINKS,
-    &MODIFIED,
-    &CHANGED,
-    &BLOCKSIZE,
-    &TOTAL_SIZE,
-    &TIME,
-    &ACCESSED,
-    &CREATED,
-    &TIME_STYLE,
-    &HYPERLINK,
-    &MOUNTS,
-    &NO_PERMISSIONS,
-    &NO_FILESIZE,
-    &NO_USER,
-    &NO_TIME,
-    &SMART_GROUP,
-    &GIT,
-    &NO_GIT,
-    &GIT_REPOS,
-    &GIT_REPOS_NO_STAT,
-    &EXTENDED,
-    &OCTAL,
-    &SECURITY_CONTEXT,
+    &VERSION, &HELP,
+
+    &ONE_LINE, &LONG, &GRID, &ACROSS, &RECURSE, &TREE, &CLASSIFY, &DEREF_LINKS,
+    &COLOR, &COLOUR, &COLOR_SCALE, &COLOUR_SCALE, &COLOR_SCALE_MODE, &COLOUR_SCALE_MODE,
+    &WIDTH, &NO_QUOTES,
+
+    &ALL, &ALMOST_ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &DIRS_FIRST,
+    &IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS, &ONLY_FILES,
+
+    &BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
+    &BLOCKSIZE, &TOTAL_SIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK, &MOUNTS,
+    &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP,
+
+    &GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
+    &EXTENDED, &OCTAL, &SECURITY_CONTEXT
 ]);
 ]);

+ 52 - 46
src/options/help.rs

@@ -8,29 +8,31 @@ static USAGE_PART1: &str = "Usage:
   eza [options] [files...]
   eza [options] [files...]
 
 
 META OPTIONS
 META OPTIONS
-  --help             show list of command-line options
-  -v, --version      show version of eza
+  --help                     show list of command-line options
+  -v, --version              show version of eza
 
 
 DISPLAY OPTIONS
 DISPLAY OPTIONS
-  -1, --oneline      display one entry per line
-  -l, --long         display extended file metadata as a table
-  -G, --grid         display entries as a grid (default)
-  -x, --across       sort the grid across, rather than downwards
-  -R, --recurse      recurse into directories
-  -T, --tree         recurse into directories as a tree
-  -X, --dereference  dereference symbolic links when displaying information
-  -F, --classify     display type indicator by file names
-  --colo[u]r=WHEN    when to use terminal colours (always, auto, never)
-  --colo[u]r-scale   highlight levels of file sizes distinctly
-  --icons=WHEN       when to display icons (always, auto, never)
-  --no-quotes        don't quote file names with spaces
-  --hyperlink        display entries as hyperlinks
-  -w, --width COLS   set screen width in columns
-  --smart-group      only show group if it has a different name from owner
+  -1, --oneline              display one entry per line
+  -l, --long                 display extended file metadata as a table
+  -G, --grid                 display entries as a grid (default)
+  -x, --across               sort the grid across, rather than downwards
+  -R, --recurse              recurse into directories
+  -T, --tree                 recurse into directories as a tree
+  -X, --dereference          dereference symbolic links when displaying information
+  -F, --classify             display type indicator by file names
+  --colo[u]r=WHEN            when to use terminal colours (always, auto, never)
+  --colo[u]r-scale           highlight levels of 'field' distinctly(all, age, size)
+  --colo[u]r-scale-mode      use gradient or fixed colors in --color-scale (fixed, gradient)
+  --icons=WHEN               when to display icons (always, auto, never)
+  --no-quotes                don't quote file names with spaces
+  --hyperlink                display entries as hyperlinks
+  -w, --width COLS           set screen width in columns
+  --smart-group              only show group if it has a different name from owner
 
 
 
 
 FILTERING AND SORTING OPTIONS
 FILTERING AND SORTING OPTIONS
-  -a, --all                  show hidden and 'dot' files. Use this twice to also show the '.' and '..' directories
+  -a, --all                  show hidden and 'dot' files. Use this twice to also
+                             show the '.' and '..' directories
   -A, --almost-all           equivalent to --all; included for compatibility with `ls -A`
   -A, --almost-all           equivalent to --all; included for compatibility with `ls -A`
   -d, --list-dirs            list directories as files; don't list their contents
   -d, --list-dirs            list directories as files; don't list their contents
   -L, --level DEPTH          limit the depth of recursion
   -L, --level DEPTH          limit the depth of recursion
@@ -50,36 +52,40 @@ static USAGE_PART2: &str = "  \
                              date, time, old, and new all refer to modified.
                              date, time, old, and new all refer to modified.
 
 
 LONG VIEW OPTIONS
 LONG VIEW OPTIONS
-  -b, --binary             list file sizes with binary prefixes
-  -B, --bytes              list file sizes in bytes, without any prefixes
-  -g, --group              list each file's group
-  -h, --header             add a header row to each column
-  -H, --links              list each file's number of hard links
-  -i, --inode              list each file's inode number
-  -m, --modified           use the modified timestamp field
-  -M, --mounts             show mount details (Linux and MacOS only)
-  -n, --numeric            list numeric user and group IDs
-  -S, --blocksize          show size of allocated file system blocks
-  -t, --time FIELD         which timestamp field to list (modified, accessed, created)
-  -u, --accessed           use the accessed timestamp field
-  -U, --created            use the created timestamp field
-  --changed                use the changed timestamp field
-  --time-style             how to format timestamps (default, iso, long-iso, full-iso, relative, or a custom style with '+' as prefix. Ex: '+%Y/%m/%d')
-  --total-size             show the size of a directory as the size of all files and directories inside
-  --no-permissions         suppress the permissions field
-  -o, --octal-permissions  list each file's permission in octal format
-  --no-filesize            suppress the filesize field
-  --no-user                suppress the user field
-  --no-time                suppress the time field";
-
-static GIT_VIEW_HELP:   &str = "  \
-  --git                    list each file's Git status, if tracked or ignored
-  --no-git                 suppress Git status (always overrides --git, --git-repos, --git-repos-no-status)
-  --git-repos              list root of git-tree status";
+  -b, --binary               list file sizes with binary prefixes
+  -B, --bytes                list file sizes in bytes, without any prefixes
+  -g, --group                list each file's group
+  -h, --header               add a header row to each column
+  -H, --links                list each file's number of hard links
+  -i, --inode                list each file's inode number
+  -m, --modified             use the modified timestamp field
+  -M, --mounts               show mount details (Linux and MacOS only)
+  -n, --numeric              list numeric user and group IDs
+  -S, --blocksize            show size of allocated file system blocks
+  -t, --time FIELD           which timestamp field to list (modified, accessed, created)
+  -u, --accessed             use the accessed timestamp field
+  -U, --created              use the created timestamp field
+  --changed                  use the changed timestamp field
+  --time-style               how to format timestamps (default, iso, long-iso,
+                             full-iso, relative, or a custom style with '+' as
+                             prefix. Ex: '+%Y/%m/%d')
+  --total-size               show the size of a directory as the size of all
+                             files and directories inside
+  --no-permissions           suppress the permissions field
+  -o, --octal-permissions    list each file's permission in octal format
+  --no-filesize              suppress the filesize field
+  --no-user                  suppress the user field
+  --no-time                  suppress the time field";
+
+static GIT_VIEW_HELP: &str = "  \
+  --git                      list each file's Git status, if tracked or ignored
+  --no-git                   suppress Git status (always overrides --git,
+                             --git-repos, --git-repos-no-status)
+  --git-repos                list root of git-tree status";
 static EXTENDED_HELP: &str = "  \
 static EXTENDED_HELP: &str = "  \
-  -@, --extended           list each file's extended attributes and sizes";
+  -@, --extended             list each file's extended attributes and sizes";
 static SECATTR_HELP: &str = "  \
 static SECATTR_HELP: &str = "  \
-  -Z, --context            list each file's security context";
+  -Z, --context              list each file's security context";
 
 
 /// All the information needed to display the help text, which depends
 /// All the information needed to display the help text, which depends
 /// on which features are enabled and whether the user only wants to
 /// on which features are enabled and whether the user only wants to

+ 57 - 17
src/options/parser.rs

@@ -99,7 +99,7 @@ pub enum TakesValue {
     Forbidden,
     Forbidden,
 
 
     /// This flag may be followed by a value to override its defaults
     /// This flag may be followed by a value to override its defaults
-    Optional(Option<Values>),
+    Optional(Option<Values>, &'static str),
 }
 }
 
 
 /// An **argument** can be matched by one of the user’s input strings.
 /// An **argument** can be matched by one of the user’s input strings.
@@ -176,7 +176,7 @@ impl Args {
                     let arg = self.lookup_long(before)?;
                     let arg = self.lookup_long(before)?;
                     let flag = Flag::Long(arg.long);
                     let flag = Flag::Long(arg.long);
                     match arg.takes_value {
                     match arg.takes_value {
-                        TakesValue::Necessary(_) | TakesValue::Optional(_) => {
+                        TakesValue::Necessary(_) | TakesValue::Optional(_, _) => {
                             result_flags.push((flag, Some(after)));
                             result_flags.push((flag, Some(after)));
                         }
                         }
                         TakesValue::Forbidden => return Err(ParseError::ForbiddenValue { flag }),
                         TakesValue::Forbidden => return Err(ParseError::ForbiddenValue { flag }),
@@ -198,11 +198,14 @@ impl Args {
                                 return Err(ParseError::NeedsValue { flag, values });
                                 return Err(ParseError::NeedsValue { flag, values });
                             }
                             }
                         }
                         }
-                        TakesValue::Optional(_) => match inputs.peek() {
-                            Some(next_arg) if is_optional_arg(next_arg) => {
+                        TakesValue::Optional(values, default) => match inputs.peek() {
+                            Some(next_arg) if is_optional_arg(next_arg, values) => {
                                 result_flags.push((flag, Some(inputs.next().unwrap())));
                                 result_flags.push((flag, Some(inputs.next().unwrap())));
                             }
                             }
-                            _ => result_flags.push((flag, None)),
+                            _ => {
+                                result_flags
+                                    .push((flag, Some(bytes_to_os_str(default.as_bytes()))));
+                            }
                         },
                         },
                     }
                     }
                 }
                 }
@@ -233,9 +236,13 @@ impl Args {
                         let arg = self.lookup_short(*byte)?;
                         let arg = self.lookup_short(*byte)?;
                         let flag = Flag::Short(*byte);
                         let flag = Flag::Short(*byte);
                         match arg.takes_value {
                         match arg.takes_value {
-                            TakesValue::Forbidden | TakesValue::Optional(_) => {
+                            TakesValue::Forbidden => {
                                 result_flags.push((flag, None));
                                 result_flags.push((flag, None));
                             }
                             }
+                            TakesValue::Optional(_, default) => {
+                                result_flags
+                                    .push((flag, Some(bytes_to_os_str(default.as_bytes()))));
+                            }
                             TakesValue::Necessary(values) => {
                             TakesValue::Necessary(values) => {
                                 return Err(ParseError::NeedsValue { flag, values });
                                 return Err(ParseError::NeedsValue { flag, values });
                             }
                             }
@@ -246,7 +253,7 @@ impl Args {
                     let arg = self.lookup_short(*arg_with_value)?;
                     let arg = self.lookup_short(*arg_with_value)?;
                     let flag = Flag::Short(arg.short.unwrap());
                     let flag = Flag::Short(arg.short.unwrap());
                     match arg.takes_value {
                     match arg.takes_value {
-                        TakesValue::Necessary(_) | TakesValue::Optional(_) => {
+                        TakesValue::Necessary(_) | TakesValue::Optional(_, _) => {
                             result_flags.push((flag, Some(after)));
                             result_flags.push((flag, Some(after)));
                         }
                         }
                         TakesValue::Forbidden => {
                         TakesValue::Forbidden => {
@@ -274,7 +281,7 @@ impl Args {
                             TakesValue::Forbidden => {
                             TakesValue::Forbidden => {
                                 result_flags.push((flag, None));
                                 result_flags.push((flag, None));
                             }
                             }
-                            TakesValue::Necessary(values) | TakesValue::Optional(values) => {
+                            TakesValue::Necessary(values) => {
                                 if index < bytes.len() - 1 {
                                 if index < bytes.len() - 1 {
                                     let remnants = &bytes[index + 1..];
                                     let remnants = &bytes[index + 1..];
                                     result_flags.push((flag, Some(bytes_to_os_str(remnants))));
                                     result_flags.push((flag, Some(bytes_to_os_str(remnants))));
@@ -283,14 +290,37 @@ impl Args {
                                     result_flags.push((flag, Some(next_arg)));
                                     result_flags.push((flag, Some(next_arg)));
                                 } else {
                                 } else {
                                     match arg.takes_value {
                                     match arg.takes_value {
-                                        TakesValue::Forbidden => {
+                                        TakesValue::Forbidden | TakesValue::Optional(_, _) => {
                                             unreachable!()
                                             unreachable!()
                                         }
                                         }
                                         TakesValue::Necessary(_) => {
                                         TakesValue::Necessary(_) => {
                                             return Err(ParseError::NeedsValue { flag, values });
                                             return Err(ParseError::NeedsValue { flag, values });
                                         }
                                         }
-                                        TakesValue::Optional(_) => {
-                                            result_flags.push((flag, None));
+                                    }
+                                }
+                            }
+                            TakesValue::Optional(values, default) => {
+                                if index < bytes.len() - 1 {
+                                    let remnants = bytes_to_os_str(&bytes[index + 1..]);
+                                    if is_optional_arg(remnants, values) {
+                                        result_flags.push((flag, Some(remnants)));
+                                    } else {
+                                        return Err(ParseError::ForbiddenValue { flag });
+                                    }
+                                    break;
+                                } else if let Some(next_arg) = inputs.peek() {
+                                    if is_optional_arg(next_arg, values) {
+                                        result_flags.push((flag, Some(inputs.next().unwrap())));
+                                    } else {
+                                        result_flags.push((flag, Some(OsStr::new(default))));
+                                    }
+                                } else {
+                                    match arg.takes_value {
+                                        TakesValue::Forbidden | TakesValue::Necessary(_) => {
+                                            unreachable!()
+                                        }
+                                        TakesValue::Optional(_, default) => {
+                                            result_flags.push((flag, Some(OsStr::new(default))));
                                         }
                                         }
                                     }
                                     }
                                 }
                                 }
@@ -331,11 +361,9 @@ 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,
+fn is_optional_arg(value: &OsStr, values: Option<&[&str]>) -> bool {
+    match (values, value.to_str()) {
+        (Some(values), Some(value)) => values.contains(&value),
         _ => false,
         _ => false,
     }
     }
 }
 }
@@ -639,7 +667,8 @@ mod parse_test {
         &Arg { short: Some(b'l'), long: "long",     takes_value: TakesValue::Forbidden },
         &Arg { short: Some(b'l'), long: "long",     takes_value: TakesValue::Forbidden },
         &Arg { short: Some(b'v'), long: "verbose",  takes_value: TakesValue::Forbidden },
         &Arg { short: Some(b'v'), long: "verbose",  takes_value: TakesValue::Forbidden },
         &Arg { short: Some(b'c'), long: "count",    takes_value: TakesValue::Necessary(None) },
         &Arg { short: Some(b'c'), long: "count",    takes_value: TakesValue::Necessary(None) },
-        &Arg { short: Some(b't'), long: "type",     takes_value: TakesValue::Necessary(Some(SUGGESTIONS)) }
+        &Arg { short: Some(b't'), long: "type",     takes_value: TakesValue::Necessary(Some(SUGGESTIONS))},
+        &Arg { short: Some(b'o'), long: "optional", takes_value: TakesValue::Optional(Some(&["all", "some", "none"]), "all")} 
     ];
     ];
 
 
     // Just filenames
     // Just filenames
@@ -697,6 +726,17 @@ mod parse_test {
     test!(unknown_short_2nd:     ["-lq"]          => error UnknownShortArgument { attempt: b'q' });
     test!(unknown_short_2nd:     ["-lq"]          => error UnknownShortArgument { attempt: b'q' });
     test!(unknown_short_eq:      ["-q=shhh"]      => error UnknownShortArgument { attempt: b'q' });
     test!(unknown_short_eq:      ["-q=shhh"]      => error UnknownShortArgument { attempt: b'q' });
     test!(unknown_short_2nd_eq:  ["-lq=shhh"]     => error UnknownShortArgument { attempt: b'q' });
     test!(unknown_short_2nd_eq:  ["-lq=shhh"]     => error UnknownShortArgument { attempt: b'q' });
+
+    // Optional args
+    test!(optional:         ["--optional"]         => frees: [], flags: [(Flag::Long("optional"), Some(OsStr::new("all")))]);
+    test!(optional_2:       ["--optional", "-l"]   => frees: [], flags: [ (Flag::Long("optional"), Some(OsStr::new("all"))), (Flag::Short(b'l'), None)]);
+    test!(optional_3:       ["--optional", "path"] => frees: ["path"], flags: [(Flag::Long("optional"), Some(OsStr::new("all")))]);
+    test!(optional_with_eq: ["--optional=none"]    => frees: [], flags: [(Flag::Long("optional"), Some(OsStr::new("none")))]);
+    test!(optional_wo_eq:   ["--optional", "none"] => frees: [], flags: [(Flag::Long("optional"), Some(OsStr::new("none")))]);
+    test!(short_opt:        ["-o"]                 => frees: [], flags: [(Flag::Short(b'o'), Some(OsStr::new("all")))]);
+    test!(short_opt_value:  ["-onone"]             => frees: [], flags: [(Flag::Short(b'o'), Some(OsStr::new("none")))]);
+    test!(short_forbidden:  ["-opath"]             => error ForbiddenValue  { flag: Flag::Short(b'o') });
+    test!(short_allowed:    ["-o","path"]          => frees: ["path"], flags: [(Flag::Short(b'o'), Some(OsStr::new("all")))]);
 }
 }
 
 
 #[cfg(test)]
 #[cfg(test)]

+ 4 - 25
src/options/theme.rs

@@ -1,11 +1,12 @@
 use crate::options::parser::MatchedFlags;
 use crate::options::parser::MatchedFlags;
 use crate::options::{flags, vars, OptionsError, Vars};
 use crate::options::{flags, vars, OptionsError, Vars};
-use crate::theme::{ColourScale, Definitions, Options, UseColours};
+use crate::output::color_scale::ColorScaleOptions;
+use crate::theme::{Definitions, Options, UseColours};
 
 
 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) -> Result<Self, OptionsError> {
         let use_colours = UseColours::deduce(matches, vars)?;
         let use_colours = UseColours::deduce(matches, vars)?;
-        let colour_scale = ColourScale::deduce(matches)?;
+        let colour_scale = ColorScaleOptions::deduce(matches, vars)?;
 
 
         let definitions = if use_colours == UseColours::Never {
         let definitions = if use_colours == UseColours::Never {
             Definitions::default()
             Definitions::default()
@@ -46,19 +47,6 @@ impl UseColours {
     }
     }
 }
 }
 
 
-impl ColourScale {
-    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
-        if matches
-            .has_where(|f| f.matches(&flags::COLOR_SCALE) || f.matches(&flags::COLOUR_SCALE))?
-            .is_some()
-        {
-            Ok(Self::Gradient)
-        } else {
-            Ok(Self::Fixed)
-        }
-    }
-}
-
 impl Definitions {
 impl Definitions {
     fn deduce<V: Vars>(vars: &V) -> Self {
     fn deduce<V: Vars>(vars: &V) -> Self {
         let ls = vars
         let ls = vars
@@ -88,6 +76,7 @@ mod terminal_test {
         &flags::COLOUR_SCALE,
         &flags::COLOUR_SCALE,
     ];
     ];
 
 
+    #[allow(unused_macro_rules)]
     macro_rules! test {
     macro_rules! test {
         ($name:ident:  $type:ident <- $inputs:expr;  $stricts:expr => $result:expr) => {
         ($name:ident:  $type:ident <- $inputs:expr;  $stricts:expr => $result:expr) => {
             #[test]
             #[test]
@@ -203,14 +192,4 @@ mod terminal_test {
     test!(overridden_6:  UseColours <- ["--color=auto",  "--colour=never"], MockVars::empty();  Complain => err OptionsError::Duplicate(Flag::Long("color"),  Flag::Long("colour")));
     test!(overridden_6:  UseColours <- ["--color=auto",  "--colour=never"], MockVars::empty();  Complain => err OptionsError::Duplicate(Flag::Long("color"),  Flag::Long("colour")));
     test!(overridden_7:  UseColours <- ["--colour=auto", "--color=never"], MockVars::empty();   Complain => err OptionsError::Duplicate(Flag::Long("colour"), Flag::Long("color")));
     test!(overridden_7:  UseColours <- ["--colour=auto", "--color=never"], MockVars::empty();   Complain => err OptionsError::Duplicate(Flag::Long("colour"), Flag::Long("color")));
     test!(overridden_8:  UseColours <- ["--color=auto",  "--color=never"], MockVars::empty();   Complain => err OptionsError::Duplicate(Flag::Long("color"),  Flag::Long("color")));
     test!(overridden_8:  UseColours <- ["--color=auto",  "--color=never"], MockVars::empty();   Complain => err OptionsError::Duplicate(Flag::Long("color"),  Flag::Long("color")));
-
-    test!(scale_1:  ColourScale <- ["--color-scale", "--colour-scale"];   Last => Ok(ColourScale::Gradient));
-    test!(scale_2:  ColourScale <- ["--color-scale",                 ];   Last => Ok(ColourScale::Gradient));
-    test!(scale_3:  ColourScale <- [                 "--colour-scale"];   Last => Ok(ColourScale::Gradient));
-    test!(scale_4:  ColourScale <- [                                 ];   Last => Ok(ColourScale::Fixed));
-
-    test!(scale_5:  ColourScale <- ["--color-scale", "--colour-scale"];   Complain => err OptionsError::Duplicate(Flag::Long("color-scale"),  Flag::Long("colour-scale")));
-    test!(scale_6:  ColourScale <- ["--color-scale",                 ];   Complain => Ok(ColourScale::Gradient));
-    test!(scale_7:  ColourScale <- [                 "--colour-scale"];   Complain => Ok(ColourScale::Gradient));
-    test!(scale_8:  ColourScale <- [                                 ];   Complain => Ok(ColourScale::Fixed));
 }
 }

+ 5 - 0
src/options/vars.rs

@@ -55,6 +55,11 @@ pub static EZA_ICON_SPACING: &str = "EZA_ICON_SPACING";
 pub static EXA_OVERRIDE_GIT: &str = "EXA_OVERRIDE_GIT";
 pub static EXA_OVERRIDE_GIT: &str = "EXA_OVERRIDE_GIT";
 pub static EZA_OVERRIDE_GIT: &str = "EZA_OVERRIDE_GIT";
 pub static EZA_OVERRIDE_GIT: &str = "EZA_OVERRIDE_GIT";
 
 
+/// Enviroment variable used to set the minimum luminance in `color_scale`. It's value
+/// can be between -100 and 100
+pub static EXA_MIN_LUMINANCE: &str = "EXA_MIN_LUMINANCE";
+pub static EZA_MIN_LUMINANCE: &str = "EZA_MIN_LUMINANCE";
+
 /// Environment variable used to automate the same behavior as `--icons=auto` if set.
 /// Environment variable used to automate the same behavior as `--icons=auto` if set.
 /// Any explicit use of `--icons=WHEN` overrides this behavior.
 /// Any explicit use of `--icons=WHEN` overrides this behavior.
 pub static EZA_ICONS_AUTO: &str = "EZA_ICONS_AUTO";
 pub static EZA_ICONS_AUTO: &str = "EZA_ICONS_AUTO";

+ 70 - 9
src/options/view.rs

@@ -1,6 +1,9 @@
+use std::ffi::OsString;
+
 use crate::fs::feature::xattr;
 use crate::fs::feature::xattr;
 use crate::options::parser::MatchedFlags;
 use crate::options::parser::MatchedFlags;
-use crate::options::{flags, NumberSource, OptionsError, Vars};
+use crate::options::{flags, vars, NumberSource, OptionsError, Vars};
+use crate::output::color_scale::{ColorScaleMode, ColorScaleOptions};
 use crate::output::file_name::Options as FileStyle;
 use crate::output::file_name::Options as FileStyle;
 use crate::output::grid_details::{self, RowThreshold};
 use crate::output::grid_details::{self, RowThreshold};
 use crate::output::table::{
 use crate::output::table::{
@@ -79,7 +82,7 @@ impl Mode {
 
 
         if flag.matches(&flags::TREE) {
         if flag.matches(&flags::TREE) {
             let _ = matches.has(&flags::TREE)?;
             let _ = matches.has(&flags::TREE)?;
-            let details = details::Options::deduce_tree(matches)?;
+            let details = details::Options::deduce_tree(matches, vars)?;
             return Ok(Self::Details(details));
             return Ok(Self::Details(details));
         }
         }
 
 
@@ -142,13 +145,14 @@ impl grid::Options {
 }
 }
 
 
 impl details::Options {
 impl details::Options {
-    fn deduce_tree(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
+    fn deduce_tree<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
         let details = details::Options {
         let details = details::Options {
             table: None,
             table: None,
             header: false,
             header: false,
             xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
             xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
             secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?,
             secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?,
             mounts: matches.has(&flags::MOUNTS)?,
             mounts: matches.has(&flags::MOUNTS)?,
+            color_scale: ColorScaleOptions::deduce(matches, vars)?,
         };
         };
 
 
         Ok(details)
         Ok(details)
@@ -169,14 +173,13 @@ impl details::Options {
             xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
             xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
             secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?,
             secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?,
             mounts: matches.has(&flags::MOUNTS)?,
             mounts: matches.has(&flags::MOUNTS)?,
+            color_scale: ColorScaleOptions::deduce(matches, vars)?,
         })
         })
     }
     }
 }
 }
 
 
 impl TerminalWidth {
 impl TerminalWidth {
     fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
     fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
-        use crate::options::vars;
-
         if let Some(width) = matches.get(&flags::WIDTH)? {
         if let Some(width) = matches.get(&flags::WIDTH)? {
             let arg_str = width.to_string_lossy();
             let arg_str = width.to_string_lossy();
             match arg_str.parse() {
             match arg_str.parse() {
@@ -208,8 +211,6 @@ impl TerminalWidth {
 
 
 impl RowThreshold {
 impl RowThreshold {
     fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {
     fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {
-        use crate::options::vars;
-
         if let Some(columns) = vars
         if let Some(columns) = vars
             .get_with_fallback(vars::EZA_GRID_ROWS, vars::EXA_GRID_ROWS)
             .get_with_fallback(vars::EZA_GRID_ROWS, vars::EXA_GRID_ROWS)
             .and_then(|s| s.into_string().ok())
             .and_then(|s| s.into_string().ok())
@@ -249,7 +250,6 @@ impl TableOptions {
 
 
 impl Columns {
 impl Columns {
     fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
     fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
-        use crate::options::vars;
         let time_types = TimeTypes::deduce(matches)?;
         let time_types = TimeTypes::deduce(matches)?;
 
 
         let no_git_env = vars
         let no_git_env = vars
@@ -319,7 +319,6 @@ impl TimeFormat {
         let word = if let Some(w) = matches.get(&flags::TIME_STYLE)? {
         let word = if let Some(w) = matches.get(&flags::TIME_STYLE)? {
             w.to_os_string()
             w.to_os_string()
         } else {
         } else {
-            use crate::options::vars;
             match vars.get(vars::TIME_STYLE) {
             match vars.get(vars::TIME_STYLE) {
                 Some(ref t) if !t.is_empty() => t.clone(),
                 Some(ref t) if !t.is_empty() => t.clone(),
                 _ => return Ok(Self::DefaultFormat),
                 _ => return Ok(Self::DefaultFormat),
@@ -417,6 +416,68 @@ impl TimeTypes {
     }
     }
 }
 }
 
 
+impl ColorScaleOptions {
+    pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
+        let min_luminance =
+            match vars.get_with_fallback(vars::EZA_MIN_LUMINANCE, vars::EXA_MIN_LUMINANCE) {
+                Some(var) => match var.to_string_lossy().parse() {
+                    Ok(luminance) if (-100..=100).contains(&luminance) => luminance,
+                    _ => 40,
+                },
+                None => 40,
+            };
+
+        let mode = if let Some(w) = matches
+            .get(&flags::COLOR_SCALE_MODE)?
+            .or(matches.get(&flags::COLOUR_SCALE_MODE)?)
+        {
+            match w.to_str() {
+                Some("fixed") => ColorScaleMode::Fixed,
+                Some("gradient") => ColorScaleMode::Gradient,
+                _ => Err(OptionsError::BadArgument(
+                    &flags::COLOR_SCALE_MODE,
+                    w.to_os_string(),
+                ))?,
+            }
+        } else {
+            ColorScaleMode::Gradient
+        };
+
+        let mut options = ColorScaleOptions {
+            mode,
+            min_luminance,
+            size: false,
+            age: false,
+        };
+
+        let words = if let Some(w) = matches
+            .get(&flags::COLOR_SCALE)?
+            .or(matches.get(&flags::COLOUR_SCALE)?)
+        {
+            w.to_os_string()
+        } else {
+            return Ok(options);
+        };
+
+        for word in words.to_string_lossy().split(',') {
+            match word {
+                "all" => {
+                    options.size = true;
+                    options.age = true;
+                }
+                "age" => options.age = true,
+                "size" => options.size = true,
+                _ => Err(OptionsError::BadArgument(
+                    &flags::COLOR_SCALE,
+                    OsString::from(word),
+                ))?,
+            };
+        }
+
+        Ok(options)
+    }
+}
+
 #[cfg(test)]
 #[cfg(test)]
 mod test {
 mod test {
     use super::*;
     use super::*;

+ 206 - 0
src/output/color_scale.rs

@@ -0,0 +1,206 @@
+use ansiterm::{Colour, Style};
+use palette::{FromColor, Oklab, Srgb};
+
+use crate::{
+    fs::{dir_action::RecurseOptions, feature::git::GitCache, fields::Size, DotFilter, File},
+    output::{table::TimeType, tree::TreeDepth},
+};
+
+#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+pub struct ColorScaleOptions {
+    pub mode: ColorScaleMode,
+    pub min_luminance: isize,
+
+    pub size: bool,
+    pub age: bool,
+}
+
+#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+pub enum ColorScaleMode {
+    Fixed,
+    Gradient,
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct ColorScaleInformation {
+    pub options: ColorScaleOptions,
+
+    pub accessed: Option<Extremes>,
+    pub changed: Option<Extremes>,
+    pub created: Option<Extremes>,
+    pub modified: Option<Extremes>,
+
+    pub size: Option<Extremes>,
+}
+
+impl ColorScaleInformation {
+    pub fn from_color_scale(
+        color_scale: ColorScaleOptions,
+        files: &[File<'_>],
+        dot_filter: DotFilter,
+        git: Option<&GitCache>,
+        git_ignoring: bool,
+        r: Option<RecurseOptions>,
+    ) -> Option<Self> {
+        if color_scale.mode == ColorScaleMode::Fixed {
+            None
+        } else {
+            let mut information = Self {
+                options: color_scale,
+                accessed: None,
+                changed: None,
+                created: None,
+                modified: None,
+                size: None,
+            };
+
+            update_information_recursively(
+                &mut information,
+                files,
+                dot_filter,
+                git,
+                git_ignoring,
+                TreeDepth::root(),
+                r,
+            );
+
+            Some(information)
+        }
+    }
+
+    pub fn adjust_style(&self, mut style: Style, value: f32, range: Option<Extremes>) -> Style {
+        if let (Some(fg), Some(range)) = (style.foreground, range) {
+            let mut ratio = ((value - range.min) / (range.max - range.min)).clamp(0.0, 1.0);
+            if ratio.is_nan() {
+                ratio = 1.0;
+            }
+
+            style.foreground = Some(adjust_luminance(
+                fg,
+                ratio,
+                self.options.min_luminance as f32 / 100.0,
+            ));
+        }
+
+        style
+    }
+
+    pub fn apply_time_gradient(&self, style: Style, file: &File<'_>, time_type: TimeType) -> Style {
+        let range = match time_type {
+            TimeType::Modified => self.modified,
+            TimeType::Changed => self.changed,
+            TimeType::Accessed => self.accessed,
+            TimeType::Created => self.created,
+        };
+
+        if let Some(file_time) = time_type.get_corresponding_time(file) {
+            self.adjust_style(style, file_time.timestamp_millis() as f32, range)
+        } else {
+            style
+        }
+    }
+}
+
+fn update_information_recursively(
+    information: &mut ColorScaleInformation,
+    files: &[File<'_>],
+    dot_filter: DotFilter,
+    git: Option<&GitCache>,
+    git_ignoring: bool,
+    depth: TreeDepth,
+    r: Option<RecurseOptions>,
+) {
+    for file in files {
+        if information.options.age {
+            Extremes::update(
+                file.created_time().map(|x| x.timestamp_millis() as f32),
+                &mut information.created,
+            );
+            Extremes::update(
+                file.modified_time().map(|x| x.timestamp_millis() as f32),
+                &mut information.modified,
+            );
+            Extremes::update(
+                file.accessed_time().map(|x| x.timestamp_millis() as f32),
+                &mut information.accessed,
+            );
+            Extremes::update(
+                file.changed_time().map(|x| x.timestamp_millis() as f32),
+                &mut information.changed,
+            );
+        }
+
+        if information.options.size {
+            let size = match file.size() {
+                Size::Some(size) => Some(size as f32),
+                _ => None,
+            };
+            Extremes::update(size, &mut information.size);
+        }
+
+        if file.is_directory() && r.is_some_and(|x| !x.is_too_deep(depth.0)) {
+            match file.to_dir() {
+                Ok(dir) => {
+                    let files: Vec<File<'_>> = dir
+                        .files(dot_filter, git, git_ignoring, false, false)
+                        .flatten()
+                        .collect();
+
+                    update_information_recursively(
+                        information,
+                        &files,
+                        dot_filter,
+                        git,
+                        git_ignoring,
+                        depth.deeper(),
+                        r,
+                    );
+                }
+                Err(_) => todo!(),
+            }
+        };
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct Extremes {
+    max: f32,
+    min: f32,
+}
+
+impl Extremes {
+    fn update(maybe_value: Option<f32>, maybe_range: &mut Option<Extremes>) {
+        match (maybe_value, maybe_range) {
+            (Some(value), Some(range)) => {
+                if value > range.max {
+                    range.max = value;
+                } else if value < range.min {
+                    range.min = value;
+                };
+            }
+            (Some(value), rel) => {
+                let _ = rel.insert({
+                    Extremes {
+                        max: value,
+                        min: value,
+                    }
+                });
+            }
+            _ => (),
+        };
+    }
+}
+
+fn adjust_luminance(color: Colour, x: f32, min_l: f32) -> Colour {
+    let color = Srgb::from_components(color.into_rgb()).into_linear();
+
+    let mut lab: Oklab = Oklab::from_color(color);
+    lab.l = (min_l + (1.0 - min_l) * (-4.0 * (1.0 - x)).exp()).clamp(0.0, 1.0);
+
+    let adjusted_rgb: Srgb<f32> = Srgb::from_color(lab);
+    Colour::RGB(
+        (adjusted_rgb.red * 255.0).round() as u8,
+        (adjusted_rgb.green * 255.0).round() as u8,
+        (adjusted_rgb.blue * 255.0).round() as u8,
+    )
+}

+ 26 - 4
src/output/details.rs

@@ -76,6 +76,7 @@ use crate::fs::fields::SecurityContextType;
 use crate::fs::filter::FileFilter;
 use crate::fs::filter::FileFilter;
 use crate::fs::{Dir, File};
 use crate::fs::{Dir, File};
 use crate::output::cell::TextCell;
 use crate::output::cell::TextCell;
+use crate::output::color_scale::{ColorScaleInformation, ColorScaleOptions};
 use crate::output::file_name::Options as FileStyle;
 use crate::output::file_name::Options as FileStyle;
 use crate::output::table::{Options as TableOptions, Row as TableRow, Table};
 use crate::output::table::{Options as TableOptions, Row as TableRow, Table};
 use crate::output::tree::{TreeDepth, TreeParams, TreeTrunk};
 use crate::output::tree::{TreeDepth, TreeParams, TreeTrunk};
@@ -113,6 +114,8 @@ pub struct Options {
 
 
     /// Whether to show a directory's mounted filesystem details
     /// Whether to show a directory's mounted filesystem details
     pub mounts: bool,
     pub mounts: bool,
+
+    pub color_scale: ColorScaleOptions,
 }
 }
 
 
 pub struct Render<'a> {
 pub struct Render<'a> {
@@ -162,6 +165,15 @@ impl<'a> Render<'a> {
         let mut pool = Pool::new(n_cpus);
         let mut pool = Pool::new(n_cpus);
         let mut rows = Vec::new();
         let mut rows = Vec::new();
 
 
+        let color_scale_info = ColorScaleInformation::from_color_scale(
+            self.opts.color_scale,
+            &self.files,
+            self.filter.dot_filter,
+            self.git,
+            self.git_ignoring,
+            self.recurse,
+        );
+
         if let Some(ref table) = self.opts.table {
         if let Some(ref table) = self.opts.table {
             match (self.git, self.dir) {
             match (self.git, self.dir) {
                 (Some(g), Some(d)) => {
                 (Some(g), Some(d)) => {
@@ -194,6 +206,7 @@ impl<'a> Render<'a> {
                 &mut rows,
                 &mut rows,
                 &self.files,
                 &self.files,
                 TreeDepth::root(),
                 TreeDepth::root(),
+                color_scale_info,
             );
             );
 
 
             for row in self.iterate_with_table(table.unwrap(), rows) {
             for row in self.iterate_with_table(table.unwrap(), rows) {
@@ -206,6 +219,7 @@ impl<'a> Render<'a> {
                 &mut rows,
                 &mut rows,
                 &self.files,
                 &self.files,
                 TreeDepth::root(),
                 TreeDepth::root(),
+                color_scale_info,
             );
             );
 
 
             for row in self.iterate(rows) {
             for row in self.iterate(rows) {
@@ -238,6 +252,7 @@ impl<'a> Render<'a> {
         rows: &mut Vec<Row>,
         rows: &mut Vec<Row>,
         src: &[File<'dir>],
         src: &[File<'dir>],
         depth: TreeDepth,
         depth: TreeDepth,
+        color_scale_info: Option<ColorScaleInformation>,
     ) {
     ) {
         use crate::fs::feature::xattr;
         use crate::fs::feature::xattr;
         use std::sync::{Arc, Mutex};
         use std::sync::{Arc, Mutex};
@@ -284,9 +299,9 @@ impl<'a> Render<'a> {
                         &[]
                         &[]
                     };
                     };
 
 
-                    let table_row = table
-                        .as_ref()
-                        .map(|t| t.row_for_file(file, self.show_xattr_hint(file)));
+                    let table_row = table.as_ref().map(|t| {
+                        t.row_for_file(file, self.show_xattr_hint(file), color_scale_info)
+                    });
 
 
                     let mut dir = None;
                     let mut dir = None;
                     if let Some(r) = self.recurse {
                     if let Some(r) = self.recurse {
@@ -376,7 +391,14 @@ impl<'a> Render<'a> {
                         ));
                         ));
                     }
                     }
 
 
-                    self.add_files_to_table(pool, table, rows, &files, depth.deeper());
+                    self.add_files_to_table(
+                        pool,
+                        table,
+                        rows,
+                        &files,
+                        depth.deeper(),
+                        color_scale_info,
+                    );
                     continue;
                     continue;
                 }
                 }
             }
             }

+ 13 - 1
src/output/grid_details.rs

@@ -9,6 +9,7 @@ use crate::fs::feature::git::GitCache;
 use crate::fs::filter::FileFilter;
 use crate::fs::filter::FileFilter;
 use crate::fs::{Dir, File};
 use crate::fs::{Dir, File};
 use crate::output::cell::{DisplayWidth, TextCell};
 use crate::output::cell::{DisplayWidth, TextCell};
+use crate::output::color_scale::ColorScaleInformation;
 use crate::output::details::{
 use crate::output::details::{
     Options as DetailsOptions, Render as DetailsRender, Row as DetailsRow,
     Options as DetailsOptions, Render as DetailsRender, Row as DetailsRow,
 };
 };
@@ -154,12 +155,23 @@ impl<'a> Render<'a> {
 
 
         let drender = self.details_for_column();
         let drender = self.details_for_column();
 
 
+        let color_scale_info = ColorScaleInformation::from_color_scale(
+            self.details.color_scale,
+            &self.files,
+            self.filter.dot_filter,
+            self.git,
+            self.git_ignoring,
+            None,
+        );
+
         let (first_table, _) = self.make_table(options, &drender);
         let (first_table, _) = self.make_table(options, &drender);
 
 
         let rows = self
         let rows = self
             .files
             .files
             .iter()
             .iter()
-            .map(|file| first_table.row_for_file(file, drender.show_xattr_hint(file)))
+            .map(|file| {
+                first_table.row_for_file(file, drender.show_xattr_hint(file), color_scale_info)
+            })
             .collect::<Vec<_>>();
             .collect::<Vec<_>>();
 
 
         let file_names = self
         let file_names = self

+ 1 - 0
src/output/icons.rs

@@ -602,6 +602,7 @@ const EXTENSION_ICONS: Map<&'static str, char> = phf_map! {
     "ppt"            => Icons::SLIDE,            // 
     "ppt"            => Icons::SLIDE,            // 
     "pptx"           => Icons::SLIDE,            // 
     "pptx"           => Icons::SLIDE,            // 
     "properties"     => Icons::JSON,             // 
     "properties"     => Icons::JSON,             // 
+    "prql"           => Icons::DATABASE,         // 
     "ps"             => Icons::VECTOR,           // 󰕙
     "ps"             => Icons::VECTOR,           // 󰕙
     "ps1"            => Icons::POWERSHELL,       // 
     "ps1"            => Icons::POWERSHELL,       // 
     "psd"            => '\u{e7b8}',              // 
     "psd"            => '\u{e7b8}',              // 

+ 1 - 0
src/output/mod.rs

@@ -1,6 +1,7 @@
 pub use self::cell::{DisplayWidth, TextCell, TextCellContents};
 pub use self::cell::{DisplayWidth, TextCell, TextCellContents};
 pub use self::escape::escape;
 pub use self::escape::escape;
 
 
+pub mod color_scale;
 pub mod details;
 pub mod details;
 pub mod file_name;
 pub mod file_name;
 pub mod grid;
 pub mod grid;

+ 52 - 14
src/output/render/size.rs

@@ -4,6 +4,7 @@ use number_prefix::Prefix;
 
 
 use crate::fs::fields as f;
 use crate::fs::fields as f;
 use crate::output::cell::{DisplayWidth, TextCell};
 use crate::output::cell::{DisplayWidth, TextCell};
+use crate::output::color_scale::{ColorScaleInformation, ColorScaleMode};
 use crate::output::table::SizeFormat;
 use crate::output::table::SizeFormat;
 
 
 impl f::Size {
 impl f::Size {
@@ -12,6 +13,7 @@ impl f::Size {
         colours: &C,
         colours: &C,
         size_format: SizeFormat,
         size_format: SizeFormat,
         numerics: &NumericLocale,
         numerics: &NumericLocale,
+        color_scale_info: Option<ColorScaleInformation>,
     ) -> TextCell {
     ) -> TextCell {
         use number_prefix::NumberPrefix;
         use number_prefix::NumberPrefix;
 
 
@@ -21,28 +23,49 @@ impl f::Size {
             Self::DeviceIDs(ref ids) => return ids.render(colours),
             Self::DeviceIDs(ref ids) => return ids.render(colours),
         };
         };
 
 
+        let gradient_style = colours.major();
+        let is_gradient_mode =
+            color_scale_info.is_some_and(|csi| csi.options.mode == ColorScaleMode::Gradient);
+
         #[rustfmt::skip]
         #[rustfmt::skip]
         let result = match size_format {
         let result = match size_format {
             SizeFormat::DecimalBytes  => NumberPrefix::decimal(size as f64),
             SizeFormat::DecimalBytes  => NumberPrefix::decimal(size as f64),
             SizeFormat::BinaryBytes   => NumberPrefix::binary(size as f64),
             SizeFormat::BinaryBytes   => NumberPrefix::binary(size as f64),
             SizeFormat::JustBytes     => {
             SizeFormat::JustBytes     => {
-
                 // Use the binary prefix to select a style.
                 // Use the binary prefix to select a style.
                 let prefix = match NumberPrefix::binary(size as f64) {
                 let prefix = match NumberPrefix::binary(size as f64) {
-                    NumberPrefix::Standalone(_)   => None,
-                    NumberPrefix::Prefixed(p, _)  => Some(p),
+                    NumberPrefix::Standalone(_) => None,
+                    NumberPrefix::Prefixed(p, _) => Some(p),
                 };
                 };
 
 
                 // But format the number directly using the locale.
                 // But format the number directly using the locale.
                 let string = numerics.format_int(size);
                 let string = numerics.format_int(size);
 
 
-                return TextCell::paint(colours.size(prefix), string);
+                return if is_gradient_mode {
+                    let csi = color_scale_info.unwrap();
+                    TextCell::paint(
+                        csi.adjust_style(gradient_style, size as f32, csi.size),
+                        string,
+                    )
+                } else {
+                    TextCell::paint(colours.size(prefix), string)
+                }
             }
             }
         };
         };
 
 
         #[rustfmt::skip]
         #[rustfmt::skip]
         let (prefix, n) = match result {
         let (prefix, n) = match result {
-            NumberPrefix::Standalone(b)   => return TextCell::paint(colours.size(None), numerics.format_int(b)),
+            NumberPrefix::Standalone(b) => {
+                return if is_gradient_mode {
+                    let csi = color_scale_info.unwrap();
+                    TextCell::paint(
+                        csi.adjust_style(gradient_style, size as f32, csi.size),
+                        numerics.format_int(b),
+                    )
+                } else {
+                    TextCell::paint(colours.size(None), numerics.format_int(b))
+                }
+            }
             NumberPrefix::Prefixed(p, n)  => (p, n),
             NumberPrefix::Prefixed(p, n)  => (p, n),
         };
         };
 
 
@@ -56,10 +79,20 @@ impl f::Size {
         TextCell {
         TextCell {
             // symbol is guaranteed to be ASCII since unit prefixes are hardcoded.
             // symbol is guaranteed to be ASCII since unit prefixes are hardcoded.
             width: DisplayWidth::from(&*number) + symbol.len(),
             width: DisplayWidth::from(&*number) + symbol.len(),
-            contents: vec![
-                colours.size(Some(prefix)).paint(number),
-                colours.unit(Some(prefix)).paint(symbol),
-            ]
+            contents: if is_gradient_mode {
+                let csi = color_scale_info.unwrap();
+                vec![
+                    csi.adjust_style(gradient_style, size as f32, csi.size)
+                        .paint(number),
+                    csi.adjust_style(gradient_style, size as f32, csi.size)
+                        .paint(symbol),
+                ]
+            } else {
+                vec![
+                    colours.size(Some(prefix)).paint(number),
+                    colours.unit(Some(prefix)).paint(symbol),
+                ]
+            }
             .into(),
             .into(),
         }
         }
     }
     }
@@ -126,7 +159,8 @@ pub mod test {
             directory.render(
             directory.render(
                 &TestColours,
                 &TestColours,
                 SizeFormat::JustBytes,
                 SizeFormat::JustBytes,
-                &NumericLocale::english()
+                &NumericLocale::english(),
+                None
             )
             )
         )
         )
     }
     }
@@ -144,7 +178,8 @@ pub mod test {
             directory.render(
             directory.render(
                 &TestColours,
                 &TestColours,
                 SizeFormat::DecimalBytes,
                 SizeFormat::DecimalBytes,
-                &NumericLocale::english()
+                &NumericLocale::english(),
+                None
             )
             )
         )
         )
     }
     }
@@ -162,7 +197,8 @@ pub mod test {
             directory.render(
             directory.render(
                 &TestColours,
                 &TestColours,
                 SizeFormat::BinaryBytes,
                 SizeFormat::BinaryBytes,
-                &NumericLocale::english()
+                &NumericLocale::english(),
+                None
             )
             )
         )
         )
     }
     }
@@ -180,7 +216,8 @@ pub mod test {
             directory.render(
             directory.render(
                 &TestColours,
                 &TestColours,
                 SizeFormat::JustBytes,
                 SizeFormat::JustBytes,
-                &NumericLocale::english()
+                &NumericLocale::english(),
+                None
             )
             )
         )
         )
     }
     }
@@ -206,7 +243,8 @@ pub mod test {
             directory.render(
             directory.render(
                 &TestColours,
                 &TestColours,
                 SizeFormat::JustBytes,
                 SizeFormat::JustBytes,
-                &NumericLocale::english()
+                &NumericLocale::english(),
+                None
             )
             )
         )
         )
     }
     }

+ 44 - 23
src/output/table.rs

@@ -13,12 +13,15 @@ use uzers::UsersCache;
 use crate::fs::feature::git::GitCache;
 use crate::fs::feature::git::GitCache;
 use crate::fs::{fields as f, File};
 use crate::fs::{fields as f, File};
 use crate::output::cell::TextCell;
 use crate::output::cell::TextCell;
+use crate::output::color_scale::ColorScaleInformation;
 #[cfg(unix)]
 #[cfg(unix)]
 use crate::output::render::{GroupRender, OctalPermissionsRender, UserRender};
 use crate::output::render::{GroupRender, OctalPermissionsRender, UserRender};
 use crate::output::render::{PermissionsPlusRender, TimeRender};
 use crate::output::render::{PermissionsPlusRender, TimeRender};
 use crate::output::time::TimeFormat;
 use crate::output::time::TimeFormat;
 use crate::theme::Theme;
 use crate::theme::Theme;
 
 
+use super::color_scale::ColorScaleMode;
+
 /// Options for displaying a table.
 /// Options for displaying a table.
 #[derive(PartialEq, Eq, Debug)]
 #[derive(PartialEq, Eq, Debug)]
 pub struct Options {
 pub struct Options {
@@ -282,6 +285,16 @@ impl TimeType {
             Self::Created => "Date Created",
             Self::Created => "Date Created",
         }
         }
     }
     }
+
+    /// Returns the corresponding time from [File]
+    pub fn get_corresponding_time(self, file: &File<'_>) -> Option<NaiveDateTime> {
+        match self {
+            TimeType::Modified => file.modified_time(),
+            TimeType::Changed => file.changed_time(),
+            TimeType::Accessed => file.accessed_time(),
+            TimeType::Created => file.created_time(),
+        }
+    }
 }
 }
 
 
 /// Fields for which of a file’s time fields should be displayed in the
 /// Fields for which of a file’s time fields should be displayed in the
@@ -415,11 +428,16 @@ impl<'a> Table<'a> {
         Row { cells }
         Row { cells }
     }
     }
 
 
-    pub fn row_for_file(&self, file: &File<'_>, xattrs: bool) -> Row {
+    pub fn row_for_file(
+        &self,
+        file: &File<'_>,
+        xattrs: bool,
+        color_scale_info: Option<ColorScaleInformation>,
+    ) -> Row {
         let cells = self
         let cells = self
             .columns
             .columns
             .iter()
             .iter()
-            .map(|c| self.display(file, *c, xattrs))
+            .map(|c| self.display(file, *c, xattrs, color_scale_info))
             .collect();
             .collect();
 
 
         Row { cells }
         Row { cells }
@@ -455,12 +473,21 @@ impl<'a> Table<'a> {
             .map(|p| f::OctalPermissions { permissions: p })
             .map(|p| f::OctalPermissions { permissions: p })
     }
     }
 
 
-    fn display(&self, file: &File<'_>, column: Column, xattrs: bool) -> TextCell {
+    fn display(
+        &self,
+        file: &File<'_>,
+        column: Column,
+        xattrs: bool,
+        color_scale_info: Option<ColorScaleInformation>,
+    ) -> TextCell {
         match column {
         match column {
             Column::Permissions => self.permissions_plus(file, xattrs).render(self.theme),
             Column::Permissions => self.permissions_plus(file, xattrs).render(self.theme),
-            Column::FileSize => file
-                .size()
-                .render(self.theme, self.size_format, &self.env.numeric),
+            Column::FileSize => file.size().render(
+                self.theme,
+                self.size_format,
+                &self.env.numeric,
+                color_scale_info,
+            ),
             #[cfg(unix)]
             #[cfg(unix)]
             Column::HardLinks => file.links().render(self.theme, &self.env.numeric),
             Column::HardLinks => file.links().render(self.theme, &self.env.numeric),
             #[cfg(unix)]
             #[cfg(unix)]
@@ -490,23 +517,17 @@ impl<'a> Table<'a> {
             #[cfg(unix)]
             #[cfg(unix)]
             Column::Octal => self.octal_permissions(file).render(self.theme.ui.octal),
             Column::Octal => self.octal_permissions(file).render(self.theme.ui.octal),
 
 
-            Column::Timestamp(TimeType::Modified) => file.modified_time().render(
-                self.theme.ui.date,
-                self.env.time_offset,
-                self.time_format.clone(),
-            ),
-            Column::Timestamp(TimeType::Changed) => file.changed_time().render(
-                self.theme.ui.date,
-                self.env.time_offset,
-                self.time_format.clone(),
-            ),
-            Column::Timestamp(TimeType::Created) => file.created_time().render(
-                self.theme.ui.date,
-                self.env.time_offset,
-                self.time_format.clone(),
-            ),
-            Column::Timestamp(TimeType::Accessed) => file.accessed_time().render(
-                self.theme.ui.date,
+            Column::Timestamp(time_type) => time_type.get_corresponding_time(file).render(
+                if color_scale_info.is_some_and(|csi| csi.options.mode == ColorScaleMode::Gradient)
+                {
+                    color_scale_info.unwrap().apply_time_gradient(
+                        self.theme.ui.date,
+                        file,
+                        time_type,
+                    )
+                } else {
+                    self.theme.ui.date
+                },
                 self.env.time_offset,
                 self.env.time_offset,
                 self.time_format.clone(),
                 self.time_format.clone(),
             ),
             ),

+ 7 - 6
src/theme/default_theme.rs

@@ -1,11 +1,11 @@
 use ansiterm::Colour::*;
 use ansiterm::Colour::*;
 use ansiterm::Style;
 use ansiterm::Style;
 
 
+use crate::output::color_scale::ColorScaleOptions;
 use crate::theme::ui_styles::*;
 use crate::theme::ui_styles::*;
-use crate::theme::ColourScale;
 
 
 impl UiStyles {
 impl UiStyles {
-    pub fn default_theme(scale: ColourScale) -> Self {
+    pub fn default_theme(scale: ColorScaleOptions) -> Self {
         Self {
         Self {
             colourful: true,
             colourful: true,
 
 
@@ -123,10 +123,11 @@ impl UiStyles {
 }
 }
 
 
 impl Size {
 impl Size {
-    pub fn colourful(scale: ColourScale) -> Self {
-        match scale {
-            ColourScale::Gradient => Self::colourful_gradient(),
-            ColourScale::Fixed => Self::colourful_fixed(),
+    pub fn colourful(scale: ColorScaleOptions) -> Self {
+        if scale.size {
+            Self::colourful_gradient()
+        } else {
+            Self::colourful_fixed()
         }
         }
     }
     }
 
 

+ 2 - 7
src/theme/mod.rs

@@ -2,6 +2,7 @@ use ansiterm::Style;
 
 
 use crate::fs::File;
 use crate::fs::File;
 use crate::info::filetype::FileType;
 use crate::info::filetype::FileType;
+use crate::output::color_scale::ColorScaleOptions;
 use crate::output::file_name::Colours as FileNameColours;
 use crate::output::file_name::Colours as FileNameColours;
 use crate::output::render;
 use crate::output::render;
 
 
@@ -17,7 +18,7 @@ mod default_theme;
 pub struct Options {
 pub struct Options {
     pub use_colours: UseColours,
     pub use_colours: UseColours,
 
 
-    pub colour_scale: ColourScale,
+    pub colour_scale: ColorScaleOptions,
 
 
     pub definitions: Definitions,
     pub definitions: Definitions,
 }
 }
@@ -41,12 +42,6 @@ pub enum UseColours {
     Never,
     Never,
 }
 }
 
 
-#[derive(PartialEq, Eq, Debug, Copy, Clone)]
-pub enum ColourScale {
-    Fixed,
-    Gradient,
-}
-
 #[derive(PartialEq, Eq, Debug, Default)]
 #[derive(PartialEq, Eq, Debug, Default)]
 pub struct Definitions {
 pub struct Definitions {
     pub ls: Option<String>,
     pub ls: Option<String>,