Explorar el Código

Merge branch 'common-view-fields'

Benjamin Sago hace 8 años
padre
commit
c7f18873fd

+ 49 - 28
Cargo.lock

@@ -7,10 +7,11 @@ dependencies = [
  "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "git2 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "locale 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "natord 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "num_cpus 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "number_prefix 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "scoped_threadpool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "term_grid 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -39,7 +40,7 @@ name = "cmake"
 version = "0.1.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -48,7 +49,7 @@ version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "iso8601 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "locale 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "num 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
  "pad 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -56,7 +57,7 @@ dependencies = [
 
 [[package]]
 name = "gcc"
-version = "0.3.50"
+version = "0.3.51"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -70,9 +71,9 @@ version = "0.6.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "libgit2-sys 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "url 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -87,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -98,9 +99,14 @@ dependencies = [
  "nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "lazy_static"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "libc"
-version = "0.2.23"
+version = "0.2.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -109,20 +115,21 @@ version = "0.6.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cmake 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
- "gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
- "libz-sys 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libz-sys 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "libz-sys"
-version = "1.0.13"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -130,7 +137,7 @@ name = "locale"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -216,10 +223,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "num_cpus"
-version = "1.5.1"
+version = "1.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -238,6 +245,11 @@ dependencies = [
  "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "percent-encoding"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "pkg-config"
 version = "0.3.9"
@@ -248,7 +260,7 @@ name = "rand"
 version = "0.3.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -279,7 +291,7 @@ dependencies = [
 
 [[package]]
 name = "unicode-normalization"
-version = "0.1.4"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -289,11 +301,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "url"
-version = "1.4.1"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "idna 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -301,9 +314,14 @@ name = "users"
 version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "vcpkg"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "zoneinfo_compiled"
 version = "0.2.1"
@@ -319,15 +337,16 @@ dependencies = [
 "checksum byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96c8b41881888cc08af32d47ac4edd52bc7fa27fef774be47a92443756451304"
 "checksum cmake 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b8ebbb35d3dc9cd09497168f33de1acb79b265d350ab0ac34133b98f8509af1f"
 "checksum datetime 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2d425bf1f6bbd57cf833081c1e60ac294fd74e7edd66acc91c3fca2e496bcee9"
-"checksum gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)" = "5f837c392f2ea61cb1576eac188653df828c861b7137d74ea4a5caa89621f9e6"
+"checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a"
 "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685"
 "checksum git2 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa01936ac96555c083c0e8553f672616274408d9d3fc5b8696603fbf63ff43ee"
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
 "checksum idna 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2233d4940b1f19f0418c158509cd7396b8d70a5db5705ce410914dc8fa603b37"
 "checksum iso8601 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "11dc464f8c6f17595d191447c9c6559298b2d023d6f846a4a23ac7ea3c46c477"
-"checksum libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e7eb6b826bfc1fdea7935d46556250d1799b7fe2d9f7951071f4291710665e3e"
+"checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf"
+"checksum libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)" = "38f5c2b18a287cf78b4097db62e20f43cace381dc76ae5c0a3073067f78b7ddc"
 "checksum libgit2-sys 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "df18a822100352d9863b302faf6f8f25c0e77f0e60feb40e5dbe1238b7f13b1d"
-"checksum libz-sys 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e5ee912a45d686d393d5ac87fac15ba0ba18daae14e8e7543c63ebf7fb7e970c"
+"checksum libz-sys 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "3fdd64ef8ee652185674455c1d450b83cbc8ad895625d543b5324d923f82e4d8"
 "checksum locale 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5fdbe492a9c0238da900a1165c42fc5067161ce292678a6fe80921f30fe307fd"
 "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
 "checksum natord 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
@@ -339,17 +358,19 @@ dependencies = [
 "checksum num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d1891bd7b936f12349b7d1403761c8a0b85a18b148e9da4429d5d102c1a41e"
 "checksum num-rational 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "33c881e104a26e1accc09449374c095ff2312c8e0c27fab7bbefe16eac7c776d"
 "checksum num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "1708c0628602a98b52fad936cf3edb9a107af06e52e49fdf0707e884456a6af6"
-"checksum num_cpus 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e416ba127a4bb3ff398cb19546a8d0414f73352efe2857f4060d36f5fe5983a"
+"checksum num_cpus 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aec53c34f2d0247c5ca5d32cca1478762f301740468ee9ee6dcb7a0dd7a0c584"
 "checksum number_prefix 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "59a14be9c211cb9c602bad35ac99f41e9a84b44d71b8cbd3040e3bd02a214902"
 "checksum pad 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d1bf3336e626b898e7263790d432a711d4277e22faea20dd9f70e0cab268fa58"
+"checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356"
 "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903"
 "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
 "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
 "checksum scoped_threadpool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef399c8893e8cb7aa9696e895427fab3a6bf265977bb96e126f24ddd2cda85a"
 "checksum term_grid 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ccc202875496cf72a683a1ecd66f0742a830e73c202bdbd21867d73dfaac8343"
 "checksum unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a6a2c4e3710edd365cd7e78383153ed739fa31af19f9172f72d3575060f5a43a"
-"checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff"
+"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f"
 "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
-"checksum url 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2ba3456fbe5c0098cb877cf08b92b76c3e18e0be9e47c35b487220d377d24e"
+"checksum url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb819346883532a271eb626deb43c4a1bb4c4dd47c519bd78137c3e72a4fe27"
 "checksum users 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7ae8fdf783cb9652109c99886459648feb92ecc749e6b8e7930f6decba74c7c"
+"checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b"
 "checksum zoneinfo_compiled 0.2.1 (git+https://github.com/rust-datetime/zoneinfo-compiled.git)" = "<none>"

+ 1 - 0
Cargo.toml

@@ -16,6 +16,7 @@ ansi_term = "0.8.0"
 datetime = "0.4.3"
 getopts = "0.2.14"
 glob = "0.2"
+lazy_static = "0.2"
 libc = "0.2.9"
 locale = "0.2.1"
 natord = "1.0.7"

+ 13 - 7
src/exa.rs

@@ -18,6 +18,10 @@ extern crate zoneinfo_compiled;
 
 #[cfg(feature="git")] extern crate git2;
 
+#[macro_use]
+extern crate lazy_static;
+
+
 use std::ffi::OsStr;
 use std::io::{stderr, Write, Result as IOResult};
 use std::path::{Component, Path};
@@ -25,9 +29,9 @@ use std::path::{Component, Path};
 use ansi_term::{ANSIStrings, Style};
 
 use fs::{Dir, File};
-use options::{Options, View};
+use options::{Options, View, Mode};
 pub use options::Misfire;
-use output::escape;
+use output::{escape, lines, grid, grid_details, details};
 
 mod fs;
 mod info;
@@ -164,11 +168,13 @@ impl<'w, W: Write + 'w> Exa<'w, W> {
     /// printing differently...
     fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File>) -> IOResult<()> {
         if !files.is_empty() {
-            match self.options.view {
-                View::Grid(ref g)         => g.view(&files, self.writer),
-                View::Details(ref d)      => d.view(dir, files, self.writer),
-                View::GridDetails(ref gd) => gd.view(dir, files, self.writer),
-                View::Lines(ref l)        => l.view(files, self.writer),
+            let View { ref mode, ref colours, classify } = self.options.view;
+
+            match *mode {
+                Mode::Lines                  => lines::Render { files, colours, classify }.render(self.writer),
+                Mode::Grid(ref opts)         => grid::Render { files, colours, classify, opts }.render(self.writer),
+                Mode::Details(ref opts)      => details::Render { dir, files, colours, classify, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.render(self.writer),
+                Mode::GridDetails(ref grid, ref details) => grid_details::Render { dir, files, colours, classify, grid, details }.render(self.writer),
             }
         }
         else {

+ 6 - 6
src/options/mod.rs

@@ -3,7 +3,7 @@ use std::ffi::OsStr;
 use getopts;
 
 use fs::feature::xattr;
-use output::{Details, GridDetails};
+use output::details;
 
 mod dir_action;
 pub use self::dir_action::{DirAction, RecurseOptions};
@@ -18,7 +18,7 @@ mod misfire;
 pub use self::misfire::Misfire;
 
 mod view;
-pub use self::view::View;
+pub use self::view::{View, Mode};
 
 
 /// These **options** represent a parsed, error-checked versions of the
@@ -123,9 +123,9 @@ impl Options {
     /// status column. It’s only worth trying to discover a repository if the
     /// results will end up being displayed.
     pub fn should_scan_for_git(&self) -> bool {
-        match self.view {
-            View::Details(Details { columns: Some(cols), .. }) |
-            View::GridDetails(GridDetails { details: Details { columns: Some(cols), .. }, .. }) => cols.should_scan_for_git(),
+        match self.view.mode {
+            Mode::Details(details::Options { columns: Some(cols), .. }) |
+            Mode::GridDetails(_, details::Options { columns: Some(cols), .. }) => cols.should_scan_for_git(),
             _ => false,
         }
     }
@@ -135,7 +135,7 @@ impl Options {
     fn deduce(matches: &getopts::Matches) -> Result<Options, Misfire> {
         let dir_action = DirAction::deduce(matches)?;
         let filter = FileFilter::deduce(matches)?;
-        let view = View::deduce(matches, filter.clone(), dir_action)?;
+        let view = View::deduce(matches)?;
 
         Ok(Options { dir_action, view, filter })
     }

+ 75 - 80
src/options/view.rs

@@ -3,32 +3,47 @@ use std::env::var_os;
 use getopts;
 
 use output::Colours;
-use output::{Grid, Details, GridDetails, Lines};
+use output::{grid, details};
 use output::column::{Columns, TimeTypes, SizeFormat};
 use output::file_name::Classify;
-use options::{FileFilter, DirAction, Misfire};
-use term::dimensions;
+use options::Misfire;
 use fs::feature::xattr;
 
 
 /// The **view** contains all information about how to format output.
 #[derive(PartialEq, Debug, Clone)]
-pub enum View {
-    Details(Details),
-    Grid(Grid),
-    GridDetails(GridDetails),
-    Lines(Lines),
+pub struct View {
+    pub mode: Mode,
+    pub colours: Colours,
+    pub classify: Classify,
 }
 
 impl View {
 
     /// Determine which view to use and all of that view’s arguments.
-    pub fn deduce(matches: &getopts::Matches, filter: FileFilter, dir_action: DirAction) -> Result<View, Misfire> {
-        use options::misfire::Misfire::*;
+    pub fn deduce(matches: &getopts::Matches) -> Result<View, Misfire> {
+        let mode     = Mode::deduce(matches)?;
+        let colours  = Colours::deduce(matches)?;
+        let classify = Classify::deduce(matches);
+        Ok(View { mode, colours, classify })
+    }
+}
 
-        let colour_scale = || {
-            matches.opt_present("color-scale") || matches.opt_present("colour-scale")
-        };
+
+/// The **mode** is the “type” of output.
+#[derive(PartialEq, Debug, Clone)]
+pub enum Mode {
+    Grid(grid::Options),
+    Details(details::Options),
+    GridDetails(grid::Options, details::Options),
+    Lines,
+}
+
+impl Mode {
+
+    /// Determine the mode from the command-line arguments.
+    pub fn deduce(matches: &getopts::Matches) -> Result<Mode, Misfire> {
+        use options::misfire::Misfire::*;
 
         let long = || {
             if matches.opt_present("across") && !matches.opt_present("grid") {
@@ -38,31 +53,11 @@ impl View {
                 Err(Useless("oneline", true, "long"))
             }
             else {
-                let term_colours = TerminalColours::deduce(matches)?;
-                let colours = match term_colours {
-                    TerminalColours::Always    => Colours::colourful(colour_scale()),
-                    TerminalColours::Never     => Colours::plain(),
-                    TerminalColours::Automatic => {
-                        if dimensions().is_some() {
-                            Colours::colourful(colour_scale())
-                        }
-                        else {
-                            Colours::plain()
-                        }
-                    },
-                };
-
-                let details = Details {
+                Ok(details::Options {
                     columns: Some(Columns::deduce(matches)?),
                     header: matches.opt_present("header"),
-                    recurse: dir_action.recurse_options(),
-                    filter: filter.clone(),
                     xattr: xattr::ENABLED && matches.opt_present("extended"),
-                    colours: colours,
-                    classify: Classify::deduce(matches),
-                };
-
-                Ok(details)
+                })
             }
         };
 
@@ -88,47 +83,31 @@ impl View {
         };
 
         let other_options_scan = || {
-            let classify     = Classify::deduce(matches);
-            let term_colours = TerminalColours::deduce(matches)?;
-            let term_width   = TerminalWidth::deduce()?;
-
-            if let Some(&width) = term_width.as_ref() {
-                let colours = match term_colours {
-                    TerminalColours::Always     |
-                    TerminalColours::Automatic  => Colours::colourful(colour_scale()),
-                    TerminalColours::Never      => Colours::plain(),
-                };
-
+            if let Some(width) = TerminalWidth::deduce()?.width() {
                 if matches.opt_present("oneline") {
                     if matches.opt_present("across") {
                         Err(Useless("across", true, "oneline"))
                     }
                     else {
-                        Ok(View::Lines(Lines { colours, classify }))
+                        Ok(Mode::Lines)
                     }
                 }
                 else if matches.opt_present("tree") {
-                    let details = Details {
+                    let details = details::Options {
                         columns: None,
                         header: false,
-                        recurse: dir_action.recurse_options(),
-                        filter: filter.clone(),  // TODO: clone
                         xattr: false,
-                        colours: colours,
-                        classify: classify,
                     };
 
-                    Ok(View::Details(details))
+                    Ok(Mode::Details(details))
                 }
                 else {
-                    let grid = Grid {
+                    let grid = grid::Options {
                         across: matches.opt_present("across"),
                         console_width: width,
-                        colours: colours,
-                        classify: classify,
                     };
 
-                    Ok(View::Grid(grid))
+                    Ok(Mode::Grid(grid))
                 }
             }
             else {
@@ -136,42 +115,31 @@ impl View {
                 // as the program’s stdout being connected to a file, then
                 // fallback to the lines view.
 
-                let colours = match term_colours {
-                    TerminalColours::Always    => Colours::colourful(colour_scale()),
-                    TerminalColours::Never | TerminalColours::Automatic => Colours::plain(),
-                };
-
                 if matches.opt_present("tree") {
-                    let details = Details {
+                    let details = details::Options {
                         columns: None,
                         header: false,
-                        recurse: dir_action.recurse_options(),
-                        filter: filter.clone(),
                         xattr: false,
-                        colours: colours,
-                        classify: classify,
                     };
 
-                    Ok(View::Details(details))
+                    Ok(Mode::Details(details))
                 }
                 else {
-                    Ok(View::Lines(Lines { colours, classify }))
+                    Ok(Mode::Lines)
                 }
             }
         };
 
         if matches.opt_present("long") {
             let details = long()?;
-
             if matches.opt_present("grid") {
-                match other_options_scan() {
-                    Ok(View::Grid(grid)) => return Ok(View::GridDetails(GridDetails { grid, details })),
-                    Ok(lines)            => return Ok(lines),
-                    Err(e)               => return Err(e),
+                match other_options_scan()? {
+                    Mode::Grid(grid)  => return Ok(Mode::GridDetails(grid, details)),
+                    others            => return Ok(others),
                 };
             }
             else {
-                return Ok(View::Details(details));
+                return Ok(Mode::Details(details));
             }
         }
 
@@ -208,7 +176,7 @@ impl TerminalWidth {
                 Err(e)     => Err(Misfire::FailedParse(e)),
             }
         }
-        else if let Some((width, _)) = dimensions() {
+        else if let Some(width) = *TERM_WIDTH {
             Ok(TerminalWidth::Terminal(width))
         }
         else {
@@ -216,11 +184,11 @@ impl TerminalWidth {
         }
     }
 
-    fn as_ref(&self) -> Option<&usize> {
+    fn width(&self) -> Option<usize> {
         match *self {
-            TerminalWidth::Set(ref width)
-            | TerminalWidth::Terminal(ref width)    => Some(width),
-            TerminalWidth::Unset                    => None,
+            TerminalWidth::Set(width)       |
+            TerminalWidth::Terminal(width)  => Some(width),
+            TerminalWidth::Unset            => None,
         }
     }
 }
@@ -359,6 +327,22 @@ impl TerminalColours {
 }
 
 
+impl Colours {
+    fn deduce(matches: &getopts::Matches) -> Result<Colours, Misfire> {
+        use self::TerminalColours::*;
+
+        let tc = TerminalColours::deduce(matches)?;
+        if tc == Always || (tc == Automatic && TERM_WIDTH.is_some()) {
+            let scale = matches.opt_present("color-scale") || matches.opt_present("colour-scale");
+            Ok(Colours::colourful(scale))
+        }
+        else {
+            Ok(Colours::plain())
+        }
+    }
+}
+
+
 
 impl Classify {
     fn deduce(matches: &getopts::Matches) -> Classify {
@@ -366,3 +350,14 @@ impl Classify {
                                       else { Classify::JustFilenames }
     }
 }
+
+
+// Gets, then caches, the width of the terminal that exa is running in.
+// This gets used multiple times above, with no real guarantee of order,
+// so it’s easier to just cache it the first time it runs.
+lazy_static! {
+    static ref TERM_WIDTH: Option<usize> = {
+        use term::dimensions;
+        dimensions().map(|t| t.0)
+    };
+}

+ 53 - 51
src/output/details.rs

@@ -113,33 +113,18 @@ use output::file_name::{FileName, LinkStyle, Classify};
 /// Almost all the heavy lifting is done in a Table object, which handles the
 /// columns for each row.
 #[derive(PartialEq, Debug, Clone, Default)]
-pub struct Details {
+pub struct Options {
 
     /// A Columns object that says which columns should be included in the
     /// output in the general case. Directories themselves can pick which
     /// columns are *added* to this list, such as the Git column.
     pub columns: Option<Columns>,
 
-    /// Whether to recurse through directories with a tree view, and if so,
-    /// which options to use. This field is only relevant here if the `tree`
-    /// field of the RecurseOptions is `true`.
-    pub recurse: Option<RecurseOptions>,
-
-    /// How to sort and filter the files after getting their details.
-    pub filter: FileFilter,
-
     /// Whether to show a header line or not.
     pub header: bool,
 
     /// Whether to show each file's extended attributes.
     pub xattr: bool,
-
-    /// The colours to use to display information in the table, including the
-    /// colour of the tree view symbols.
-    pub colours: Colours,
-
-    /// Whether to show a file type indiccator.
-    pub classify: Classify,
 }
 
 /// The **environment** struct contains any data that could change between
@@ -224,16 +209,30 @@ fn determine_time_zone() -> TZResult<TimeZone> {
     TimeZone::from_file("/etc/localtime")
 }
 
-impl Details {
 
-    /// Print the details of the given vector of files -- all of which will
-    /// have been read from the given directory, if present -- to stdout.
-    pub fn view<W: Write>(&self, dir: Option<&Dir>, files: Vec<File>, w: &mut W) -> IOResult<()> {
+pub struct Render<'a> {
+    pub dir: Option<&'a Dir>,
+    pub files: Vec<File<'a>>,
+    pub colours: &'a Colours,
+    pub classify: Classify,
+    pub opts: &'a Options,
+
+    /// Whether to recurse through directories with a tree view, and if so,
+    /// which options to use. This field is only relevant here if the `tree`
+    /// field of the RecurseOptions is `true`.
+    pub recurse: Option<RecurseOptions>,
+
+    /// How to sort and filter the files after getting their details.
+    pub filter: &'a FileFilter,
+}
+
+impl<'a> Render<'a> {
+    pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> {
 
         // First, transform the Columns object into a vector of columns for
         // the current directory.
-        let columns_for_dir = match self.columns {
-            Some(cols) => cols.for_dir(dir),
+        let columns_for_dir = match self.opts.columns {
+            Some(cols) => cols.for_dir(self.dir),
             None => Vec::new(),
         };
 
@@ -243,16 +242,18 @@ impl Details {
         // Build the table to put rows in.
         let mut table = Table {
             columns: &*columns_for_dir,
-            opts: self,
+            colours: self.colours,
+            classify: self.classify,
+            xattr: self.opts.xattr,
             env: env,
             rows: Vec::new(),
         };
 
         // Next, add a header if the user requests it.
-        if self.header { table.add_header() }
+        if self.opts.header { table.add_header() }
 
         // Then add files to the table and print it out.
-        self.add_files_to_table(&mut table, files, 0);
+        self.add_files_to_table(&mut table, &self.files, 0);
         for cell in table.print_table() {
             writeln!(w, "{}", cell.strings())?;
         }
@@ -262,7 +263,7 @@ impl Details {
 
     /// Adds files to the table, possibly recursively. This is easily
     /// parallelisable, and uses a pool of threads.
-    fn add_files_to_table<'dir, U: Users+Groups+Send>(&self, mut table: &mut Table<U>, src: Vec<File<'dir>>, depth: usize) {
+    fn add_files_to_table<'dir, U: Users+Groups+Send>(&self, mut table: &mut Table<U>, src: &Vec<File<'dir>>, depth: usize) {
         use num_cpus;
         use scoped_threadpool::Pool;
         use std::sync::{Arc, Mutex};
@@ -276,12 +277,12 @@ impl Details {
             xattrs:  Vec<Attribute>,
             errors:  Vec<(IOError, Option<PathBuf>)>,
             dir:     Option<Dir>,
-            file:    File<'a>,
+            file:    &'a File<'a>,
         }
 
         impl<'a> AsRef<File<'a>> for Egg<'a> {
             fn as_ref(&self) -> &File<'a> {
-                &self.file
+                self.file
             }
         }
 
@@ -306,7 +307,7 @@ impl Details {
 
                     let cells = table.cells_for_file(&file, !xattrs.is_empty());
 
-                    if !table.opts.xattr {
+                    if !table.xattr {
                         xattrs.clear();
                     }
 
@@ -336,7 +337,7 @@ impl Details {
             let row = Row {
                 depth:    depth,
                 cells:    Some(egg.cells),
-                name:     FileName::new(&egg.file, LinkStyle::FullLinkPaths, self.classify, &self.colours).paint().promote(),
+                name:     FileName::new(&egg.file, LinkStyle::FullLinkPaths, table.classify, table.colours).paint().promote(),
                 last:     index == num_eggs - 1,
             };
 
@@ -361,7 +362,7 @@ impl Details {
                         table.add_error(&error, depth + 1, false, path);
                     }
 
-                    self.add_files_to_table(table, files, depth + 1);
+                    self.add_files_to_table(table, &files, depth + 1);
                     continue;
                 }
             }
@@ -420,9 +421,10 @@ impl Row {
 /// directories.
 pub struct Table<'a, U: 'a> { // where U: Users+Groups
     pub rows: Vec<Row>,
-
     pub columns: &'a [Column],
-    pub opts: &'a Details,
+    pub colours: &'a Colours,
+    pub xattr: bool,
+    pub classify: Classify,
     pub env: Arc<Environment<U>>,
 }
 
@@ -434,8 +436,8 @@ impl<'a, U: Users+Groups+'a> Table<'a, U> {
     pub fn add_header(&mut self) {
         let row = Row {
             depth:    0,
-            cells:    Some(self.columns.iter().map(|c| TextCell::paint_str(self.opts.colours.header, c.header())).collect()),
-            name:     TextCell::paint_str(self.opts.colours.header, "Name"),
+            cells:    Some(self.columns.iter().map(|c| TextCell::paint_str(self.colours.header, c.header())).collect()),
+            name:     TextCell::paint_str(self.colours.header, "Name"),
             last:     false,
         };
 
@@ -451,7 +453,7 @@ impl<'a, U: Users+Groups+'a> Table<'a, U> {
         let row = Row {
             depth:    depth,
             cells:    None,
-            name:     TextCell::paint(self.opts.colours.broken_arrow, error_message),
+            name:     TextCell::paint(self.colours.broken_arrow, error_message),
             last:     last,
         };
 
@@ -462,15 +464,15 @@ impl<'a, U: Users+Groups+'a> Table<'a, U> {
         let row = Row {
             depth:    depth,
             cells:    None,
-            name:     TextCell::paint(self.opts.colours.perms.attribute, format!("{} (len {})", xattr.name, xattr.size)),
+            name:     TextCell::paint(self.colours.perms.attribute, format!("{} (len {})", xattr.name, xattr.size)),
             last:     last,
         };
 
         self.rows.push(row);
     }
 
-    pub fn filename(&self, file: File, links: LinkStyle) -> TextCellContents {
-        FileName::new(&file, links, self.opts.classify, &self.opts.colours).paint()
+    pub fn filename(&self, file: &File, links: LinkStyle) -> TextCellContents {
+        FileName::new(file, links, self.classify, &self.colours).paint()
     }
 
     pub fn add_file_with_cells(&mut self, cells: Vec<TextCell>, name_cell: TextCell, depth: usize, last: bool) {
@@ -504,17 +506,17 @@ impl<'a, U: Users+Groups+'a> Table<'a, U> {
         use output::column::TimeType::*;
 
         match *column {
-            Column::Permissions          => self.permissions_plus(file, xattrs).render(&self.opts.colours),
-            Column::FileSize(fmt)        => file.size().render(&self.opts.colours, fmt, &self.env.numeric),
-            Column::Timestamp(Modified)  => file.modified_time().render(&self.opts.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year),
-            Column::Timestamp(Created)   => file.created_time().render( &self.opts.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year),
-            Column::Timestamp(Accessed)  => file.accessed_time().render(&self.opts.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year),
-            Column::HardLinks            => file.links().render(&self.opts.colours, &self.env.numeric),
-            Column::Inode                => file.inode().render(&self.opts.colours),
-            Column::Blocks               => file.blocks().render(&self.opts.colours),
-            Column::User                 => file.user().render(&self.opts.colours, &*self.env.lock_users()),
-            Column::Group                => file.group().render(&self.opts.colours, &*self.env.lock_users()),
-            Column::GitStatus            => file.git_status().render(&self.opts.colours),
+            Column::Permissions          => self.permissions_plus(file, xattrs).render(&self.colours),
+            Column::FileSize(fmt)        => file.size().render(&self.colours, fmt, &self.env.numeric),
+            Column::Timestamp(Modified)  => file.modified_time().render(&self.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year),
+            Column::Timestamp(Created)   => file.created_time().render( &self.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year),
+            Column::Timestamp(Accessed)  => file.accessed_time().render(&self.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year),
+            Column::HardLinks            => file.links().render(&self.colours, &self.env.numeric),
+            Column::Inode                => file.inode().render(&self.colours),
+            Column::Blocks               => file.blocks().render(&self.colours),
+            Column::User                 => file.user().render(&self.colours, &*self.env.lock_users()),
+            Column::Group                => file.group().render(&self.colours, &*self.env.lock_users()),
+            Column::GitStatus            => file.git_status().render(&self.colours),
         }
     }
 
@@ -554,7 +556,7 @@ impl<'a, U: Users+Groups+'a> Table<'a, U> {
             let mut filename = TextCell::default();
 
             for tree_part in tree_trunk.new_row(row.depth, row.last) {
-                filename.push(self.opts.colours.punctuation.paint(tree_part.ascii_art()), 4);
+                filename.push(self.colours.punctuation.paint(tree_part.ascii_art()), 4);
             }
 
             // If any tree characters have been printed, then add an extra

+ 28 - 18
src/output/grid.rs

@@ -1,6 +1,6 @@
 use std::io::{Write, Result as IOResult};
 
-use term_grid as grid;
+use term_grid as tg;
 
 use fs::File;
 use output::colours::Colours;
@@ -8,42 +8,52 @@ use output::file_name::{FileName, LinkStyle, Classify};
 
 
 #[derive(PartialEq, Debug, Copy, Clone)]
-pub struct Grid {
+pub struct Options {
     pub across: bool,
     pub console_width: usize,
-    pub colours: Colours,
-    pub classify: Classify,
 }
 
-impl Grid {
-    pub fn view<W: Write>(&self, files: &[File], w: &mut W) -> IOResult<()> {
-        let direction = if self.across { grid::Direction::LeftToRight }
-                                  else { grid::Direction::TopToBottom };
+impl Options {
+    pub fn direction(&self) -> tg::Direction {
+        if self.across { tg::Direction::LeftToRight }
+                  else { tg::Direction::TopToBottom }
+    }
+}
+
+
+pub struct Render<'a> {
+    pub files: Vec<File<'a>>,
+    pub colours: &'a Colours,
+    pub classify: Classify,
+    pub opts: &'a Options,
+}
 
-        let mut grid = grid::Grid::new(grid::GridOptions {
-            direction:  direction,
-            filling:    grid::Filling::Spaces(2),
+impl<'a> Render<'a> {
+    pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> {
+        let mut grid = tg::Grid::new(tg::GridOptions {
+            direction:  self.opts.direction(),
+            filling:    tg::Filling::Spaces(2),
         });
 
-        grid.reserve(files.len());
+        grid.reserve(self.files.len());
 
-        for file in files.iter() {
-            let filename = FileName::new(file, LinkStyle::JustFilenames, self.classify, &self.colours).paint();
+        for file in self.files.iter() {
+            let filename = FileName::new(file, LinkStyle::JustFilenames, self.classify, self.colours).paint();
             let width = filename.width();
 
-            grid.add(grid::Cell {
+            grid.add(tg::Cell {
                 contents:  filename.strings().to_string(),
                 width:     *width,
             });
         }
 
-        if let Some(display) = grid.fit_into_width(self.console_width) {
+        if let Some(display) = grid.fit_into_width(self.opts.console_width) {
             write!(w, "{}", display)
         }
         else {
             // File names too long for a grid - drop down to just listing them!
-            for file in files.iter() {
-                let name_cell = FileName::new(file, LinkStyle::JustFilenames, self.classify, &self.colours).paint();
+            for file in self.files.iter() {
+                let name_cell = FileName::new(file, LinkStyle::JustFilenames, self.classify, self.colours).paint();
                 writeln!(w, "{}", name_cell.strings())?;
             }
             Ok(())

+ 34 - 30
src/output/grid_details.rs

@@ -10,29 +10,26 @@ use fs::feature::xattr::FileAttributes;
 
 use output::cell::TextCell;
 use output::column::Column;
-use output::details::{Details, Table, Environment};
-use output::grid::Grid;
-use output::file_name::LinkStyle;
-
-
-#[derive(PartialEq, Debug, Clone)]
-pub struct GridDetails {
-    pub grid: Grid,
-    pub details: Details,
+use output::colours::Colours;
+use output::details::{Table, Environment, Options as DetailsOptions};
+use output::grid::Options as GridOptions;
+use output::file_name::{Classify, LinkStyle};
+
+
+pub struct Render<'a> {
+    pub dir: Option<&'a Dir>,
+    pub files: Vec<File<'a>>,
+    pub colours: &'a Colours,
+    pub classify: Classify,
+    pub grid: &'a GridOptions,
+    pub details: &'a DetailsOptions,
 }
 
-fn file_has_xattrs(file: &File) -> bool {
-    match file.path.attributes() {
-        Ok(attrs) => !attrs.is_empty(),
-        Err(_) => false,
-    }
-}
+impl<'a> Render<'a> {
+    pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> {
 
-impl GridDetails {
-    pub fn view<W>(&self, dir: Option<&Dir>, files: Vec<File>, w: &mut W) -> IOResult<()>
-    where W: Write {
         let columns_for_dir = match self.details.columns {
-            Some(cols) => cols.for_dir(dir),
+            Some(cols) => cols.for_dir(self.dir),
             None => Vec::new(),
         };
 
@@ -40,23 +37,23 @@ impl GridDetails {
 
         let (cells, file_names) = {
 
-            let first_table = self.make_table(env.clone(), &*columns_for_dir);
+            let first_table = self.make_table(env.clone(), &*columns_for_dir, self.colours, self.classify);
 
-            let cells = files.iter()
+            let cells = self.files.iter()
                               .map(|file| first_table.cells_for_file(file, file_has_xattrs(file)))
                               .collect::<Vec<_>>();
 
-            let file_names = files.into_iter()
+            let file_names = self.files.iter()
                                   .map(|file| first_table.filename(file, LinkStyle::JustFilenames).promote())
                                   .collect::<Vec<_>>();
 
             (cells, file_names)
         };
 
-        let mut last_working_table = self.make_grid(env.clone(), 1, &columns_for_dir, &file_names, cells.clone());
+        let mut last_working_table = self.make_grid(env.clone(), 1, &columns_for_dir, &file_names, cells.clone(), self.colours, self.classify);
 
         for column_count in 2.. {
-            let grid = self.make_grid(env.clone(), column_count, &columns_for_dir, &file_names, cells.clone());
+            let grid = self.make_grid(env.clone(), column_count, &columns_for_dir, &file_names, cells.clone(), self.colours, self.classify);
 
             let the_grid_fits = {
                 let d = grid.fit_into_columns(column_count);
@@ -74,12 +71,11 @@ impl GridDetails {
         Ok(())
     }
 
-    fn make_table<'a>(&'a self, env: Arc<Environment<UsersCache>>, columns_for_dir: &'a [Column]) -> Table<UsersCache> {
+    fn make_table<'g>(&'g self, env: Arc<Environment<UsersCache>>, columns_for_dir: &'g [Column], colours: &'g Colours, classify: Classify) -> Table<UsersCache> {
         let mut table = Table {
             columns: columns_for_dir,
-            opts: &self.details,
-            env: env,
-
+            colours, classify, env,
+            xattr: self.details.xattr,
             rows: Vec::new(),
         };
 
@@ -87,10 +83,10 @@ impl GridDetails {
         table
     }
 
-    fn make_grid<'a>(&'a self, env: Arc<Environment<UsersCache>>, column_count: usize, columns_for_dir: &'a [Column], file_names: &[TextCell], cells: Vec<Vec<TextCell>>) -> grid::Grid {
+    fn make_grid<'g>(&'g self, env: Arc<Environment<UsersCache>>, column_count: usize, columns_for_dir: &'g [Column], file_names: &[TextCell], cells: Vec<Vec<TextCell>>, colours: &'g Colours, classify: Classify) -> grid::Grid {
         let mut tables = Vec::new();
         for _ in 0 .. column_count {
-            tables.push(self.make_table(env.clone(), columns_for_dir));
+            tables.push(self.make_table(env.clone(), columns_for_dir, colours, classify));
         }
 
         let mut num_cells = cells.len();
@@ -159,3 +155,11 @@ fn divide_rounding_up(a: usize, b: usize) -> usize {
     if a % b != 0 { result += 1; }
     result
 }
+
+
+fn file_has_xattrs(file: &File) -> bool {
+    match file.path.attributes() {
+        Ok(attrs) => !attrs.is_empty(),
+        Err(_) => false,
+    }
+}

+ 13 - 8
src/output/lines.rs

@@ -8,19 +8,24 @@ use output::file_name::{FileName, LinkStyle, Classify};
 use super::colours::Colours;
 
 
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub struct Lines {
-    pub colours: Colours,
+/// The lines view literally just displays each file, line-by-line.
+pub struct Render<'a> {
+    pub files: Vec<File<'a>>,
+    pub colours: &'a Colours,
     pub classify: Classify,
 }
 
-/// The lines view literally just displays each file, line-by-line.
-impl Lines {
-    pub fn view<W: Write>(&self, files: Vec<File>, w: &mut W) -> IOResult<()> {
-        for file in files {
-            let name_cell = FileName::new(&file, LinkStyle::FullLinkPaths, self.classify, &self.colours).paint();
+impl<'a> Render<'a> {
+    pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> {
+        for file in &self.files {
+            let name_cell = self.render_file(file).paint();
             writeln!(w, "{}", ANSIStrings(&name_cell))?;
         }
+
         Ok(())
     }
+
+    fn render_file<'f>(&self, file: &'f File<'a>) -> FileName<'f, 'a> {
+        FileName::new(file, LinkStyle::FullLinkPaths, self.classify, self.colours)
+    }
 }

+ 7 - 10
src/output/mod.rs

@@ -1,19 +1,16 @@
 pub use self::cell::{TextCell, TextCellContents, DisplayWidth};
 pub use self::colours::Colours;
-pub use self::details::Details;
-pub use self::grid_details::GridDetails;
-pub use self::grid::Grid;
-pub use self::lines::Lines;
 pub use self::escape::escape;
 
-mod grid;
-pub mod details;
-mod lines;
-mod grid_details;
 pub mod column;
+pub mod details;
+pub mod file_name;
+pub mod grid_details;
+pub mod grid;
+pub mod lines;
+
 mod cell;
 mod colours;
-mod tree;
-pub mod file_name;
 mod escape;
 mod render;
+mod tree;

+ 7 - 7
src/output/render/blocks.rs

@@ -15,7 +15,7 @@ impl f::Blocks {
 
 #[cfg(test)]
 pub mod test {
-    use output::details::Details;
+    use output::colours::Colours;
     use output::cell::TextCell;
     use fs::fields as f;
 
@@ -24,21 +24,21 @@ pub mod test {
 
     #[test]
     fn blocklessness() {
-        let mut details = Details::default();
-        details.colours.punctuation = Green.italic();
+        let mut colours = Colours::default();
+        colours.punctuation = Green.italic();
 
         let blox = f::Blocks::None;
         let expected = TextCell::blank(Green.italic());
-        assert_eq!(expected, blox.render(&details.colours).into());
+        assert_eq!(expected, blox.render(&colours).into());
     }
 
     #[test]
     fn blockfulity() {
-        let mut details = Details::default();
-        details.colours.blocks = Red.blink();
+        let mut colours = Colours::default();
+        colours.blocks = Red.blink();
 
         let blox = f::Blocks::Some(3005);
         let expected = TextCell::paint_str(Red.blink(), "3005");
-        assert_eq!(expected, blox.render(&details.colours).into());
+        assert_eq!(expected, blox.render(&colours).into());
     }
 }

+ 8 - 8
src/output/render/git.rs

@@ -33,7 +33,7 @@ impl f::GitStatus {
 
 #[cfg(test)]
 pub mod test {
-    use output::details::Details;
+    use output::colours::Colours;
     use output::cell::{TextCell, DisplayWidth};
     use fs::fields as f;
 
@@ -42,8 +42,8 @@ pub mod test {
 
     #[test]
     fn git_blank() {
-        let mut details = Details::default();
-        details.colours.punctuation = Fixed(44).normal();
+        let mut colours = Colours::default();
+        colours.punctuation = Fixed(44).normal();
 
         let stati = f::Git {
             staged:   f::GitStatus::NotModified,
@@ -58,15 +58,15 @@ pub mod test {
             ].into(),
         };
 
-        assert_eq!(expected, stati.render(&details.colours).into())
+        assert_eq!(expected, stati.render(&colours).into())
     }
 
 
     #[test]
     fn git_new_changed() {
-        let mut details = Details::default();
-        details.colours.git.new = Red.normal();
-        details.colours.git.modified = Purple.normal();
+        let mut colours = Colours::default();
+        colours.git.new = Red.normal();
+        colours.git.modified = Purple.normal();
 
         let stati = f::Git {
             staged:   f::GitStatus::New,
@@ -81,6 +81,6 @@ pub mod test {
             ].into(),
         };
 
-        assert_eq!(expected, stati.render(&details.colours).into())
+        assert_eq!(expected, stati.render(&colours).into())
     }
 }

+ 16 - 17
src/output/render/groups.rs

@@ -32,10 +32,9 @@ impl f::Group {
 #[cfg(test)]
 #[allow(unused_results)]
 pub mod test {
-    use output::details::Details;
-
     use fs::fields as f;
     use output::cell::TextCell;
+    use output::colours::Colours;
 
     use users::{User, Group};
     use users::mock::MockUsers;
@@ -45,33 +44,33 @@ pub mod test {
 
     #[test]
     fn named() {
-        let mut details = Details::default();
-        details.colours.users.group_not_yours = Fixed(101).normal();
+        let mut colours = Colours::default();
+        colours.users.group_not_yours = Fixed(101).normal();
 
         let mut users = MockUsers::with_current_uid(1000);
         users.add_group(Group::new(100, "folk"));
 
         let group = f::Group(100);
         let expected = TextCell::paint_str(Fixed(101).normal(), "folk");
-        assert_eq!(expected, group.render(&details.colours, &users))
+        assert_eq!(expected, group.render(&colours, &users))
     }
 
     #[test]
     fn unnamed() {
-        let mut details = Details::default();
-        details.colours.users.group_not_yours = Fixed(87).normal();
+        let mut colours = Colours::default();
+        colours.users.group_not_yours = Fixed(87).normal();
 
         let users = MockUsers::with_current_uid(1000);
 
         let group = f::Group(100);
         let expected = TextCell::paint_str(Fixed(87).normal(), "100");
-        assert_eq!(expected, group.render(&details.colours, &users));
+        assert_eq!(expected, group.render(&colours, &users));
     }
 
     #[test]
     fn primary() {
-        let mut details = Details::default();
-        details.colours.users.group_yours = Fixed(64).normal();
+        let mut colours = Colours::default();
+        colours.users.group_yours = Fixed(64).normal();
 
         let mut users = MockUsers::with_current_uid(2);
         users.add_user(User::new(2, "eve", 100));
@@ -79,13 +78,13 @@ pub mod test {
 
         let group = f::Group(100);
         let expected = TextCell::paint_str(Fixed(64).normal(), "folk");
-        assert_eq!(expected, group.render(&details.colours, &users))
+        assert_eq!(expected, group.render(&colours, &users))
     }
 
     #[test]
     fn secondary() {
-        let mut details = Details::default();
-        details.colours.users.group_yours = Fixed(31).normal();
+        let mut colours = Colours::default();
+        colours.users.group_yours = Fixed(31).normal();
 
         let mut users = MockUsers::with_current_uid(2);
         users.add_user(User::new(2, "eve", 666));
@@ -95,16 +94,16 @@ pub mod test {
 
         let group = f::Group(100);
         let expected = TextCell::paint_str(Fixed(31).normal(), "folk");
-        assert_eq!(expected, group.render(&details.colours, &users))
+        assert_eq!(expected, group.render(&colours, &users))
     }
 
     #[test]
     fn overflow() {
-        let mut details = Details::default();
-        details.colours.users.group_not_yours = Blue.underline();
+        let mut colours = Colours::default();
+        colours.users.group_not_yours = Blue.underline();
 
         let group = f::Group(2_147_483_648);
         let expected = TextCell::paint_str(Blue.underline(), "2147483648");
-        assert_eq!(expected, group.render(&details.colours, &MockUsers::with_current_uid(0)));
+        assert_eq!(expected, group.render(&colours, &MockUsers::with_current_uid(0)));
     }
 }

+ 4 - 4
src/output/render/inode.rs

@@ -12,7 +12,7 @@ impl f::Inode {
 
 #[cfg(test)]
 pub mod test {
-    use output::details::Details;
+    use output::colours::Colours;
     use output::cell::TextCell;
     use fs::fields as f;
 
@@ -21,11 +21,11 @@ pub mod test {
 
     #[test]
     fn blocklessness() {
-        let mut details = Details::default();
-        details.colours.inode = Cyan.underline();
+        let mut colours = Colours::default();
+        colours.inode = Cyan.underline();
 
         let io = f::Inode(1414213);
         let expected = TextCell::paint_str(Cyan.underline(), "1414213");
-        assert_eq!(expected, io.render(&details.colours).into());
+        assert_eq!(expected, io.render(&colours).into());
     }
 }

+ 10 - 10
src/output/render/links.rs

@@ -17,7 +17,7 @@ impl f::Links {
 
 #[cfg(test)]
 pub mod test {
-    use output::details::Details;
+    use output::colours::Colours;
     use output::cell::{TextCell, DisplayWidth};
     use fs::fields as f;
 
@@ -27,8 +27,8 @@ pub mod test {
 
     #[test]
     fn regular_file() {
-        let mut details = Details::default();
-        details.colours.links.normal = Blue.normal();
+        let mut colours = Colours::default();
+        colours.links.normal = Blue.normal();
 
         let stati = f::Links {
             count:    1,
@@ -40,13 +40,13 @@ pub mod test {
             contents: vec![ Blue.paint("1") ].into(),
         };
 
-        assert_eq!(expected, stati.render(&details.colours, &locale::Numeric::english()).into());
+        assert_eq!(expected, stati.render(&colours, &locale::Numeric::english()).into());
     }
 
     #[test]
     fn regular_directory() {
-        let mut details = Details::default();
-        details.colours.links.normal = Blue.normal();
+        let mut colours = Colours::default();
+        colours.links.normal = Blue.normal();
 
         let stati = f::Links {
             count:    3005,
@@ -58,13 +58,13 @@ pub mod test {
             contents: vec![ Blue.paint("3,005") ].into(),
         };
 
-        assert_eq!(expected, stati.render(&details.colours, &locale::Numeric::english()).into());
+        assert_eq!(expected, stati.render(&colours, &locale::Numeric::english()).into());
     }
 
     #[test]
     fn popular_file() {
-        let mut details = Details::default();
-        details.colours.links.multi_link_file = Blue.on(Red);
+        let mut colours = Colours::default();
+        colours.links.multi_link_file = Blue.on(Red);
 
         let stati = f::Links {
             count:    3005,
@@ -76,6 +76,6 @@ pub mod test {
             contents: vec![ Blue.on(Red).paint("3,005") ].into(),
         };
 
-        assert_eq!(expected, stati.render(&details.colours, &locale::Numeric::english()).into());
+        assert_eq!(expected, stati.render(&colours, &locale::Numeric::english()).into());
     }
 }

+ 24 - 24
src/output/render/permissions.rs

@@ -92,7 +92,7 @@ impl f::Type {
 #[cfg(test)]
 #[allow(unused_results)]
 pub mod test {
-    use output::details::Details;
+    use output::colours::Colours;
     use output::cell::TextCellContents;
     use fs::fields as f;
 
@@ -101,8 +101,8 @@ pub mod test {
 
     #[test]
     fn negate() {
-        let mut details = Details::default();
-        details.colours.punctuation = Fixed(11).normal();
+        let mut colours = Colours::default();
+        colours.punctuation = Fixed(11).normal();
 
         let bits = f::Permissions {
             user_read:  false,  user_write:  false,  user_execute:  false,  setuid: false,
@@ -116,24 +116,24 @@ pub mod test {
             Fixed(11).paint("-"),  Fixed(11).paint("-"),  Fixed(11).paint("-"),
         ]);
 
-        assert_eq!(expected, bits.render(&details.colours, false).into())
+        assert_eq!(expected, bits.render(&colours, false).into())
     }
 
 
     #[test]
     fn affirm() {
-        let mut details = Details::default();
-        details.colours.perms.user_read    = Fixed(101).normal();
-        details.colours.perms.user_write   = Fixed(102).normal();
-        details.colours.perms.user_execute_file = Fixed(103).normal();
+        let mut colours = Colours::default();
+        colours.perms.user_read    = Fixed(101).normal();
+        colours.perms.user_write   = Fixed(102).normal();
+        colours.perms.user_execute_file = Fixed(103).normal();
 
-        details.colours.perms.group_read    = Fixed(104).normal();
-        details.colours.perms.group_write   = Fixed(105).normal();
-        details.colours.perms.group_execute = Fixed(106).normal();
+        colours.perms.group_read    = Fixed(104).normal();
+        colours.perms.group_write   = Fixed(105).normal();
+        colours.perms.group_execute = Fixed(106).normal();
 
-        details.colours.perms.other_read    = Fixed(107).normal();
-        details.colours.perms.other_write   = Fixed(108).normal();
-        details.colours.perms.other_execute = Fixed(109).normal();
+        colours.perms.other_read    = Fixed(107).normal();
+        colours.perms.other_write   = Fixed(108).normal();
+        colours.perms.other_execute = Fixed(109).normal();
 
         let bits = f::Permissions {
             user_read:  true,  user_write:  true,  user_execute:  true,  setuid: false,
@@ -147,16 +147,16 @@ pub mod test {
             Fixed(107).paint("r"),  Fixed(108).paint("w"),  Fixed(109).paint("x"),
         ]);
 
-        assert_eq!(expected, bits.render(&details.colours, true).into())
+        assert_eq!(expected, bits.render(&colours, true).into())
     }
 
 
     #[test]
     fn specials() {
-        let mut details = Details::default();
-        details.colours.punctuation = Fixed(11).normal();
-        details.colours.perms.special_user_file = Fixed(77).normal();
-        details.colours.perms.special_other = Fixed(88).normal();
+        let mut colours = Colours::default();
+        colours.punctuation = Fixed(11).normal();
+        colours.perms.special_user_file = Fixed(77).normal();
+        colours.perms.special_other = Fixed(88).normal();
 
         let bits = f::Permissions {
             user_read:  false,  user_write:  false,  user_execute:  true,  setuid: true,
@@ -170,15 +170,15 @@ pub mod test {
             Fixed(11).paint("-"),  Fixed(11).paint("-"),  Fixed(88).paint("t"),
         ]);
 
-        assert_eq!(expected, bits.render(&details.colours, true).into())
+        assert_eq!(expected, bits.render(&colours, true).into())
     }
 
 
     #[test]
     fn extra_specials() {
-        let mut details = Details::default();
-        details.colours.punctuation = Fixed(11).normal();
-        details.colours.perms.special_other = Fixed(88).normal();
+        let mut colours = Colours::default();
+        colours.punctuation = Fixed(11).normal();
+        colours.perms.special_other = Fixed(88).normal();
 
         let bits = f::Permissions {
             user_read:  false,  user_write:  false,  user_execute:  false,  setuid: true,
@@ -192,6 +192,6 @@ pub mod test {
             Fixed(11).paint("-"),  Fixed(11).paint("-"),  Fixed(88).paint("T"),
         ]);
 
-        assert_eq!(expected, bits.render(&details.colours, true).into())
+        assert_eq!(expected, bits.render(&colours, true).into())
     }
 }

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

@@ -67,7 +67,7 @@ impl f::DeviceIDs {
 
 #[cfg(test)]
 pub mod test {
-    use output::details::Details;
+    use output::colours::Colours;
     use output::column::SizeFormat;
     use output::cell::{TextCell, DisplayWidth};
     use fs::fields as f;
@@ -78,20 +78,20 @@ pub mod test {
 
     #[test]
     fn directory() {
-        let mut details = Details::default();
-        details.colours.punctuation = Green.italic();
+        let mut colours = Colours::default();
+        colours.punctuation = Green.italic();
 
         let directory = f::Size::None;
         let expected = TextCell::blank(Green.italic());
-        assert_eq!(expected, directory.render(&details.colours, SizeFormat::JustBytes, &locale::Numeric::english()))
+        assert_eq!(expected, directory.render(&colours, SizeFormat::JustBytes, &locale::Numeric::english()))
     }
 
 
     #[test]
     fn file_decimal() {
-        let mut details = Details::default();
-        details.colours.size.numbers = Blue.on(Red);
-        details.colours.size.unit    = Yellow.bold();
+        let mut colours = Colours::default();
+        colours.size.numbers = Blue.on(Red);
+        colours.size.unit    = Yellow.bold();
 
         let directory = f::Size::Some(2_100_000);
         let expected = TextCell {
@@ -102,15 +102,15 @@ pub mod test {
             ].into(),
         };
 
-        assert_eq!(expected, directory.render(&details.colours, SizeFormat::DecimalBytes, &locale::Numeric::english()))
+        assert_eq!(expected, directory.render(&colours, SizeFormat::DecimalBytes, &locale::Numeric::english()))
     }
 
 
     #[test]
     fn file_binary() {
-        let mut details = Details::default();
-        details.colours.size.numbers = Blue.on(Red);
-        details.colours.size.unit    = Yellow.bold();
+        let mut colours = Colours::default();
+        colours.size.numbers = Blue.on(Red);
+        colours.size.unit    = Yellow.bold();
 
         let directory = f::Size::Some(1_048_576);
         let expected = TextCell {
@@ -121,14 +121,14 @@ pub mod test {
             ].into(),
         };
 
-        assert_eq!(expected, directory.render(&details.colours, SizeFormat::BinaryBytes, &locale::Numeric::english()))
+        assert_eq!(expected, directory.render(&colours, SizeFormat::BinaryBytes, &locale::Numeric::english()))
     }
 
 
     #[test]
     fn file_bytes() {
-        let mut details = Details::default();
-        details.colours.size.numbers = Blue.on(Red);
+        let mut colours = Colours::default();
+    	colours.size.numbers = Blue.on(Red);
 
         let directory = f::Size::Some(1048576);
         let expected = TextCell {
@@ -138,16 +138,16 @@ pub mod test {
             ].into(),
         };
 
-        assert_eq!(expected, directory.render(&details.colours, SizeFormat::JustBytes, &locale::Numeric::english()))
+        assert_eq!(expected, directory.render(&colours, SizeFormat::JustBytes, &locale::Numeric::english()))
     }
 
 
     #[test]
     fn device_ids() {
-        let mut details = Details::default();
-        details.colours.size.major = Blue.on(Red);
-        details.colours.punctuation = Green.italic();
-        details.colours.size.minor = Cyan.on(Yellow);
+        let mut colours = Colours::default();
+        colours.size.major = Blue.on(Red);
+        colours.punctuation = Green.italic();
+        colours.size.minor = Cyan.on(Yellow);
 
         let directory = f::Size::DeviceIDs(f::DeviceIDs { major: 10, minor: 80 });
         let expected = TextCell {
@@ -159,6 +159,6 @@ pub mod test {
             ].into(),
         };
 
-        assert_eq!(expected, directory.render(&details.colours, SizeFormat::JustBytes, &locale::Numeric::english()))
+        assert_eq!(expected, directory.render(&colours, SizeFormat::JustBytes, &locale::Numeric::english()))
     }
 }

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

@@ -21,10 +21,9 @@ impl f::User {
 #[cfg(test)]
 #[allow(unused_results)]
 pub mod test {
-    use output::details::Details;
-
     use fs::fields as f;
     use output::cell::TextCell;
+    use output::colours::Colours;
 
     use users::User;
     use users::mock::MockUsers;
@@ -32,59 +31,59 @@ pub mod test {
 
     #[test]
     fn named() {
-        let mut details = Details::default();
-        details.colours.users.user_you = Red.bold();
+        let mut colours = Colours::default();
+        colours.users.user_you = Red.bold();
 
         let mut users = MockUsers::with_current_uid(1000);
         users.add_user(User::new(1000, "enoch", 100));
 
         let user = f::User(1000);
         let expected = TextCell::paint_str(Red.bold(), "enoch");
-        assert_eq!(expected, user.render(&details.colours, &users))
+        assert_eq!(expected, user.render(&colours, &users))
     }
 
     #[test]
     fn unnamed() {
-        let mut details = Details::default();
-        details.colours.users.user_you = Cyan.bold();
+        let mut colours = Colours::default();
+        colours.users.user_you = Cyan.bold();
 
         let users = MockUsers::with_current_uid(1000);
 
         let user = f::User(1000);
         let expected = TextCell::paint_str(Cyan.bold(), "1000");
-        assert_eq!(expected, user.render(&details.colours, &users));
+        assert_eq!(expected, user.render(&colours, &users));
     }
 
     #[test]
     fn different_named() {
-        let mut details = Details::default();
-        details.colours.users.user_someone_else = Green.bold();
+        let mut colours = Colours::default();
+        colours.users.user_someone_else = Green.bold();
 
         let mut users = MockUsers::with_current_uid(0);
         users.add_user(User::new(1000, "enoch", 100));
 
         let user = f::User(1000);
         let expected = TextCell::paint_str(Green.bold(), "enoch");
-        assert_eq!(expected, user.render(&details.colours, &users));
+        assert_eq!(expected, user.render(&colours, &users));
     }
 
     #[test]
     fn different_unnamed() {
-        let mut details = Details::default();
-        details.colours.users.user_someone_else = Red.normal();
+        let mut colours = Colours::default();
+        colours.users.user_someone_else = Red.normal();
 
         let user = f::User(1000);
         let expected = TextCell::paint_str(Red.normal(), "1000");
-        assert_eq!(expected, user.render(&details.colours, &MockUsers::with_current_uid(0)));
+        assert_eq!(expected, user.render(&colours, &MockUsers::with_current_uid(0)));
     }
 
     #[test]
     fn overflow() {
-        let mut details = Details::default();
-        details.colours.users.user_someone_else = Blue.underline();
+        let mut colours = Colours::default();
+        colours.users.user_someone_else = Blue.underline();
 
         let user = f::User(2_147_483_648);
         let expected = TextCell::paint_str(Blue.underline(), "2147483648");
-        assert_eq!(expected, user.render(&details.colours, &MockUsers::with_current_uid(0)));
+        assert_eq!(expected, user.render(&colours, &MockUsers::with_current_uid(0)));
     }
 }

+ 39 - 0
xtests/files_l_bw

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

+ 16 - 5
xtests/run.sh

@@ -2,14 +2,17 @@
 set +xe
 
 
-# The exa binary we want to run
-exa="$HOME/target/debug/exa --colour=always"
+# The exa binary
+exa_binary="$HOME/target/debug/exa"
+
+# The exa command that ends up being run
+exa="$exa_binary --colour=always"
 
 # Directory containing our awkward testcase files
-testcases=/testcases
+testcases="/testcases"
 
 # Directory containing existing test results to compare against
-results=/vagrant/xtests
+results="/vagrant/xtests"
 
 
 # Check that no files were created more than a year ago.
@@ -25,7 +28,7 @@ $exa $testcases/files -lhb | diff -q - $results/files_lhb   || exit 1
 $exa $testcases/files -lhB | diff -q - $results/files_lhb2  || exit 1
 $exa $testcases/attributes/dirs/empty-with-attribute -lh | diff -q - $results/empty  || exit 1
 
-$exa $testcases/files -l --color-scale  | diff -q - $results/files_l_scale  || exit 1
+$exa --color-scale         $testcases/files -l | diff -q - $results/files_l_scale  || exit 1
 
 
 # Grid view tests
@@ -105,6 +108,14 @@ COLUMNS=80 $exa $testcases/links    2>&1 | diff -q - $results/links        || ex
 $exa $testcases/links/* -1 | diff -q - $results/links_1_files || exit 1
 
 
+# Colours and terminals
+# Just because COLUMNS is present, doesn’t mean output is to a terminal
+COLUMNS=80 $exa_binary                    $testcases/files -l | diff -q - $results/files_l_bw  || exit 1
+COLUMNS=80 $exa_binary --colour=always    $testcases/files -l | diff -q - $results/files_l     || exit 1
+COLUMNS=80 $exa_binary --colour=never     $testcases/files -l | diff -q - $results/files_l_bw  || exit 1
+COLUMNS=80 $exa_binary --colour=automatic $testcases/files -l | diff -q - $results/files_l_bw  || exit 1
+
+
 # Git
 $exa $testcases/git/additions -l --git 2>&1 | diff -q - $results/git_additions  || exit 1
 $exa $testcases/git/edits     -l --git 2>&1 | diff -q - $results/git_edits      || exit 1