Sfoglia il codice sorgente

Merge branch 'master' into chesterliu/dev/win-support

skyline75489 4 anni fa
parent
commit
8ad46e2ee5
44 ha cambiato i file con 344 aggiunte e 242 eliminazioni
  1. 4 46
      Cargo.lock
  2. 15 7
      Cargo.toml
  3. 53 23
      Justfile
  4. 7 1
      README.md
  5. 1 0
      completions/completions.fish
  6. 1 0
      completions/completions.zsh
  7. 3 0
      man/exa.1.md
  8. 1 1
      src/fs/feature/git.rs
  9. 1 1
      src/main.rs
  10. 2 1
      src/options/flags.rs
  11. 2 1
      src/options/help.rs
  12. 30 18
      src/options/view.rs
  13. 14 7
      src/output/grid_details.rs
  14. 19 7
      src/output/render/groups.rs
  15. 3 3
      src/output/render/size.rs
  16. 16 9
      src/output/render/users.rs
  17. 28 5
      src/output/table.rs
  18. 21 0
      xtests/grid-details-view.toml
  19. 1 1
      xtests/help.toml
  20. 25 25
      xtests/outputs/exts_oneline_icons.ansitxt
  21. 3 3
      xtests/outputs/files_long.ansitxt
  22. 3 3
      xtests/outputs/files_long_colourscale.ansitxt
  23. 3 3
      xtests/outputs/files_long_grid_1col.ansitxt
  24. 3 3
      xtests/outputs/files_long_grid_2col.ansitxt
  25. 3 3
      xtests/outputs/files_long_grid_3col.ansitxt
  26. 3 3
      xtests/outputs/files_long_grid_4col.ansitxt
  27. 3 3
      xtests/outputs/files_long_grid_icons.ansitxt
  28. 3 3
      xtests/outputs/files_long_header.ansitxt
  29. 3 3
      xtests/outputs/files_long_icons.ansitxt
  30. 3 3
      xtests/outputs/files_long_monochrome.ansitxt
  31. 4 4
      xtests/outputs/files_long_tree_icons.ansitxt
  32. 3 3
      xtests/outputs/files_paths_long_grid_1col.ansitxt
  33. 3 3
      xtests/outputs/files_paths_long_grid_2col.ansitxt
  34. 3 3
      xtests/outputs/files_paths_long_grid_3col.ansitxt
  35. 2 0
      xtests/outputs/files_paths_long_grid_header_1file.ansitxt
  36. 2 0
      xtests/outputs/files_paths_long_grid_header_2files.ansitxt
  37. 1 1
      xtests/outputs/files_tree_icons.ansitxt
  38. 2 0
      xtests/outputs/help.ansitxt
  39. 9 9
      xtests/outputs/links_oneline_icons.ansitxt
  40. 9 9
      xtests/outputs/links_oneline_icons_width0.ansitxt
  41. 9 9
      xtests/outputs/links_oneline_icons_width2.ansitxt
  42. 9 9
      xtests/outputs/links_oneline_icons_width3.ansitxt
  43. 6 6
      xtests/outputs/permissions_oneline_icons.ansitxt
  44. 5 0
      xtests/run.sh

+ 4 - 46
Cargo.lock

@@ -44,14 +44,12 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 
 [[package]]
 name = "datetime"
-version = "0.5.1"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0fcb4df22ae812fa2f6d5e3b577247584cc67fce06ad0779168d1dd41cbcce3"
+checksum = "44c3f7a77f3e57fedf80e09136f2d8777ebf621207306f6d96d610af048354bc"
 dependencies = [
- "iso8601",
  "libc",
  "locale",
- "num-traits",
  "pad",
  "redox_syscall",
  "winapi",
@@ -120,15 +118,6 @@ dependencies = [
  "unicode-normalization",
 ]
 
-[[package]]
-name = "iso8601"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43e86914a73535f3f541a765adea0a9fafcf53fa6adb73662c4988fd9233766f"
-dependencies = [
- "nom",
-]
-
 [[package]]
 name = "jobserver"
 version = "0.1.21"
@@ -198,37 +187,12 @@ version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
 
-[[package]]
-name = "memchr"
-version = "2.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
-
 [[package]]
 name = "natord"
 version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
 
-[[package]]
-name = "nom"
-version = "4.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
-dependencies = [
- "memchr",
- "version_check",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
-dependencies = [
- "autocfg",
-]
-
 [[package]]
 name = "num_cpus"
 version = "1.13.0"
@@ -377,12 +341,6 @@ version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
 
-[[package]]
-name = "version_check"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
-
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -407,9 +365,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
 name = "zoneinfo_compiled"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7033eef97c288bfa49e3ebf958245a41016f1673a5317196efad03eb656a7648"
+checksum = "64fbebe65e899530f43bd760b23fda8f141118f4db49952b02998cbd0907a5de"
 dependencies = [
  "byteorder",
  "datetime",

+ 15 - 7
Cargo.toml

@@ -23,7 +23,6 @@ name = "exa"
 
 [dependencies]
 ansi_term = "0.12"
-datetime = "0.5"
 glob = "0.3"
 lazy_static = "1.3"
 libc = "0.2"
@@ -36,29 +35,38 @@ scoped_threadpool = "0.1"
 term_grid = "0.1"
 term_size = "0.3"
 unicode-width = "0.1"
-zoneinfo_compiled = "0.5"
+zoneinfo_compiled = "0.5.1"
 
 [target.'cfg(unix)'.dependencies]
 users = "0.11"
 
+[dependencies.datetime]
+version = "0.5.2"
+default-features = false
+features = ["format"]
+
 [dependencies.git2]
 version = "0.13"
 optional = true
 default-features = false
 
-[build-dependencies]
-datetime = "0.5"
+[build-dependencies.datetime]
+version = "0.5.2"
+default-features = false
 
 [features]
 default = [ "git" ]
 git = [ "git2" ]
 vendored-openssl = ["git2/vendored-openssl"]
 
-[profile.release]
-opt-level = 3
+
+# make dev builds faster by excluding debug symbols
+[profile.dev]
 debug = false
+
+# use LTO for smaller binaries (that take longer to build)
+[profile.release]
 lto = true
-panic = "abort"
 
 
 [package.metadata.deb]

+ 53 - 23
Justfile

@@ -1,74 +1,104 @@
-all: build test xtests
-all-release: build-release test-release xtests-release
+all: build test
+all-release: build-release test-release
 
 
-# compiles the exa binary
+#----------#
+# building #
+#----------#
+
+# compile the exa binary
 @build:
     cargo build
 
-# compiles the exa binary (in release mode)
+# compile the exa binary (in release mode)
 @build-release:
     cargo build --release --verbose
 
-# compiles the exa binary with every combination of feature flags
-@build-features:
-    cargo hack build --feature-powerset
+# produce an HTML chart of compilation timings
+@build-time:
+    cargo +nightly clean
+    cargo +nightly build -Z timings
+
+# check that the exa binary can compile
+@check:
+    cargo check
 
 
-# runs unit tests
+#---------------#
+# running tests #
+#---------------#
+
+# run unit tests
 @test:
-    cargo test --all -- --quiet
+    cargo test --workspace -- --quiet
 
-# runs unit tests (in release mode)
+# run unit tests (in release mode)
 @test-release:
-    cargo test --release --all --verbose
+    cargo test --workspace --release --verbose
 
-# runs unit tests with every combination of feature flags
-@test-features:
-    cargo hack test --feature-powerset -- --quiet
 
+#------------------------#
+# running extended tests #
+#------------------------#
 
-# runs extended tests
+# run extended tests
 @xtests:
     xtests/run.sh
 
-# runs extended tests (using the release mode exa)
+# run extended tests (using the release mode exa)
 @xtests-release:
     xtests/run.sh --release
 
+# display the number of extended tests that get run
+@count-xtests:
+    grep -F '[[cmd]]' -R xtests | wc -l
+
 
-# lints the code
+#-----------------------#
+# code quality and misc #
+#-----------------------#
+
+# lint the code
 @clippy:
     touch src/main.rs
     cargo clippy
 
-# updates dependency versions, and checks for outdated ones
+# update dependency versions, and checks for outdated ones
 @update-deps:
     cargo update
     command -v cargo-outdated >/dev/null || (echo "cargo-outdated not installed" && exit 1)
     cargo outdated
 
-# lists unused dependencies
+# list unused dependencies
 @unused-deps:
     command -v cargo-udeps >/dev/null || (echo "cargo-udeps not installed" && exit 1)
     cargo +nightly udeps
 
-# prints versions of the necessary build tools
+# check that every combination of feature flags is successful
+@check-features:
+    command -v cargo-hack >/dev/null || (echo "cargo-hack not installed" && exit 1)
+    cargo hack check --feature-powerset
+
+# print versions of the necessary build tools
 @versions:
     rustc --version
     cargo --version
 
 
-# builds the man pages
+#---------------#
+# documentation #
+#---------------#
+
+# build the man pages
 @man:
     mkdir -p "${CARGO_TARGET_DIR:-target}/man"
     pandoc --standalone -f markdown -t man man/exa.1.md        > "${CARGO_TARGET_DIR:-target}/man/exa.1"
     pandoc --standalone -f markdown -t man man/exa_colors.5.md > "${CARGO_TARGET_DIR:-target}/man/exa_colors.5"
 
-# builds and previews the main man page (exa.1)
+# build and preview the main man page (exa.1)
 @man-1-preview: man
     man "${CARGO_TARGET_DIR:-target}/man/exa.1"
 
-# builds and previews the colour configuration man page (exa_colors.5)
+# build and preview the colour configuration man page (exa_colors.5)
 @man-5-preview: man
     man "${CARGO_TARGET_DIR:-target}/man/exa_colors.5"

+ 7 - 1
README.md

@@ -117,6 +117,12 @@ On Arch, install the [`exa`](https://www.archlinux.org/packages/community/x86_64
 
     $ pacman -S exa
 
+### Android / Termux
+
+On Android / Termux, install the [`exa`](https://github.com/termux/termux-packages/tree/master/packages/exa) package.
+
+    $ pkg install exa
+
 ### Debian
 
 On Debian, install the [`exa`](https://packages.debian.org/unstable/exa) package.
@@ -164,7 +170,7 @@ On openSUSE, install the [`exa`](https://software.opensuse.org/package/exa) pack
 
 On Ubuntu 20.10 (Groovy Gorilla) and later, install the [`exa`](https://packages.ubuntu.com/groovy/exa) package.
 
-    $ apt install exa
+    $ sudo apt install exa
 
 ### Void Linux
 

+ 1 - 0
completions/completions.fish

@@ -66,6 +66,7 @@ complete -c exa -s 't' -l 'time'  -x -d "Which timestamp field to list" -a "
     created\t'Display created time'
 "
 complete -c exa -s 'm' -l 'modified'      -d "Use the modified timestamp field"
+complete -c exa -s 'n' -l 'numeric'       -d "List numeric user and group IDs."
 complete -c exa        -l 'changed'       -d "Use the changed timestamp field"
 complete -c exa -s 'u' -l 'accessed'      -d "Use the accessed timestamp field"
 complete -c exa -s 'U' -l 'created'       -d "Use the created timestamp field"

+ 1 - 0
completions/completions.zsh

@@ -40,6 +40,7 @@ __exa() {
         {-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]" \
+        {-n,--numeric}"[List numeric user and group IDs.]" \
         {-S,--blocks}"[List each file's number of filesystem blocks]" \
         {-t,--time}="[Which time field to show]:(time field):(accessed changed created modified)" \
         --time-style="[How to format timestamps]:(time style):(default iso long-iso full-iso)" \

+ 3 - 0
man/exa.1.md

@@ -143,6 +143,9 @@ These options are available when running with `--long` (`-l`):
 `-m`, `--modified`
 : Use the modified timestamp field.
 
+`-n`, `--numeric`
+: List numeric user and group IDs.
+
 `-S`, `--blocks`
 : List each file’s number of file system blocks.
 

+ 1 - 1
src/fs/feature/git.rs

@@ -211,7 +211,7 @@ fn repo_to_statuses(repo: &git2::Repository, workdir: &Path) -> Git {
             }
         }
         Err(e) => {
-            error!("Error looking up Git statuses: {:?}", e)
+            error!("Error looking up Git statuses: {:?}", e);
         }
     }
 

+ 1 - 1
src/main.rs

@@ -93,7 +93,7 @@ fn main() {
         }
 
         OptionsResult::InvalidOptions(error) => {
-            eprintln!("{}", error);
+            eprintln!("exa: {}", error);
 
             if let Some(s) = error.suggestion() {
                 eprintln!("{}", s);

+ 2 - 1
src/options/flags.rs

@@ -39,6 +39,7 @@ const SORTS: Values = &[ "name", "Name", "size", "extension",
 pub static BINARY:     Arg = Arg { short: Some(b'b'), long: "binary",     takes_value: TakesValue::Forbidden };
 pub static BYTES:      Arg = Arg { short: Some(b'B'), long: "bytes",      takes_value: TakesValue::Forbidden };
 pub static GROUP:      Arg = Arg { short: Some(b'g'), long: "group",      takes_value: TakesValue::Forbidden };
+pub static NUMERIC:    Arg = Arg { short: Some(b'n'), long: "numeric",    takes_value: TakesValue::Forbidden };
 pub static HEADER:     Arg = Arg { short: Some(b'h'), long: "header",     takes_value: TakesValue::Forbidden };
 pub static ICONS:      Arg = Arg { short: None,       long: "icons",      takes_value: TakesValue::Forbidden };
 pub static INODE:      Arg = Arg { short: Some(b'i'), long: "inode",      takes_value: TakesValue::Forbidden };
@@ -75,7 +76,7 @@ pub static ALL_ARGS: Args = Args(&[
     &ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &DIRS_FIRST,
     &IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS,
 
-    &BINARY, &BYTES, &GROUP, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
+    &BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
     &BLOCKS, &TIME, &ACCESSED, &CREATED, &TIME_STYLE,
     &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &NO_ICONS,
 

+ 2 - 1
src/options/help.rs

@@ -48,6 +48,7 @@ LONG VIEW OPTIONS
   -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
+  -n, --numeric        list numeric user and group IDs
   -S, --blocks         show number of file system blocks
   -t, --time FIELD     which timestamp field to list (modified, accessed, created)
   -u, --accessed       use the accessed timestamp field
@@ -111,7 +112,7 @@ impl fmt::Display for HelpString {
             write!(f, "\n{}", EXTENDED_HELP)?;
         }
 
-        Ok(())
+        writeln!(f)
     }
 }
 

+ 30 - 18
src/options/view.rs

@@ -4,7 +4,7 @@ use crate::options::parser::MatchedFlags;
 use crate::output::{View, Mode, TerminalWidth, grid, details};
 use crate::output::grid_details::{self, RowThreshold};
 use crate::output::file_name::Options as FileStyle;
-use crate::output::table::{TimeTypes, SizeFormat, Columns, Options as TableOptions};
+use crate::output::table::{TimeTypes, SizeFormat, UserFormat, Columns, Options as TableOptions};
 use crate::output::time::TimeFormat;
 
 
@@ -85,7 +85,7 @@ impl Mode {
         // user about flags that won’t have any effect.
         if matches.is_strict() {
             for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS,
-                             &flags::HEADER, &flags::BLOCKS, &flags::TIME, &flags::GROUP ] {
+                             &flags::HEADER, &flags::BLOCKS, &flags::TIME, &flags::GROUP, &flags::NUMERIC ] {
                 if matches.has(option)? {
                     return Err(OptionsError::Useless(*option, false, &flags::LONG));
                 }
@@ -183,8 +183,9 @@ impl TableOptions {
     fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
         let time_format = TimeFormat::deduce(matches, vars)?;
         let size_format = SizeFormat::deduce(matches)?;
+        let user_format = UserFormat::deduce(matches)?;
         let columns = Columns::deduce(matches)?;
-        Ok(Self { time_format, size_format, columns })
+        Ok(Self { time_format, size_format, columns , user_format})
     }
 }
 
@@ -266,6 +267,14 @@ impl TimeFormat {
 }
 
 
+impl UserFormat {
+    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
+        let flag = matches.has(&flags::NUMERIC)?;
+        Ok(if flag { Self::Numeric } else { Self::Name })
+    }
+}
+
+
 impl TimeTypes {
 
     /// Determine which of a file’s time fields should be displayed for it
@@ -345,7 +354,8 @@ mod test {
                                    &flags::CREATED, &flags::ACCESSED,
                                    &flags::HEADER, &flags::GROUP,  &flags::INODE, &flags::GIT,
                                    &flags::LINKS,  &flags::BLOCKS, &flags::LONG,  &flags::LEVEL,
-                                   &flags::GRID,   &flags::ACROSS, &flags::ONE_LINE, &flags::TREE ];
+                                   &flags::GRID,   &flags::ACROSS, &flags::ONE_LINE, &flags::TREE,
+                                   &flags::NUMERIC ];
 
     macro_rules! test {
 
@@ -547,24 +557,26 @@ mod test {
         test!(long_across:   Mode <- ["--long", "--across"],   None;  Last => like Ok(Mode::Details(_)));
 
         // Options that do nothing without --long
-        test!(just_header:   Mode <- ["--header"], None;  Last => like Ok(Mode::Grid(_)));
-        test!(just_group:    Mode <- ["--group"],  None;  Last => like Ok(Mode::Grid(_)));
-        test!(just_inode:    Mode <- ["--inode"],  None;  Last => like Ok(Mode::Grid(_)));
-        test!(just_links:    Mode <- ["--links"],  None;  Last => like Ok(Mode::Grid(_)));
-        test!(just_blocks:   Mode <- ["--blocks"], None;  Last => like Ok(Mode::Grid(_)));
-        test!(just_binary:   Mode <- ["--binary"], None;  Last => like Ok(Mode::Grid(_)));
-        test!(just_bytes:    Mode <- ["--bytes"],  None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_header:   Mode <- ["--header"],   None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_group:    Mode <- ["--group"],    None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_inode:    Mode <- ["--inode"],    None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_links:    Mode <- ["--links"],    None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_blocks:   Mode <- ["--blocks"],   None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_binary:   Mode <- ["--binary"],   None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_bytes:    Mode <- ["--bytes"],    None;  Last => like Ok(Mode::Grid(_)));
+        test!(just_numeric:  Mode <- ["--numeric"],  None;  Last => like Ok(Mode::Grid(_)));
 
         #[cfg(feature = "git")]
         test!(just_git:      Mode <- ["--git"],    None;  Last => like Ok(Mode::Grid(_)));
 
-        test!(just_header_2: Mode <- ["--header"], None;  Complain => err OptionsError::Useless(&flags::HEADER, false, &flags::LONG));
-        test!(just_group_2:  Mode <- ["--group"],  None;  Complain => err OptionsError::Useless(&flags::GROUP,  false, &flags::LONG));
-        test!(just_inode_2:  Mode <- ["--inode"],  None;  Complain => err OptionsError::Useless(&flags::INODE,  false, &flags::LONG));
-        test!(just_links_2:  Mode <- ["--links"],  None;  Complain => err OptionsError::Useless(&flags::LINKS,  false, &flags::LONG));
-        test!(just_blocks_2: Mode <- ["--blocks"], None;  Complain => err OptionsError::Useless(&flags::BLOCKS, false, &flags::LONG));
-        test!(just_binary_2: Mode <- ["--binary"], None;  Complain => err OptionsError::Useless(&flags::BINARY, false, &flags::LONG));
-        test!(just_bytes_2:  Mode <- ["--bytes"],  None;  Complain => err OptionsError::Useless(&flags::BYTES,  false, &flags::LONG));
+        test!(just_header_2: Mode <- ["--header"],   None;  Complain => err OptionsError::Useless(&flags::HEADER,  false, &flags::LONG));
+        test!(just_group_2:  Mode <- ["--group"],    None;  Complain => err OptionsError::Useless(&flags::GROUP,   false, &flags::LONG));
+        test!(just_inode_2:  Mode <- ["--inode"],    None;  Complain => err OptionsError::Useless(&flags::INODE,   false, &flags::LONG));
+        test!(just_links_2:  Mode <- ["--links"],    None;  Complain => err OptionsError::Useless(&flags::LINKS,   false, &flags::LONG));
+        test!(just_blocks_2: Mode <- ["--blocks"],   None;  Complain => err OptionsError::Useless(&flags::BLOCKS,  false, &flags::LONG));
+        test!(just_binary_2: Mode <- ["--binary"],   None;  Complain => err OptionsError::Useless(&flags::BINARY,  false, &flags::LONG));
+        test!(just_bytes_2:  Mode <- ["--bytes"],    None;  Complain => err OptionsError::Useless(&flags::BYTES,   false, &flags::LONG));
+        test!(just_numeric2: Mode <- ["--numeric"],  None;  Complain => err OptionsError::Useless(&flags::NUMERIC, false, &flags::LONG));
 
         #[cfg(feature = "git")]
         test!(just_git_2:    Mode <- ["--git"],    None;  Complain => err OptionsError::Useless(&flags::GIT,    false, &flags::LONG));

+ 14 - 7
src/output/grid_details.rs

@@ -157,7 +157,11 @@ impl<'a> Render<'a> {
                              .map(|file| self.file_style.for_file(file, self.theme).paint().promote())
                              .collect::<Vec<_>>();
 
-        let mut last_working_table = self.make_grid(1, options, &file_names, rows.clone(), &drender);
+        let mut last_working_grid = self.make_grid(1, options, &file_names, rows.clone(), &drender);
+        
+        if file_names.len() == 1 {
+            return Some((last_working_grid, 1));
+        }
 
         // If we can’t fit everything in a grid 100 columns wide, then
         // something has gone seriously awry
@@ -166,23 +170,26 @@ impl<'a> Render<'a> {
 
             let the_grid_fits = {
                 let d = grid.fit_into_columns(column_count);
-                d.is_complete() && d.width() <= self.console_width
+                d.width() <= self.console_width
             };
 
             if the_grid_fits {
-                last_working_table = grid;
-            }
-            else {
+                if column_count == file_names.len() {
+                    return Some((grid, column_count));
+                } else { 
+                    last_working_grid = grid;
+                }
+            } else {
                 // If we’ve figured out how many columns can fit in the user’s
                 // terminal, and it turns out there aren’t enough rows to
                 // make it worthwhile, then just resort to the lines view.
                 if let RowThreshold::MinimumRows(thresh) = self.row_threshold {
-                    if last_working_table.fit_into_columns(column_count - 1).row_count() < thresh {
+                    if last_working_grid.fit_into_columns(column_count - 1).row_count() < thresh {
                         return None;
                     }
                 }
 
-                return Some((last_working_table, column_count - 1));
+                return Some((last_working_grid, column_count - 1));
             }
         }
 

+ 19 - 7
src/output/render/groups.rs

@@ -3,10 +3,11 @@ use users::{Users, Groups};
 
 use crate::fs::fields as f;
 use crate::output::cell::TextCell;
+use crate::output::table::UserFormat;
 
 
 impl f::Group {
-    pub fn render<C: Colours, U: Users+Groups>(self, colours: &C, users: &U) -> TextCell {
+    pub fn render<C: Colours, U: Users+Groups>(self, colours: &C, users: &U, format: UserFormat) -> TextCell {
         use users::os::unix::GroupExt;
 
         let mut style = colours.not_yours();
@@ -26,7 +27,12 @@ impl f::Group {
             }
         }
 
-        TextCell::paint(style, group.name().to_string_lossy().into())
+        let group_name = match format {
+            UserFormat::Name => group.name().to_string_lossy().into(),
+            UserFormat::Numeric => group.gid().to_string(),
+        };
+
+        TextCell::paint(style, group_name)
     }
 }
 
@@ -43,6 +49,7 @@ pub mod test {
     use super::Colours;
     use crate::fs::fields as f;
     use crate::output::cell::TextCell;
+    use crate::output::table::UserFormat;
 
     use users::{User, Group};
     use users::mock::MockUsers;
@@ -66,16 +73,21 @@ pub mod test {
 
         let group = f::Group(100);
         let expected = TextCell::paint_str(Fixed(81).normal(), "folk");
-        assert_eq!(expected, group.render(&TestColours, &users))
+        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name));
+
+        let expected = TextCell::paint_str(Fixed(81).normal(), "100");
+        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Numeric));
     }
 
+
     #[test]
     fn unnamed() {
         let users = MockUsers::with_current_uid(1000);
 
         let group = f::Group(100);
         let expected = TextCell::paint_str(Fixed(81).normal(), "100");
-        assert_eq!(expected, group.render(&TestColours, &users));
+        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name));
+        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Numeric));
     }
 
     #[test]
@@ -86,7 +98,7 @@ pub mod test {
 
         let group = f::Group(100);
         let expected = TextCell::paint_str(Fixed(80).normal(), "folk");
-        assert_eq!(expected, group.render(&TestColours, &users))
+        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name))
     }
 
     #[test]
@@ -99,13 +111,13 @@ pub mod test {
 
         let group = f::Group(100);
         let expected = TextCell::paint_str(Fixed(80).normal(), "folk");
-        assert_eq!(expected, group.render(&TestColours, &users))
+        assert_eq!(expected, group.render(&TestColours, &users, UserFormat::Name))
     }
 
     #[test]
     fn overflow() {
         let group = f::Group(2_147_483_648);
         let expected = TextCell::paint_str(Fixed(81).normal(), "2147483648");
-        assert_eq!(expected, group.render(&TestColours, &MockUsers::with_current_uid(0)));
+        assert_eq!(expected, group.render(&TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric));
     }
 }

+ 3 - 3
src/output/render/size.rs

@@ -41,9 +41,9 @@ impl f::Size {
         };
 
         let symbol = prefix.symbol();
-        let number = if n < 10_f64 { numerics.format_float(n, 1) }
-                              else { numerics.format_int(n as isize) };
-
+        let decimal_to_diplay = if n < 10_f64 { 1 } else { 0 };
+        let number = numerics.format_float(n, decimal_to_diplay);
+        
         // The numbers and symbols are guaranteed to be written in ASCII, so
         // we can skip the display width calculation.
         let width = DisplayWidth::from(number.len() + symbol.len());

+ 16 - 9
src/output/render/users.rs

@@ -3,13 +3,15 @@ use users::Users;
 
 use crate::fs::fields as f;
 use crate::output::cell::TextCell;
+use crate::output::table::UserFormat;
 
 
 impl f::User {
-    pub fn render<C: Colours, U: Users>(self, colours: &C, users: &U) -> TextCell {
-        let user_name = match users.get_user_by_uid(self.0) {
-            Some(user)  => user.name().to_string_lossy().into(),
-            None        => self.0.to_string(),
+    pub fn render<C: Colours, U: Users>(self, colours: &C, users: &U, format: UserFormat) -> TextCell {
+        let user_name = match (format, users.get_user_by_uid(self.0)) {
+            (_, None)                      => self.0.to_string(),
+            (UserFormat::Numeric, _)       => self.0.to_string(),
+            (UserFormat::Name, Some(user)) => user.name().to_string_lossy().into(),
         };
 
         let style = if users.get_current_uid() == self.0 { colours.you() }
@@ -31,6 +33,7 @@ pub mod test {
     use super::Colours;
     use crate::fs::fields as f;
     use crate::output::cell::TextCell;
+    use crate::output::table::UserFormat;
 
     use users::User;
     use users::mock::MockUsers;
@@ -53,7 +56,10 @@ pub mod test {
 
         let user = f::User(1000);
         let expected = TextCell::paint_str(Red.bold(), "enoch");
-        assert_eq!(expected, user.render(&TestColours, &users))
+        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name));
+
+        let expected = TextCell::paint_str(Red.bold(), "1000");
+        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Numeric));
     }
 
     #[test]
@@ -62,7 +68,8 @@ pub mod test {
 
         let user = f::User(1000);
         let expected = TextCell::paint_str(Red.bold(), "1000");
-        assert_eq!(expected, user.render(&TestColours, &users));
+        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name));
+        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Numeric));
     }
 
     #[test]
@@ -72,20 +79,20 @@ pub mod test {
 
         let user = f::User(1000);
         let expected = TextCell::paint_str(Blue.underline(), "enoch");
-        assert_eq!(expected, user.render(&TestColours, &users));
+        assert_eq!(expected, user.render(&TestColours, &users, UserFormat::Name));
     }
 
     #[test]
     fn different_unnamed() {
         let user = f::User(1000);
         let expected = TextCell::paint_str(Blue.underline(), "1000");
-        assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0)));
+        assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric));
     }
 
     #[test]
     fn overflow() {
         let user = f::User(2_147_483_648);
         let expected = TextCell::paint_str(Blue.underline(), "2147483648");
-        assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0)));
+        assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0), UserFormat::Numeric));
     }
 }

+ 28 - 5
src/output/table.rs

@@ -25,6 +25,7 @@ use crate::theme::Theme;
 pub struct Options {
     pub size_format: SizeFormat,
     pub time_format: TimeFormat,
+    pub user_format: UserFormat,
     pub columns: Columns,
 }
 
@@ -213,6 +214,15 @@ pub enum SizeFormat {
     JustBytes,
 }
 
+/// Formatting options for user and group.
+#[derive(PartialEq, Debug, Copy, Clone)]
+pub enum UserFormat {
+    /// The UID / GID
+    Numeric,
+    /// Show the name
+    Name,
+}
+
 impl Default for SizeFormat {
     fn default() -> Self {
         Self::DecimalBytes
@@ -329,9 +339,20 @@ impl Environment {
 #[cfg(unix)]
 fn determine_time_zone() -> TZResult<TimeZone> {
     if let Ok(file) = env::var("TZ") {
-        TimeZone::from_file(format!("/usr/share/zoneinfo/{}", file))
-    }
-    else {
+        TimeZone::from_file({
+            if file.starts_with("/") {
+                file
+            } else {
+                format!("/usr/share/zoneinfo/{}", {
+                    if file.starts_with(":") {
+                        file.replacen(":", "", 1)
+                    } else {
+                        file
+                    }
+                })
+            }
+        })
+    } else {
         TimeZone::from_file("/etc/localtime")
     }
 }
@@ -373,6 +394,7 @@ pub struct Table<'a> {
     widths: TableWidths,
     time_format: TimeFormat,
     size_format: SizeFormat,
+    user_format: UserFormat,
     git: Option<&'a GitCache>,
 }
 
@@ -395,6 +417,7 @@ impl<'a, 'f> Table<'a> {
             env,
             time_format: options.time_format,
             size_format: options.size_format,
+            user_format: options.user_format,
         }
     }
 
@@ -462,11 +485,11 @@ impl<'a, 'f> Table<'a> {
             }
             #[cfg(unix)]
             Column::User => {
-                file.user().render(self.theme, &*self.env.lock_users())
+                file.user().render(self.theme, &*self.env.lock_users(), self.user_format)
             }
             #[cfg(unix)]
             Column::Group => {
-                file.group().render(self.theme, &*self.env.lock_users())
+                file.group().render(self.theme, &*self.env.lock_users(), self.user_format)
             }
             Column::GitStatus => {
                 self.git_status(file).render(self.theme)

+ 21 - 0
xtests/grid-details-view.toml

@@ -76,3 +76,24 @@ stdout = { file = "outputs/files_paths_long_grid_3col.ansitxt" }
 stderr = { empty = true }
 status = 0
 tags = [ 'env', 'long', 'grid' ]
+
+
+# check if exa is using the minimum number of columns with headers
+
+[[cmd]]
+name = "‘COLUMN=200 exa -lGh’ with one file don’t produce extra columns even if there place for more"
+shell = "exa -lGh /testcases/files/10_bytes"
+environment = { COLUMNS = "200" }
+stdout = { file = "outputs/files_paths_long_grid_header_1file.ansitxt" }
+stderr = { empty = true }
+status = 0
+tags = [ 'env', 'long', 'grid' ]
+
+[[cmd]]
+name = "‘COLUMN=200 exa -lGh’ with several files don’t produce extra columns even if there place for more"
+shell = "exa -lGh /testcases/files/10_{bytes,KiB}"
+environment = { COLUMNS = "200" }
+stdout = { file = "outputs/files_paths_long_grid_header_2files.ansitxt" }
+stderr = { empty = true }
+status = 0
+tags = [ 'env', 'long', 'grid' ]

+ 1 - 1
xtests/help.toml

@@ -4,4 +4,4 @@ shell = "exa --help"
 stdout = { file = "outputs/help.ansitxt" }
 stderr = { empty = true }
 status = 0
-tags = [ 'help ']
+tags = [ 'help' ]

+ 25 - 25
xtests/outputs/exts_oneline_icons.ansitxt

@@ -1,26 +1,26 @@
- #SAVEFILE#
- backup~
- compiled.class
+ #SAVEFILE#
+ backup~
+ compiled.class
  compiled.coffee
- compiled.js
- compiled.o
- compressed.deb
- compressed.tar.gz
- compressed.tar.xz
- compressed.tgz
- compressed.txz
- COMPRESSED.ZIP
- crypto.asc
- crypto.signature
- document.pdf
- DOCUMENT.XLSX
- file.tmp
- IMAGE.PNG
- image.svg
- lossless.flac
- lossless.wav
- Makefile
- music.mp3
- MUSIC.OGG
- VIDEO.AVI
- video.wmv
+ compiled.js
+compiled.o
+ compressed.deb
+ compressed.tar.gz
+ compressed.tar.xz
+compressed.tgz
+compressed.txz
+ COMPRESSED.ZIP
+crypto.asc
+crypto.signature
+ document.pdf
+ DOCUMENT.XLSX
+file.tmp
+ IMAGE.PNG
+ image.svg
+ lossless.flac
+ lossless.wav
+ Makefile
+ music.mp3
+ MUSIC.OGG
+ VIDEO.AVI
+ video.wmv

+ 3 - 3
xtests/outputs/files_long.ansitxt

@@ -30,10 +30,10 @@
 .rw-r--r--  10M cassowary  1 Jan 12:34 10_MiB
 .rw-r--r--   11 cassowary  1 Jan 12:34 11_bytes
 .rw-r--r--  11k cassowary  1 Jan 12:34 11_KiB
-.rw-r--r--  11M cassowary  1 Jan 12:34 11_MiB
+.rw-r--r--  12M cassowary  1 Jan 12:34 11_MiB
 .rw-r--r--   12 cassowary  1 Jan 12:34 12_bytes
 .rw-r--r--  12k cassowary  1 Jan 12:34 12_KiB
-.rw-r--r--  12M cassowary  1 Jan 12:34 12_MiB
+.rw-r--r--  13M cassowary  1 Jan 12:34 12_MiB
 .rw-r--r--   13 cassowary  1 Jan 12:34 13_bytes
 .rw-r--r--  13k cassowary  1 Jan 12:34 13_KiB
-.rw-r--r--  13M cassowary  1 Jan 12:34 13_MiB
+.rw-r--r--  14M cassowary  1 Jan 12:34 13_MiB

+ 3 - 3
xtests/outputs/files_long_colourscale.ansitxt

@@ -30,10 +30,10 @@
 .rw-r--r--  10M cassowary  1 Jan 12:34 10_MiB
 .rw-r--r--   11 cassowary  1 Jan 12:34 11_bytes
 .rw-r--r--  11k cassowary  1 Jan 12:34 11_KiB
-.rw-r--r--  11M cassowary  1 Jan 12:34 11_MiB
+.rw-r--r--  12M cassowary  1 Jan 12:34 11_MiB
 .rw-r--r--   12 cassowary  1 Jan 12:34 12_bytes
 .rw-r--r--  12k cassowary  1 Jan 12:34 12_KiB
-.rw-r--r--  12M cassowary  1 Jan 12:34 12_MiB
+.rw-r--r--  13M cassowary  1 Jan 12:34 12_MiB
 .rw-r--r--   13 cassowary  1 Jan 12:34 13_bytes
 .rw-r--r--  13k cassowary  1 Jan 12:34 13_KiB
-.rw-r--r--  13M cassowary  1 Jan 12:34 13_MiB
+.rw-r--r--  14M cassowary  1 Jan 12:34 13_MiB

+ 3 - 3
xtests/outputs/files_long_grid_1col.ansitxt

@@ -30,10 +30,10 @@
 .rw-r--r--  10M cassowary  1 Jan 12:34 10_MiB
 .rw-r--r--   11 cassowary  1 Jan 12:34 11_bytes
 .rw-r--r--  11k cassowary  1 Jan 12:34 11_KiB
-.rw-r--r--  11M cassowary  1 Jan 12:34 11_MiB
+.rw-r--r--  12M cassowary  1 Jan 12:34 11_MiB
 .rw-r--r--   12 cassowary  1 Jan 12:34 12_bytes
 .rw-r--r--  12k cassowary  1 Jan 12:34 12_KiB
-.rw-r--r--  12M cassowary  1 Jan 12:34 12_MiB
+.rw-r--r--  13M cassowary  1 Jan 12:34 12_MiB
 .rw-r--r--   13 cassowary  1 Jan 12:34 13_bytes
 .rw-r--r--  13k cassowary  1 Jan 12:34 13_KiB
-.rw-r--r--  13M cassowary  1 Jan 12:34 13_MiB
+.rw-r--r--  14M cassowary  1 Jan 12:34 13_MiB

+ 3 - 3
xtests/outputs/files_long_grid_2col.ansitxt

@@ -10,11 +10,11 @@
 .rw-r--r--    4 cassowary  1 Jan 12:34 4_bytes    .rw-r--r--  10M cassowary  1 Jan 12:34 10_MiB
 .rw-r--r-- 4.1k cassowary  1 Jan 12:34 4_KiB      .rw-r--r--   11 cassowary  1 Jan 12:34 11_bytes
 .rw-r--r-- 4.2M cassowary  1 Jan 12:34 4_MiB      .rw-r--r--  11k cassowary  1 Jan 12:34 11_KiB
-.rw-r--r--    5 cassowary  1 Jan 12:34 5_bytes    .rw-r--r--  11M cassowary  1 Jan 12:34 11_MiB
+.rw-r--r--    5 cassowary  1 Jan 12:34 5_bytes    .rw-r--r--  12M cassowary  1 Jan 12:34 11_MiB
 .rw-r--r-- 5.1k cassowary  1 Jan 12:34 5_KiB      .rw-r--r--   12 cassowary  1 Jan 12:34 12_bytes
 .rw-r--r-- 5.2M cassowary  1 Jan 12:34 5_MiB      .rw-r--r--  12k cassowary  1 Jan 12:34 12_KiB
-.rw-r--r--    6 cassowary  1 Jan 12:34 6_bytes    .rw-r--r--  12M cassowary  1 Jan 12:34 12_MiB
+.rw-r--r--    6 cassowary  1 Jan 12:34 6_bytes    .rw-r--r--  13M cassowary  1 Jan 12:34 12_MiB
 .rw-r--r-- 6.1k cassowary  1 Jan 12:34 6_KiB      .rw-r--r--   13 cassowary  1 Jan 12:34 13_bytes
 .rw-r--r-- 6.3M cassowary  1 Jan 12:34 6_MiB      .rw-r--r--  13k cassowary  1 Jan 12:34 13_KiB
-.rw-r--r--    7 cassowary  1 Jan 12:34 7_bytes    .rw-r--r--  13M cassowary  1 Jan 12:34 13_MiB
+.rw-r--r--    7 cassowary  1 Jan 12:34 7_bytes    .rw-r--r--  14M cassowary  1 Jan 12:34 13_MiB
 .rw-r--r-- 7.2k cassowary  1 Jan 12:34 7_KiB      

+ 3 - 3
xtests/outputs/files_long_grid_3col.ansitxt

@@ -4,10 +4,10 @@
 .rw-r--r--    2 cassowary  1 Jan 12:34 2_bytes    .rw-r--r-- 6.1k cassowary  1 Jan 12:34 6_KiB      .rw-r--r--  10M cassowary  1 Jan 12:34 10_MiB
 .rw-r--r-- 2.0k cassowary  1 Jan 12:34 2_KiB      .rw-r--r-- 6.3M cassowary  1 Jan 12:34 6_MiB      .rw-r--r--   11 cassowary  1 Jan 12:34 11_bytes
 .rw-r--r-- 2.1M cassowary  1 Jan 12:34 2_MiB      .rw-r--r--    7 cassowary  1 Jan 12:34 7_bytes    .rw-r--r--  11k cassowary  1 Jan 12:34 11_KiB
-.rw-r--r--    3 cassowary  1 Jan 12:34 3_bytes    .rw-r--r-- 7.2k cassowary  1 Jan 12:34 7_KiB      .rw-r--r--  11M cassowary  1 Jan 12:34 11_MiB
+.rw-r--r--    3 cassowary  1 Jan 12:34 3_bytes    .rw-r--r-- 7.2k cassowary  1 Jan 12:34 7_KiB      .rw-r--r--  12M cassowary  1 Jan 12:34 11_MiB
 .rw-r--r-- 3.1k cassowary  1 Jan 12:34 3_KiB      .rw-r--r-- 7.3M cassowary  1 Jan 12:34 7_MiB      .rw-r--r--   12 cassowary  1 Jan 12:34 12_bytes
 .rw-r--r-- 3.1M cassowary  1 Jan 12:34 3_MiB      .rw-r--r--    8 cassowary  1 Jan 12:34 8_bytes    .rw-r--r--  12k cassowary  1 Jan 12:34 12_KiB
-.rw-r--r--    4 cassowary  1 Jan 12:34 4_bytes    .rw-r--r-- 8.2k cassowary  1 Jan 12:34 8_KiB      .rw-r--r--  12M cassowary  1 Jan 12:34 12_MiB
+.rw-r--r--    4 cassowary  1 Jan 12:34 4_bytes    .rw-r--r-- 8.2k cassowary  1 Jan 12:34 8_KiB      .rw-r--r--  13M cassowary  1 Jan 12:34 12_MiB
 .rw-r--r-- 4.1k cassowary  1 Jan 12:34 4_KiB      .rw-r--r-- 8.4M cassowary  1 Jan 12:34 8_MiB      .rw-r--r--   13 cassowary  1 Jan 12:34 13_bytes
 .rw-r--r-- 4.2M cassowary  1 Jan 12:34 4_MiB      .rw-r--r--    9 cassowary  1 Jan 12:34 9_bytes    .rw-r--r--  13k cassowary  1 Jan 12:34 13_KiB
-.rw-r--r--    5 cassowary  1 Jan 12:34 5_bytes    .rw-r--r-- 9.2k cassowary  1 Jan 12:34 9_KiB      .rw-r--r--  13M cassowary  1 Jan 12:34 13_MiB
+.rw-r--r--    5 cassowary  1 Jan 12:34 5_bytes    .rw-r--r-- 9.2k cassowary  1 Jan 12:34 9_KiB      .rw-r--r--  14M cassowary  1 Jan 12:34 13_MiB

+ 3 - 3
xtests/outputs/files_long_grid_4col.ansitxt

@@ -1,10 +1,10 @@
 .rw-r--r--    1 cassowary  1 Jan 12:34 1_bytes    .rw-r--r-- 4.1k cassowary  1 Jan 12:34 4_KiB      .rw-r--r-- 7.3M cassowary  1 Jan 12:34 7_MiB       .rw-r--r--  11 cassowary  1 Jan 12:34 11_bytes
 .rw-r--r-- 1.0k cassowary  1 Jan 12:34 1_KiB      .rw-r--r-- 4.2M cassowary  1 Jan 12:34 4_MiB      .rw-r--r--    8 cassowary  1 Jan 12:34 8_bytes     .rw-r--r-- 11k cassowary  1 Jan 12:34 11_KiB
-.rw-r--r-- 1.0M cassowary  1 Jan 12:34 1_MiB      .rw-r--r--    5 cassowary  1 Jan 12:34 5_bytes    .rw-r--r-- 8.2k cassowary  1 Jan 12:34 8_KiB       .rw-r--r-- 11M cassowary  1 Jan 12:34 11_MiB
+.rw-r--r-- 1.0M cassowary  1 Jan 12:34 1_MiB      .rw-r--r--    5 cassowary  1 Jan 12:34 5_bytes    .rw-r--r-- 8.2k cassowary  1 Jan 12:34 8_KiB       .rw-r--r-- 12M cassowary  1 Jan 12:34 11_MiB
 .rw-r--r--    2 cassowary  1 Jan 12:34 2_bytes    .rw-r--r-- 5.1k cassowary  1 Jan 12:34 5_KiB      .rw-r--r-- 8.4M cassowary  1 Jan 12:34 8_MiB       .rw-r--r--  12 cassowary  1 Jan 12:34 12_bytes
 .rw-r--r-- 2.0k cassowary  1 Jan 12:34 2_KiB      .rw-r--r-- 5.2M cassowary  1 Jan 12:34 5_MiB      .rw-r--r--    9 cassowary  1 Jan 12:34 9_bytes     .rw-r--r-- 12k cassowary  1 Jan 12:34 12_KiB
-.rw-r--r-- 2.1M cassowary  1 Jan 12:34 2_MiB      .rw-r--r--    6 cassowary  1 Jan 12:34 6_bytes    .rw-r--r-- 9.2k cassowary  1 Jan 12:34 9_KiB       .rw-r--r-- 12M cassowary  1 Jan 12:34 12_MiB
+.rw-r--r-- 2.1M cassowary  1 Jan 12:34 2_MiB      .rw-r--r--    6 cassowary  1 Jan 12:34 6_bytes    .rw-r--r-- 9.2k cassowary  1 Jan 12:34 9_KiB       .rw-r--r-- 13M cassowary  1 Jan 12:34 12_MiB
 .rw-r--r--    3 cassowary  1 Jan 12:34 3_bytes    .rw-r--r-- 6.1k cassowary  1 Jan 12:34 6_KiB      .rw-r--r-- 9.4M cassowary  1 Jan 12:34 9_MiB       .rw-r--r--  13 cassowary  1 Jan 12:34 13_bytes
 .rw-r--r-- 3.1k cassowary  1 Jan 12:34 3_KiB      .rw-r--r-- 6.3M cassowary  1 Jan 12:34 6_MiB      .rw-r--r--   10 cassowary  1 Jan 12:34 10_bytes    .rw-r--r-- 13k cassowary  1 Jan 12:34 13_KiB
-.rw-r--r-- 3.1M cassowary  1 Jan 12:34 3_MiB      .rw-r--r--    7 cassowary  1 Jan 12:34 7_bytes    .rw-r--r--  10k cassowary  1 Jan 12:34 10_KiB      .rw-r--r-- 13M cassowary  1 Jan 12:34 13_MiB
+.rw-r--r-- 3.1M cassowary  1 Jan 12:34 3_MiB      .rw-r--r--    7 cassowary  1 Jan 12:34 7_bytes    .rw-r--r--  10k cassowary  1 Jan 12:34 10_KiB      .rw-r--r-- 14M cassowary  1 Jan 12:34 13_MiB
 .rw-r--r--    4 cassowary  1 Jan 12:34 4_bytes    .rw-r--r-- 7.2k cassowary  1 Jan 12:34 7_KiB      .rw-r--r--  10M cassowary  1 Jan 12:34 10_MiB      

+ 3 - 3
xtests/outputs/files_long_grid_icons.ansitxt

@@ -30,10 +30,10 @@
 .rw-r--r--  10M cassowary  1 Jan 12:34  10_MiB
 .rw-r--r--   11 cassowary  1 Jan 12:34  11_bytes
 .rw-r--r--  11k cassowary  1 Jan 12:34  11_KiB
-.rw-r--r--  11M cassowary  1 Jan 12:34  11_MiB
+.rw-r--r--  12M cassowary  1 Jan 12:34  11_MiB
 .rw-r--r--   12 cassowary  1 Jan 12:34  12_bytes
 .rw-r--r--  12k cassowary  1 Jan 12:34  12_KiB
-.rw-r--r--  12M cassowary  1 Jan 12:34  12_MiB
+.rw-r--r--  13M cassowary  1 Jan 12:34  12_MiB
 .rw-r--r--   13 cassowary  1 Jan 12:34  13_bytes
 .rw-r--r--  13k cassowary  1 Jan 12:34  13_KiB
-.rw-r--r--  13M cassowary  1 Jan 12:34  13_MiB
+.rw-r--r--  14M cassowary  1 Jan 12:34  13_MiB

+ 3 - 3
xtests/outputs/files_long_header.ansitxt

@@ -31,10 +31,10 @@
 .rw-r--r--   10M cassowary  1 Jan 12:34  10_MiB
 .rw-r--r--    11 cassowary  1 Jan 12:34  11_bytes
 .rw-r--r--   11k cassowary  1 Jan 12:34  11_KiB
-.rw-r--r--   11M cassowary  1 Jan 12:34  11_MiB
+.rw-r--r--   12M cassowary  1 Jan 12:34  11_MiB
 .rw-r--r--    12 cassowary  1 Jan 12:34  12_bytes
 .rw-r--r--   12k cassowary  1 Jan 12:34  12_KiB
-.rw-r--r--   12M cassowary  1 Jan 12:34  12_MiB
+.rw-r--r--   13M cassowary  1 Jan 12:34  12_MiB
 .rw-r--r--    13 cassowary  1 Jan 12:34  13_bytes
 .rw-r--r--   13k cassowary  1 Jan 12:34  13_KiB
-.rw-r--r--   13M cassowary  1 Jan 12:34  13_MiB
+.rw-r--r--   14M cassowary  1 Jan 12:34  13_MiB

+ 3 - 3
xtests/outputs/files_long_icons.ansitxt

@@ -30,10 +30,10 @@
 .rw-r--r--  10M cassowary  1 Jan 12:34  10_MiB
 .rw-r--r--   11 cassowary  1 Jan 12:34  11_bytes
 .rw-r--r--  11k cassowary  1 Jan 12:34  11_KiB
-.rw-r--r--  11M cassowary  1 Jan 12:34  11_MiB
+.rw-r--r--  12M cassowary  1 Jan 12:34  11_MiB
 .rw-r--r--   12 cassowary  1 Jan 12:34  12_bytes
 .rw-r--r--  12k cassowary  1 Jan 12:34  12_KiB
-.rw-r--r--  12M cassowary  1 Jan 12:34  12_MiB
+.rw-r--r--  13M cassowary  1 Jan 12:34  12_MiB
 .rw-r--r--   13 cassowary  1 Jan 12:34  13_bytes
 .rw-r--r--  13k cassowary  1 Jan 12:34  13_KiB
-.rw-r--r--  13M cassowary  1 Jan 12:34  13_MiB
+.rw-r--r--  14M cassowary  1 Jan 12:34  13_MiB

+ 3 - 3
xtests/outputs/files_long_monochrome.ansitxt

@@ -30,10 +30,10 @@
 .rw-r--r--  10M cassowary  1 Jan 12:34 10_MiB
 .rw-r--r--   11 cassowary  1 Jan 12:34 11_bytes
 .rw-r--r--  11k cassowary  1 Jan 12:34 11_KiB
-.rw-r--r--  11M cassowary  1 Jan 12:34 11_MiB
+.rw-r--r--  12M cassowary  1 Jan 12:34 11_MiB
 .rw-r--r--   12 cassowary  1 Jan 12:34 12_bytes
 .rw-r--r--  12k cassowary  1 Jan 12:34 12_KiB
-.rw-r--r--  12M cassowary  1 Jan 12:34 12_MiB
+.rw-r--r--  13M cassowary  1 Jan 12:34 12_MiB
 .rw-r--r--   13 cassowary  1 Jan 12:34 13_bytes
 .rw-r--r--  13k cassowary  1 Jan 12:34 13_KiB
-.rw-r--r--  13M cassowary  1 Jan 12:34 13_MiB
+.rw-r--r--  14M cassowary  1 Jan 12:34 13_MiB

+ 4 - 4
xtests/outputs/files_long_tree_icons.ansitxt

@@ -1,4 +1,4 @@
-drwxrwxr-x    - vagrant    1 Jan 12:34  /testcases/files
+drwxrwxr-x    - vagrant    1 Jan 12:34  /testcases/files
 .rw-r--r--    1 cassowary  1 Jan 12:34 ├──  1_bytes
 .rw-r--r-- 1.0k cassowary  1 Jan 12:34 ├──  1_KiB
 .rw-r--r-- 1.0M cassowary  1 Jan 12:34 ├──  1_MiB
@@ -31,10 +31,10 @@
 .rw-r--r--  10M cassowary  1 Jan 12:34 ├──  10_MiB
 .rw-r--r--   11 cassowary  1 Jan 12:34 ├──  11_bytes
 .rw-r--r--  11k cassowary  1 Jan 12:34 ├──  11_KiB
-.rw-r--r--  11M cassowary  1 Jan 12:34 ├──  11_MiB
+.rw-r--r--  12M cassowary  1 Jan 12:34 ├──  11_MiB
 .rw-r--r--   12 cassowary  1 Jan 12:34 ├──  12_bytes
 .rw-r--r--  12k cassowary  1 Jan 12:34 ├──  12_KiB
-.rw-r--r--  12M cassowary  1 Jan 12:34 ├──  12_MiB
+.rw-r--r--  13M cassowary  1 Jan 12:34 ├──  12_MiB
 .rw-r--r--   13 cassowary  1 Jan 12:34 ├──  13_bytes
 .rw-r--r--  13k cassowary  1 Jan 12:34 ├──  13_KiB
-.rw-r--r--  13M cassowary  1 Jan 12:34 └──  13_MiB
+.rw-r--r--  14M cassowary  1 Jan 12:34 └──  13_MiB

+ 3 - 3
xtests/outputs/files_paths_long_grid_1col.ansitxt

@@ -3,13 +3,13 @@
 .rw-r--r--  10M cassowary  1 Jan 12:34 /testcases/files/10_MiB
 .rw-r--r--   11 cassowary  1 Jan 12:34 /testcases/files/11_bytes
 .rw-r--r--  11k cassowary  1 Jan 12:34 /testcases/files/11_KiB
-.rw-r--r--  11M cassowary  1 Jan 12:34 /testcases/files/11_MiB
+.rw-r--r--  12M cassowary  1 Jan 12:34 /testcases/files/11_MiB
 .rw-r--r--   12 cassowary  1 Jan 12:34 /testcases/files/12_bytes
 .rw-r--r--  12k cassowary  1 Jan 12:34 /testcases/files/12_KiB
-.rw-r--r--  12M cassowary  1 Jan 12:34 /testcases/files/12_MiB
+.rw-r--r--  13M cassowary  1 Jan 12:34 /testcases/files/12_MiB
 .rw-r--r--   13 cassowary  1 Jan 12:34 /testcases/files/13_bytes
 .rw-r--r--  13k cassowary  1 Jan 12:34 /testcases/files/13_KiB
-.rw-r--r--  13M cassowary  1 Jan 12:34 /testcases/files/13_MiB
+.rw-r--r--  14M cassowary  1 Jan 12:34 /testcases/files/13_MiB
 .rw-r--r--    1 cassowary  1 Jan 12:34 /testcases/files/1_bytes
 .rw-r--r-- 1.0k cassowary  1 Jan 12:34 /testcases/files/1_KiB
 .rw-r--r-- 1.0M cassowary  1 Jan 12:34 /testcases/files/1_MiB

+ 3 - 3
xtests/outputs/files_paths_long_grid_2col.ansitxt

@@ -3,13 +3,13 @@
 .rw-r--r--  10M cassowary  1 Jan 12:34 /testcases/files/10_MiB      .rw-r--r-- 4.1k cassowary  1 Jan 12:34 /testcases/files/4_KiB
 .rw-r--r--   11 cassowary  1 Jan 12:34 /testcases/files/11_bytes    .rw-r--r-- 4.2M cassowary  1 Jan 12:34 /testcases/files/4_MiB
 .rw-r--r--  11k cassowary  1 Jan 12:34 /testcases/files/11_KiB      .rw-r--r--    5 cassowary  1 Jan 12:34 /testcases/files/5_bytes
-.rw-r--r--  11M cassowary  1 Jan 12:34 /testcases/files/11_MiB      .rw-r--r-- 5.1k cassowary  1 Jan 12:34 /testcases/files/5_KiB
+.rw-r--r--  12M cassowary  1 Jan 12:34 /testcases/files/11_MiB      .rw-r--r-- 5.1k cassowary  1 Jan 12:34 /testcases/files/5_KiB
 .rw-r--r--   12 cassowary  1 Jan 12:34 /testcases/files/12_bytes    .rw-r--r-- 5.2M cassowary  1 Jan 12:34 /testcases/files/5_MiB
 .rw-r--r--  12k cassowary  1 Jan 12:34 /testcases/files/12_KiB      .rw-r--r--    6 cassowary  1 Jan 12:34 /testcases/files/6_bytes
-.rw-r--r--  12M cassowary  1 Jan 12:34 /testcases/files/12_MiB      .rw-r--r-- 6.1k cassowary  1 Jan 12:34 /testcases/files/6_KiB
+.rw-r--r--  13M cassowary  1 Jan 12:34 /testcases/files/12_MiB      .rw-r--r-- 6.1k cassowary  1 Jan 12:34 /testcases/files/6_KiB
 .rw-r--r--   13 cassowary  1 Jan 12:34 /testcases/files/13_bytes    .rw-r--r-- 6.3M cassowary  1 Jan 12:34 /testcases/files/6_MiB
 .rw-r--r--  13k cassowary  1 Jan 12:34 /testcases/files/13_KiB      .rw-r--r--    7 cassowary  1 Jan 12:34 /testcases/files/7_bytes
-.rw-r--r--  13M cassowary  1 Jan 12:34 /testcases/files/13_MiB      .rw-r--r-- 7.2k cassowary  1 Jan 12:34 /testcases/files/7_KiB
+.rw-r--r--  14M cassowary  1 Jan 12:34 /testcases/files/13_MiB      .rw-r--r-- 7.2k cassowary  1 Jan 12:34 /testcases/files/7_KiB
 .rw-r--r--    1 cassowary  1 Jan 12:34 /testcases/files/1_bytes     .rw-r--r-- 7.3M cassowary  1 Jan 12:34 /testcases/files/7_MiB
 .rw-r--r-- 1.0k cassowary  1 Jan 12:34 /testcases/files/1_KiB       .rw-r--r--    8 cassowary  1 Jan 12:34 /testcases/files/8_bytes
 .rw-r--r-- 1.0M cassowary  1 Jan 12:34 /testcases/files/1_MiB       .rw-r--r-- 8.2k cassowary  1 Jan 12:34 /testcases/files/8_KiB

+ 3 - 3
xtests/outputs/files_paths_long_grid_3col.ansitxt

@@ -3,11 +3,11 @@
 .rw-r--r-- 10M cassowary  1 Jan 12:34 /testcases/files/10_MiB      .rw-r--r--    2 cassowary  1 Jan 12:34 /testcases/files/2_bytes    .rw-r--r-- 6.1k cassowary  1 Jan 12:34 /testcases/files/6_KiB
 .rw-r--r--  11 cassowary  1 Jan 12:34 /testcases/files/11_bytes    .rw-r--r-- 2.0k cassowary  1 Jan 12:34 /testcases/files/2_KiB      .rw-r--r-- 6.3M cassowary  1 Jan 12:34 /testcases/files/6_MiB
 .rw-r--r-- 11k cassowary  1 Jan 12:34 /testcases/files/11_KiB      .rw-r--r-- 2.1M cassowary  1 Jan 12:34 /testcases/files/2_MiB      .rw-r--r--    7 cassowary  1 Jan 12:34 /testcases/files/7_bytes
-.rw-r--r-- 11M cassowary  1 Jan 12:34 /testcases/files/11_MiB      .rw-r--r--    3 cassowary  1 Jan 12:34 /testcases/files/3_bytes    .rw-r--r-- 7.2k cassowary  1 Jan 12:34 /testcases/files/7_KiB
+.rw-r--r-- 12M cassowary  1 Jan 12:34 /testcases/files/11_MiB      .rw-r--r--    3 cassowary  1 Jan 12:34 /testcases/files/3_bytes    .rw-r--r-- 7.2k cassowary  1 Jan 12:34 /testcases/files/7_KiB
 .rw-r--r--  12 cassowary  1 Jan 12:34 /testcases/files/12_bytes    .rw-r--r-- 3.1k cassowary  1 Jan 12:34 /testcases/files/3_KiB      .rw-r--r-- 7.3M cassowary  1 Jan 12:34 /testcases/files/7_MiB
 .rw-r--r-- 12k cassowary  1 Jan 12:34 /testcases/files/12_KiB      .rw-r--r-- 3.1M cassowary  1 Jan 12:34 /testcases/files/3_MiB      .rw-r--r--    8 cassowary  1 Jan 12:34 /testcases/files/8_bytes
-.rw-r--r-- 12M cassowary  1 Jan 12:34 /testcases/files/12_MiB      .rw-r--r--    4 cassowary  1 Jan 12:34 /testcases/files/4_bytes    .rw-r--r-- 8.2k cassowary  1 Jan 12:34 /testcases/files/8_KiB
+.rw-r--r-- 13M cassowary  1 Jan 12:34 /testcases/files/12_MiB      .rw-r--r--    4 cassowary  1 Jan 12:34 /testcases/files/4_bytes    .rw-r--r-- 8.2k cassowary  1 Jan 12:34 /testcases/files/8_KiB
 .rw-r--r--  13 cassowary  1 Jan 12:34 /testcases/files/13_bytes    .rw-r--r-- 4.1k cassowary  1 Jan 12:34 /testcases/files/4_KiB      .rw-r--r-- 8.4M cassowary  1 Jan 12:34 /testcases/files/8_MiB
 .rw-r--r-- 13k cassowary  1 Jan 12:34 /testcases/files/13_KiB      .rw-r--r-- 4.2M cassowary  1 Jan 12:34 /testcases/files/4_MiB      .rw-r--r--    9 cassowary  1 Jan 12:34 /testcases/files/9_bytes
-.rw-r--r-- 13M cassowary  1 Jan 12:34 /testcases/files/13_MiB      .rw-r--r--    5 cassowary  1 Jan 12:34 /testcases/files/5_bytes    .rw-r--r-- 9.2k cassowary  1 Jan 12:34 /testcases/files/9_KiB
+.rw-r--r-- 14M cassowary  1 Jan 12:34 /testcases/files/13_MiB      .rw-r--r--    5 cassowary  1 Jan 12:34 /testcases/files/5_bytes    .rw-r--r-- 9.2k cassowary  1 Jan 12:34 /testcases/files/9_KiB
 .rw-r--r--   1 cassowary  1 Jan 12:34 /testcases/files/1_bytes     .rw-r--r-- 5.1k cassowary  1 Jan 12:34 /testcases/files/5_KiB      .rw-r--r-- 9.4M cassowary  1 Jan 12:34 /testcases/files/9_MiB

+ 2 - 0
xtests/outputs/files_paths_long_grid_header_1file.ansitxt

@@ -0,0 +1,2 @@
+Permissions Size User      Date Modified Name
+.rw-r--r--    10 cassowary  1 Jan 12:34  /testcases/files/10_bytes

+ 2 - 0
xtests/outputs/files_paths_long_grid_header_2files.ansitxt

@@ -0,0 +1,2 @@
+Permissions Size User      Date Modified Name                         Permissions Size User      Date Modified Name
+.rw-r--r--    10 cassowary  1 Jan 12:34  /testcases/files/10_bytes    .rw-r--r--   10k cassowary  1 Jan 12:34  /testcases/files/10_KiB

+ 1 - 1
xtests/outputs/files_tree_icons.ansitxt

@@ -1,4 +1,4 @@
- /testcases/files
+ /testcases/files
 ├──  1_bytes
 ├──  1_KiB
 ├──  1_MiB

+ 2 - 0
xtests/outputs/help.ansitxt

@@ -16,6 +16,7 @@ DISPLAY OPTIONS
   --colo[u]r=WHEN    when to use terminal colours (always, auto, never)
   --colo[u]r-scale   highlight levels of file sizes distinctly
   --icons            display icons
+  --no-icons         don't display icons (always overrides --icons)
 
 FILTERING AND SORTING OPTIONS
   -a, --all                  show hidden and 'dot' files
@@ -39,6 +40,7 @@ LONG VIEW OPTIONS
   -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
+  -n, --numeric        list numeric user and group IDs
   -S, --blocks         show number of file system blocks
   -t, --time FIELD     which timestamp field to list (modified, accessed, created)
   -u, --accessed       use the accessed timestamp field

+ 9 - 9
xtests/outputs/links_oneline_icons.ansitxt

@@ -1,10 +1,10 @@
- broken -> nowhere
- current_dir -> .
- forbidden -> /proc/1/root
- itself -> itself
- parent_dir -> ..
- root -> /
+ broken -> nowhere
+ current_dir -> .
+ forbidden -> /proc/1/root
+ itself -> itself
+ parent_dir -> ..
+ root -> /
  some_file
- some_file_absolute -> /testcases/links/some_file
- some_file_relative -> some_file
- usr -> /usr
+ some_file_absolute -> /testcases/links/some_file
+ some_file_relative -> some_file
+ usr -> /usr

+ 9 - 9
xtests/outputs/links_oneline_icons_width0.ansitxt

@@ -1,10 +1,10 @@
-broken -> nowhere
-current_dir -> .
-forbidden -> /proc/1/root
-itself -> itself
-parent_dir -> ..
-root -> /
+broken -> nowhere
+current_dir -> .
+forbidden -> /proc/1/root
+itself -> itself
+parent_dir -> ..
+root -> /
 some_file
-some_file_absolute -> /testcases/links/some_file
-some_file_relative -> some_file
-usr -> /usr
+some_file_absolute -> /testcases/links/some_file
+some_file_relative -> some_file
+usr -> /usr

+ 9 - 9
xtests/outputs/links_oneline_icons_width2.ansitxt

@@ -1,10 +1,10 @@
-  broken -> nowhere
-  current_dir -> .
-  forbidden -> /proc/1/root
-  itself -> itself
-  parent_dir -> ..
-  root -> /
+  broken -> nowhere
+  current_dir -> .
+  forbidden -> /proc/1/root
+  itself -> itself
+  parent_dir -> ..
+  root -> /
   some_file
-  some_file_absolute -> /testcases/links/some_file
-  some_file_relative -> some_file
-  usr -> /usr
+  some_file_absolute -> /testcases/links/some_file
+  some_file_relative -> some_file
+  usr -> /usr

+ 9 - 9
xtests/outputs/links_oneline_icons_width3.ansitxt

@@ -1,10 +1,10 @@
-   broken -> nowhere
-   current_dir -> .
-   forbidden -> /proc/1/root
-   itself -> itself
-   parent_dir -> ..
-   root -> /
+   broken -> nowhere
+   current_dir -> .
+   forbidden -> /proc/1/root
+   itself -> itself
+   parent_dir -> ..
+   root -> /
    some_file
-   some_file_absolute -> /testcases/links/some_file
-   some_file_relative -> some_file
-   usr -> /usr
+   some_file_absolute -> /testcases/links/some_file
+   some_file_relative -> some_file
+   usr -> /usr

+ 6 - 6
xtests/outputs/permissions_oneline_icons.ansitxt

@@ -5,18 +5,18 @@
  010
  020
  040
- 100
+ 100
  200
  400
  644
- 755
- 777
+ 755
+ 777
  1000
  1001
  2000
  2010
  4000
- 4100
+ 4100
  7666
- 7777
- forbidden-directory
+ 7777
+ forbidden-directory

+ 5 - 0
xtests/run.sh

@@ -7,6 +7,11 @@ case "$1" in
   *)           exa_binary="$HOME/target/debug/exa" ;;
 esac
 
+if [ ! -e /vagrant ]; then
+  echo "The extended tests must be run on the Vagrant machine."
+  exit 1
+fi
+
 if [ ! -f "$exa_binary" ]; then
   echo "exa binary ($exa_binary) does not exist"
   if [ "$1" != "--release" ]; then echo -e "create it first with \033[1;32mbuild-exa\033[0m or \033[1;32mb\033[0m"; fi