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

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

Chester Liu 4 лет назад
Родитель
Сommit
78a3bc9838

+ 2 - 0
README.md

@@ -47,6 +47,7 @@ exa’s options are almost, but not quite, entirely unlike `ls`’s.
 - **--colo[u]r**: when to use terminal colours
 - **--colo[u]r-scale**: highlight levels of file sizes distinctly
 - **--icons**: display icons
+- **--no-icons**: don't display icons (always overrides --icons)
 
 ### Filtering options
 
@@ -82,6 +83,7 @@ These options are available when running with `--long` (`-l`):
 - **--git**: list each file’s Git status, if tracked or ignored
 - **--time-style**: how to format timestamps
 - **--no-permissions**: suppress the permissions field
+- **--octal-permissions**: list each file's permission in octal format
 - **--no-filesize**: suppress the filesize field
 - **--no-user**: suppress the user field
 - **--no-time**: suppress the time field

+ 2 - 0
completions/completions.fish

@@ -15,6 +15,7 @@ complete -c exa        -l 'colour'       -d "When to use terminal colours"
 complete -c exa        -l 'color-scale'  -d "Highlight levels of file sizes distinctly"
 complete -c exa        -l 'colour-scale' -d "Highlight levels of file sizes distinctly"
 complete -c exa        -l 'icons'        -d "Display icons"
+complete -c exa        -l 'no-icons'     -d "Don't display icons"
 
 # Filtering and sorting options
 complete -c exa -l 'group-directories-first' -d "Sort directories before other files"
@@ -75,6 +76,7 @@ complete -c exa        -l 'time-style' -x -d "How to format timestamps" -a "
     full-iso\t'Display full ISO timestamps, up to the nanosecond'
 "
 complete -c exa        -l 'no-permissions' -d "Suppress the permissions field"
+complete -c exa        -l 'octal-permissions' -d "List each file's permission in octal format"
 complete -c exa        -l 'no-filesize'    -d "Suppress the filesize field"
 complete -c exa        -l 'no-user'        -d "Suppress the user field"
 complete -c exa        -l 'no-time'        -d "Suppress the time field"

+ 2 - 0
completions/completions.zsh

@@ -22,6 +22,7 @@ __exa() {
         --colo{,u}r"[When to use terminal colours]" \
         --colo{,u}r-scale"[Highlight levels of file sizes distinctly]" \
         --icons"[Display icons]" \
+        --no-icons"[Hide icons]" \
         --group-directories-first"[Sort directories before other files]" \
         --git-ignore"[Ignore files mentioned in '.gitignore']" \
         {-a,--all}"[Show hidden and 'dot' files]" \
@@ -43,6 +44,7 @@ __exa() {
         {-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)" \
         --no-permissions"[Suppress the permissions field]" \
+        --octal-permissions"[List each file's permission in octal format]" \
         --no-filesize"[Suppress the filesize field]" \
         --no-user"[Suppress the user field]" \
         --no-time"[Suppress the time field]" \

+ 5 - 2
man/exa.1.md

@@ -72,6 +72,9 @@ Valid settings are ‘`always`’, ‘`automatic`’, and ‘`never`’.
 `--icons`
 : Display icons next to file names.
 
+`--no-icons`
+: Don't display icons. (Always overrides --icons)
+
 
 FILTERING AND SORTING OPTIONS
 =============================
@@ -101,7 +104,7 @@ Sort fields starting with a capital letter will sort uppercase before lowercase:
 `-I`, `--ignore-glob=GLOBS`
 : Glob patterns, pipe-separated, of files to ignore.
 
-`--git-ignore`
+`--git-ignore` [if exa was built with git support]
 : Do not list files that are ignored by Git.
 
 `--group-directories-first`
@@ -174,7 +177,7 @@ These options are available when running with `--long` (`-l`):
 `-@`, `--extended`
 : List each file’s extended attributes and sizes.
 
-`--git`
+`--git`  [if exa was built with git support]
 : List each file’s Git status, if tracked.
 
 

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

@@ -27,7 +27,7 @@ pub mod git {
         }
 
         pub fn get(&self, _index: &Path, _prefix_lookup: bool) -> f::Git {
-            panic!("Tried to query a Git cache, but Git support is disabled")
+            unreachable!();
         }
     }
 }

+ 1 - 3
src/fs/feature/xattr.rs

@@ -7,9 +7,7 @@ use std::io;
 use std::path::Path;
 
 
-pub const ENABLED: bool =
-    cfg!(feature="git") &&
-    cfg!(any(target_os = "macos", target_os = "linux"));
+pub const ENABLED: bool = cfg!(any(target_os = "macos", target_os = "linux"));
 
 
 pub trait FileAttributes {

+ 4 - 2
src/main.rs

@@ -264,13 +264,15 @@ impl<'args> Exa<'args> {
 
         match (mode, self.console_width) {
             (Mode::Grid(ref opts), Some(console_width)) => {
-                let r = grid::Render { files, theme, file_style, opts, console_width };
+                let filter = &self.options.filter;
+                let r = grid::Render { files, theme, file_style, opts, console_width, filter };
                 r.render(&mut self.writer)
             }
 
             (Mode::Grid(_), None) |
             (Mode::Lines,   _)    => {
-                let r = lines::Render { files, theme, file_style };
+                let filter = &self.options.filter;
+                let r = lines::Render { files, theme, file_style, filter };
                 r.render(&mut self.writer)
             }
 

+ 1 - 1
src/options/error.rs

@@ -16,7 +16,7 @@ pub enum OptionsError {
     /// The user supplied an illegal choice to an Argument.
     BadArgument(&'static Arg, OsString),
 
-    /// The user supplied a set of options
+    /// The user supplied a set of options that are unsupported
     Unsupported(String),
 
     /// An option was given twice or more in strict mode.

+ 1 - 1
src/options/file_name.rs

@@ -25,7 +25,7 @@ impl Classify {
 
 impl ShowIcons {
     pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
-        if ! matches.has(&flags::ICONS)? {
+        if matches.has(&flags::NO_ICONS)? || !matches.has(&flags::ICONS)? {
             Ok(Self::Off)
         }
         else if let Some(columns) = vars.get(vars::EXA_ICON_SPACING).and_then(|s| s.into_string().ok()) {

+ 2 - 1
src/options/flags.rs

@@ -58,6 +58,7 @@ pub static NO_PERMISSIONS: Arg = Arg { short: None, long: "no-permissions", take
 pub static NO_FILESIZE: Arg = Arg { short: None, long: "no-filesize", takes_value: TakesValue::Forbidden };
 pub static NO_USER: Arg = Arg { short: None, long: "no-user", takes_value: TakesValue::Forbidden };
 pub static NO_TIME: Arg = Arg { short: None, long: "no-time", takes_value: TakesValue::Forbidden };
+pub static NO_ICONS: Arg = Arg { short: None, long: "no-icons", takes_value: TakesValue::Forbidden };
 
 // optional feature options
 pub static GIT:       Arg = Arg { short: None,       long: "git",               takes_value: TakesValue::Forbidden };
@@ -76,7 +77,7 @@ pub static ALL_ARGS: Args = Args(&[
 
     &BINARY, &BYTES, &GROUP, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
     &BLOCKS, &TIME, &ACCESSED, &CREATED, &TIME_STYLE,
-    &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME,
+    &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &NO_ICONS,
 
     &GIT, &EXTENDED, &OCTAL
 ]);

+ 19 - 10
src/options/help.rs

@@ -5,7 +5,7 @@ use crate::options::flags;
 use crate::options::parser::MatchedFlags;
 
 
-static USAGE: &str = r##"Usage:
+static USAGE_PART1: &str = "Usage:
   exa [options] [files...]
 
 META OPTIONS
@@ -23,6 +23,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
@@ -32,8 +33,9 @@ FILTERING AND SORTING OPTIONS
   -s, --sort SORT_FIELD      which field to sort by
   --group-directories-first  list directories before other files
   -D, --only-dirs            list only directories
-  -I, --ignore-glob GLOBS    glob patterns (pipe-separated) of files to ignore
-  --git-ignore               ignore files mentioned in '.gitignore'
+  -I, --ignore-glob GLOBS    glob patterns (pipe-separated) of files to ignore";
+
+  static USAGE_PART2: &str = "  \
   Valid sort fields:         name, Name, extension, Extension, size, type,
                              modified, accessed, created, inode, and none.
                              date, time, old, and new all refer to modified.
@@ -56,10 +58,11 @@ LONG VIEW OPTIONS
   --octal-permissions  list each file's permission in octal format
   --no-filesize        suppress the filesize field
   --no-user            suppress the user field
-  --no-time            suppress the time field"##;
+  --no-time            suppress the time field";
 
-static GIT_HELP:      &str = r##"  --git                list each file's Git status, if tracked or ignored"##;
-static EXTENDED_HELP: &str = r##"  -@, --extended       list each file's extended attributes and sizes"##;
+static GIT_FILTER_HELP: &str = "  --git-ignore               ignore files mentioned in '.gitignore'";
+static GIT_VIEW_HELP:   &str = "  --git                list each file's Git status, if tracked or ignored";
+static EXTENDED_HELP:   &str = "  -@, --extended       list each file's extended attributes and sizes";
 
 
 /// All the information needed to display the help text, which depends
@@ -92,14 +95,20 @@ impl fmt::Display for HelpString {
     /// Format this help options into an actual string of help
     /// text to be displayed to the user.
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
-        writeln!(f, "{}", USAGE)?;
+        write!(f, "{}", USAGE_PART1)?;
+
+        if cfg!(feature = "git") {
+            write!(f, "\n{}", GIT_FILTER_HELP)?;
+        }
+
+        write!(f, "\n{}", USAGE_PART2)?;
 
-        if cfg!(feature="git") {
-            writeln!(f, "{}", GIT_HELP)?;
+        if cfg!(feature = "git") {
+            write!(f, "\n{}", GIT_VIEW_HELP)?;
         }
 
         if xattr::ENABLED {
-            writeln!(f, "{}", EXTENDED_HELP)?;
+            write!(f, "\n{}", EXTENDED_HELP)?;
         }
 
         Ok(())

+ 7 - 0
src/options/mod.rs

@@ -176,6 +176,13 @@ impl Options {
     /// Determines the complete set of options based on the given command-line
     /// arguments, after they’ve been parsed.
     fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
+        if cfg!(not(feature = "git")) &&
+                matches.has_where_any(|f| f.matches(&flags::GIT) || f.matches(&flags::GIT_IGNORE)).is_some() {
+            return Err(OptionsError::Unsupported(format!(
+                "Options --git and --git-ignore can't be used because `git` feature was disabled in this build of exa"
+            )));
+        }
+
         let view = View::deduce(matches, vars)?;
         let dir_action = DirAction::deduce(matches, matches!(view.mode, Mode::Details(_)))?;
         let filter = FileFilter::deduce(matches)?;

+ 5 - 1
src/options/version.rs

@@ -31,7 +31,11 @@ impl VersionString {
 
 impl fmt::Display for VersionString {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
-        writeln!(f, "{}", include!(concat!(env!("OUT_DIR"), "/version_string.txt")))
+        writeln!(
+            f,
+            "{} (git support: {})",
+            include!(concat!(env!("OUT_DIR"), "/version_string.txt")),
+            if cfg!(feature = "git") { "enabled" } else { "disabled" })
     }
 }
 

+ 2 - 2
src/options/view.rs

@@ -91,7 +91,7 @@ impl Mode {
                 }
             }
 
-            if cfg!(feature = "git") && matches.has(&flags::GIT)? {
+            if matches.has(&flags::GIT)? {
                 return Err(OptionsError::Useless(&flags::GIT, false, &flags::LONG));
             }
             else if matches.has(&flags::LEVEL)? && ! matches.has(&flags::RECURSE)? && ! matches.has(&flags::TREE)? {
@@ -192,7 +192,7 @@ impl TableOptions {
 impl Columns {
     fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
         let time_types = TimeTypes::deduce(matches)?;
-        let git = cfg!(feature = "git") && matches.has(&flags::GIT)?;
+        let git = matches.has(&flags::GIT)?;
 
         let blocks = matches.has(&flags::BLOCKS)?;
         let group  = matches.has(&flags::GROUP)?;

+ 3 - 3
src/output/file_name.rs

@@ -132,9 +132,9 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
             bits.push(style.paint(file_icon));
 
             match spaces_count {
-                1 => bits.push(Style::default().paint(" ")),
-                2 => bits.push(Style::default().paint("  ")),
-                n => bits.push(Style::default().paint(spaces(n))),
+                1 => bits.push(style.paint(" ")),
+                2 => bits.push(style.paint("  ")),
+                n => bits.push(style.paint(spaces(n))),
             }
         }
 

+ 4 - 1
src/output/grid.rs

@@ -3,6 +3,7 @@ use std::io::{self, Write};
 use term_grid as tg;
 
 use crate::fs::File;
+use crate::fs::filter::FileFilter;
 use crate::output::file_name::Options as FileStyle;
 use crate::theme::Theme;
 
@@ -26,10 +27,11 @@ pub struct Render<'a> {
     pub file_style: &'a FileStyle,
     pub opts: &'a Options,
     pub console_width: usize,
+    pub filter: &'a FileFilter,
 }
 
 impl<'a> Render<'a> {
-    pub fn render<W: Write>(&self, w: &mut W) -> io::Result<()> {
+    pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {
         let mut grid = tg::Grid::new(tg::GridOptions {
             direction:  self.opts.direction(),
             filling:    tg::Filling::Spaces(2),
@@ -37,6 +39,7 @@ impl<'a> Render<'a> {
 
         grid.reserve(self.files.len());
 
+        self.filter.sort_files(&mut self.files);
         for file in &self.files {
             let filename = self.file_style.for_file(file, self.theme).paint();
 

+ 283 - 144
src/output/icons.rs

@@ -2,6 +2,8 @@ use ansi_term::Style;
 
 use crate::fs::File;
 use crate::info::filetype::FileExtensions;
+use lazy_static::lazy_static;
+use std::collections::HashMap;
 
 
 pub trait FileIcon {
@@ -42,156 +44,293 @@ pub fn iconify_style<'a>(style: Style) -> Style {
 }
 
 
+
+lazy_static! {
+    static ref MAP_BY_NAME: HashMap<&'static str, char> = {
+        let mut m = HashMap::new();
+        m.insert(".Trash", '\u{f1f8}'); // 
+        m.insert(".atom", '\u{e764}'); // 
+        m.insert(".bashprofile", '\u{e615}'); // 
+        m.insert(".bashrc", '\u{f489}'); // 
+        m.insert(".git", '\u{f1d3}'); // 
+        m.insert(".gitattributes", '\u{f1d3}'); // 
+        m.insert(".gitconfig", '\u{f1d3}'); // 
+        m.insert(".github", '\u{f408}'); // 
+        m.insert(".gitignore", '\u{f1d3}'); // 
+        m.insert(".gitmodules", '\u{f1d3}'); // 
+        m.insert(".rvm", '\u{e21e}'); // 
+        m.insert(".vimrc", '\u{e62b}'); // 
+        m.insert(".vscode", '\u{e70c}'); // 
+        m.insert(".zshrc", '\u{f489}'); // 
+        m.insert("Cargo.lock", '\u{e7a8}'); // 
+        m.insert("bin", '\u{e5fc}'); // 
+        m.insert("config", '\u{e5fc}'); // 
+        m.insert("docker-compose.yml", '\u{f308}'); // 
+        m.insert("Dockerfile", '\u{f308}'); // 
+        m.insert("ds_store", '\u{f179}'); // 
+        m.insert("gitignore_global", '\u{f1d3}'); // 
+        m.insert("gradle", '\u{e70e}'); // 
+        m.insert("gruntfile.coffee", '\u{e611}'); // 
+        m.insert("gruntfile.js", '\u{e611}'); // 
+        m.insert("gruntfile.ls", '\u{e611}'); // 
+        m.insert("gulpfile.coffee", '\u{e610}'); // 
+        m.insert("gulpfile.js", '\u{e610}'); // 
+        m.insert("gulpfile.ls", '\u{e610}'); // 
+        m.insert("hidden", '\u{f023}'); // 
+        m.insert("include", '\u{e5fc}'); // 
+        m.insert("lib", '\u{f121}'); // 
+        m.insert("localized", '\u{f179}'); // 
+        m.insert("Makefile", '\u{e779}'); // 
+        m.insert("node_modules", '\u{e718}'); // 
+        m.insert("npmignore", '\u{e71e}'); // 
+        m.insert("rubydoc", '\u{e73b}'); // 
+        m.insert("yarn.lock", '\u{e718}'); // 
+
+        m
+    };
+}
+
 pub fn icon_for_file(file: &File<'_>) -> char {
     let extensions = Box::new(FileExtensions);
 
-    if file.points_to_directory() {
-        '\u{f115}'
-    }
-    else if let Some(icon) = extensions.icon_file(file) {
-        icon
+    if let Some(icon) = MAP_BY_NAME.get(file.name.as_str()) { *icon }
+    else if file.points_to_directory() {
+        match file.name.as_str() {
+            "bin"           => '\u{e5fc}', // 
+            ".git"          => '\u{f1d3}', // 
+            ".idea"         => '\u{e7b5}', // 
+            _               => '\u{f115}'  // 
+        }
     }
+    else if let Some(icon) = extensions.icon_file(file) { icon }
     else if let Some(ext) = file.ext.as_ref() {
         match ext.as_str() {
-            "ai"        => '\u{e7b4}',
-            "android"   => '\u{e70e}',
-            "apple"     => '\u{f179}',
-            "avro"      => '\u{e60b}',
-            "clj"       => '\u{e768}',
-            "coffee"    => '\u{f0f4}',
-            "cpp"       => '\u{e61d}',
-            "hpp"       => '\u{e61d}',
-            "c"         => '\u{e61e}',
-            "h"         => '\u{e61e}',
-            "cs"        => '\u{f81a}',
-            "css"       => '\u{e749}',
-            "d"         => '\u{e7af}',
-            "dart"      => '\u{e798}',
-            "db"        => '\u{f1c0}',
-            "diff"      => '\u{f440}',
-            "patch"     => '\u{f440}',
-            "rtf"       => '\u{f1c2}',
-            "doc"       => '\u{f1c2}',
-            "docx"      => '\u{f1c2}',
-            "odt"       => '\u{f1c2}',
-            "ebook"     => '\u{e28b}',
-            "env"       => '\u{f462}',
-            "epub"      => '\u{e28a}',
-            "erl"       => '\u{e7b1}',
-            "font"      => '\u{f031}',
-            "gform"     => '\u{f298}',
-            "git"       => '\u{f1d3}',
-            "go"        => '\u{e626}',
-            "hs"        => '\u{e777}',
-            "htm"       => '\u{f13b}',
-            "html"      => '\u{f13b}',
-            "xhtml"     => '\u{f13b}',
-            "iml"       => '\u{e7b5}',
-            "java"      => '\u{e204}',
-            "js"        => '\u{e74e}',
-            "mjs"       => '\u{e74e}',
-            "json"      => '\u{e60b}',
-            "jsx"       => '\u{e7ba}',
-            "vue"       => '\u{fd42}',
-            "node"      => '\u{f898}',
-            "less"      => '\u{e758}',
-            "log"       => '\u{f18d}',
-            "lua"       => '\u{e620}',
-            "md"        => '\u{f48a}',
-            "markdown"  => '\u{f48a}',
-            "mustache"  => '\u{e60f}',
-            "npmignore" => '\u{e71e}',
-            "pdf"       => '\u{f1c1}',
-            "djvu"      => '\u{f02d}',
-            "mobi"      => '\u{f02d}',
-            "php"       => '\u{e73d}',
-            "pl"        => '\u{e769}',
-            "ppt"       => '\u{f1c4}',
-            "pptx"      => '\u{f1c4}',
-            "odp"       => '\u{f1c4}',
-            "psd"       => '\u{e7b8}',
-            "py"        => '\u{e606}',
-            "r"         => '\u{f25d}',
-            "rb"        => '\u{e21e}',
-            "ru"        => '\u{e21e}',
-            "erb"       => '\u{e21e}',
-            "gem"       => '\u{e21e}',
-            "rdb"       => '\u{e76d}',
-            "rs"        => '\u{e7a8}',
-            "rss"       => '\u{f09e}',
-            "rubydoc"   => '\u{e73b}',
-            "sass"      => '\u{e74b}',
-            "stylus"    => '\u{e759}',
-            "scala"     => '\u{e737}',
-            "shell"     => '\u{f489}',
-            "sqlite3"   => '\u{e7c4}',
-            "styl"      => '\u{e600}',
-            "latex"     => '\u{e600}',
-            "tex"       => '\u{e600}',
-            "ts"        => '\u{e628}',
-            "tsx"       => '\u{e628}',
-            "twig"      => '\u{e61c}',
-            "txt"       => '\u{f15c}',
-            "video"     => '\u{f03d}',
-            "vim"       => '\u{e62b}',
-            "xml"       => '\u{e619}',
-            "yml"       => '\u{f481}',
-            "yaml"      => '\u{f481}',
-            "rar"       => '\u{f410}',
-            "zip"       => '\u{f410}',
-            "bz"        => '\u{f410}',
-            "bz2"       => '\u{f410}',
-            "xz"        => '\u{f410}',
-            "taz"       => '\u{f410}',
-            "tbz"       => '\u{f410}',
-            "tbz2"      => '\u{f410}',
-            "tz"        => '\u{f410}',
-            "tar"       => '\u{f410}',
-            "tzo"       => '\u{f410}',
-            "lz"        => '\u{f410}',
-            "lzh"       => '\u{f410}',
-            "lzma"      => '\u{f410}',
-            "lzo"       => '\u{f410}',
-            "gz"        => '\u{f410}',
-            "deb"       => '\u{e77d}',
-            "rpm"       => '\u{e7bb}',
-            "exe"       => '\u{e70f}',
-            "msi"       => '\u{e70f}',
-            "dll"       => '\u{e70f}',
-            "cab"       => '\u{e70f}',
-            "bat"       => '\u{e70f}',
-            "cmd"       => '\u{e70f}',
-            "sh"        => '\u{f489}',
-            "bash"      => '\u{f489}',
-            "zsh"       => '\u{f489}',
-            "fish"      => '\u{f489}',
-            "csh"       => '\u{f489}',
-            "ini"       => '\u{e615}',
-            "toml"      => '\u{e615}',
-            "cfg"       => '\u{e615}',
-            "conf"      => '\u{e615}',
-            "apk"       => '\u{e70e}',
-            "ttf"       => '\u{f031}',
-            "woff"      => '\u{f031}',
-            "woff2"     => '\u{f031}',
-            "otf"       => '\u{f031}',
-            "csv"       => '\u{f1c3}',
-            "tsv"       => '\u{f1c3}',
-            "xls"       => '\u{f1c3}',
-            "xlsx"      => '\u{f1c3}',
-            "ods"       => '\u{f1c3}',
-            "so"        => '\u{f17c}',
-            "sql"       => '\u{f1c0}',
-            "jar"       => '\u{e256}',
-            "jad"       => '\u{e256}',
-            "class"     => '\u{e256}',
-            "war"       => '\u{e256}',
-            "groovy"    => '\u{e775}',
-            "iso"       => '\u{e271}',
-            "lock"      => '\u{f023}',
-            "swift"     => '\u{e755}',
-            "nix"       => '\u{f313}',
-            _           => '\u{f016}'
+            "ai"            => '\u{e7b4}', // 
+            "android"       => '\u{e70e}', // 
+            "apk"           => '\u{e70e}', // 
+            "apple"         => '\u{f179}', // 
+            "avi"           => '\u{f03d}', // 
+            "avro"          => '\u{e60b}', // 
+            "awk"           => '\u{f489}', // 
+            "bash"          => '\u{f489}', // 
+            "bash_history"  => '\u{f489}', // 
+            "bash_profile"  => '\u{f489}', // 
+            "bashrc"        => '\u{f489}', // 
+            "bat"           => '\u{f17a}', // 
+            "bmp"           => '\u{f1c5}', // 
+            "bz"            => '\u{f410}', // 
+            "bz2"           => '\u{f410}', // 
+            "c"             => '\u{e61e}', // 
+            "c++"           => '\u{e61d}', // 
+            "cab"           => '\u{e70f}', // 
+            "cc"            => '\u{e61d}', // 
+            "cfg"           => '\u{e615}', // 
+            "class"         => '\u{e256}', // 
+            "clj"           => '\u{e768}', // 
+            "cljs"          => '\u{e76a}', // 
+            "cls"           => '\u{e600}', // 
+            "cmd"           => '\u{e70f}', // 
+            "coffee"        => '\u{f0f4}', // 
+            "conf"          => '\u{e615}', // 
+            "cp"            => '\u{e61d}', // 
+            "cpp"           => '\u{e61d}', // 
+            "cs"            => '\u{f81a}', // 
+            "csh"           => '\u{f489}', // 
+            "cshtml"        => '\u{f1fa}', // 
+            "csproj"        => '\u{f81a}', // 
+            "css"           => '\u{e749}', // 
+            "csv"           => '\u{f1c3}', // 
+            "csx"           => '\u{f81a}', // 
+            "cxx"           => '\u{e61d}', // 
+            "d"             => '\u{e7af}', // 
+            "dart"          => '\u{e798}', // 
+            "db"            => '\u{f1c0}', // 
+            "deb"           => '\u{e77d}', // 
+            "diff"          => '\u{f440}', // 
+            "djvu"          => '\u{f02d}', // 
+            "dll"           => '\u{e70f}', // 
+            "doc"           => '\u{f1c2}', // 
+            "docx"          => '\u{f1c2}', // 
+            "ds_store"      => '\u{f179}', // 
+            "DS_store"      => '\u{f179}', // 
+            "dump"          => '\u{f1c0}', // 
+            "ebook"         => '\u{e28b}', // 
+            "editorconfig"  => '\u{e615}', // 
+            "ejs"           => '\u{e618}', // 
+            "elm"           => '\u{e62c}', // 
+            "env"           => '\u{f462}', // 
+            "eot"           => '\u{f031}', // 
+            "epub"          => '\u{e28a}', // 
+            "erb"           => '\u{e73b}', // 
+            "erl"           => '\u{e7b1}', // 
+            "ex"            => '\u{e62d}', // 
+            "exe"           => '\u{f17a}', // 
+            "exs"           => '\u{e62d}', // 
+            "fish"          => '\u{f489}', // 
+            "flac"          => '\u{f001}', // 
+            "flv"           => '\u{f03d}', // 
+            "font"          => '\u{f031}', // 
+            "gdoc"          => '\u{f1c2}', // 
+            "gem"           => '\u{e21e}', // 
+            "gemfile"       => '\u{e21e}', // 
+            "gemspec"       => '\u{e21e}', // 
+            "gform"         => '\u{f298}', // 
+            "gif"           => '\u{f1c5}', // 
+            "git"           => '\u{f1d3}', // 
+            "gitattributes" => '\u{f1d3}', // 
+            "gitignore"     => '\u{f1d3}', // 
+            "gitmodules"    => '\u{f1d3}', // 
+            "go"            => '\u{e626}', // 
+            "gradle"        => '\u{e70e}', // 
+            "groovy"        => '\u{e775}', // 
+            "gsheet"        => '\u{f1c3}', // 
+            "gslides"       => '\u{f1c4}', // 
+            "guardfile"     => '\u{e21e}', // 
+            "gz"            => '\u{f410}', // 
+            "h"             => '\u{f0fd}', // 
+            "hbs"           => '\u{e60f}', // 
+            "hpp"           => '\u{f0fd}', // 
+            "hs"            => '\u{e777}', // 
+            "htm"           => '\u{f13b}', // 
+            "html"          => '\u{f13b}', // 
+            "hxx"           => '\u{f0fd}', // 
+            "ico"           => '\u{f1c5}', // 
+            "image"         => '\u{f1c5}', // 
+            "iml"           => '\u{e7b5}', // 
+            "ini"           => '\u{f17a}', // 
+            "ipynb"         => '\u{e606}', // 
+            "iso"           => '\u{e271}', // 
+            "jad"           => '\u{e256}', // 
+            "jar"           => '\u{e204}', // 
+            "java"          => '\u{e204}', // 
+            "jpeg"          => '\u{f1c5}', // 
+            "jpg"           => '\u{f1c5}', // 
+            "js"            => '\u{e74e}', // 
+            "json"          => '\u{e60b}', // 
+            "jsx"           => '\u{e7ba}', // 
+            "ksh"           => '\u{f489}', // 
+            "latex"         => '\u{e600}', // 
+            "less"          => '\u{e758}', // 
+            "lhs"           => '\u{e777}', // 
+            "license"       => '\u{f718}', // 
+            "localized"     => '\u{f179}', // 
+            "lock"          => '\u{f023}', // 
+            "log"           => '\u{f18d}', // 
+            "lua"           => '\u{e620}', // 
+            "lz"            => '\u{f410}', // 
+            "lzh"           => '\u{f410}', // 
+            "lzma"          => '\u{f410}', // 
+            "lzo"           => '\u{f410}', // 
+            "m"             => '\u{e61e}', // 
+            "mm"            => '\u{e61d}', // 
+            "m4a"           => '\u{f001}', // 
+            "markdown"      => '\u{f48a}', // 
+            "md"            => '\u{f48a}', // 
+            "mjs"           => '\u{e74e}', // 
+            "mkd"           => '\u{f48a}', // 
+            "mkv"           => '\u{f03d}', // 
+            "mobi"          => '\u{e28b}', // 
+            "mov"           => '\u{f03d}', // 
+            "mp3"           => '\u{f001}', // 
+            "mp4"           => '\u{f03d}', // 
+            "msi"           => '\u{e70f}', // 
+            "mustache"      => '\u{e60f}', // 
+            "nix"           => '\u{f313}', // 
+            "node"          => '\u{f898}', // 
+            "npmignore"     => '\u{e71e}', // 
+            "odp"           => '\u{f1c4}', // 
+            "ods"           => '\u{f1c3}', // 
+            "odt"           => '\u{f1c2}', // 
+            "ogg"           => '\u{f001}', // 
+            "ogv"           => '\u{f03d}', // 
+            "otf"           => '\u{f031}', // 
+            "patch"         => '\u{f440}', // 
+            "pdf"           => '\u{f1c1}', // 
+            "php"           => '\u{e73d}', // 
+            "pl"            => '\u{e769}', // 
+            "png"           => '\u{f1c5}', // 
+            "ppt"           => '\u{f1c4}', // 
+            "pptx"          => '\u{f1c4}', // 
+            "procfile"      => '\u{e21e}', // 
+            "properties"    => '\u{e60b}', // 
+            "ps1"           => '\u{f489}', // 
+            "psd"           => '\u{e7b8}', // 
+            "pxm"           => '\u{f1c5}', // 
+            "py"            => '\u{e606}', // 
+            "pyc"           => '\u{e606}', // 
+            "r"             => '\u{f25d}', // 
+            "rakefile"      => '\u{e21e}', // 
+            "rar"           => '\u{f410}', // 
+            "razor"         => '\u{f1fa}', // 
+            "rb"            => '\u{e21e}', // 
+            "rdata"         => '\u{f25d}', // 
+            "rdb"           => '\u{e76d}', // 
+            "rdoc"          => '\u{f48a}', // 
+            "rds"           => '\u{f25d}', // 
+            "readme"        => '\u{f48a}', // 
+            "rlib"          => '\u{e7a8}', // 
+            "rmd"           => '\u{f48a}', // 
+            "rpm"           => '\u{e7bb}', // 
+            "rs"            => '\u{e7a8}', // 
+            "rspec"         => '\u{e21e}', // 
+            "rspec_parallel"=> '\u{e21e}', // 
+            "rspec_status"  => '\u{e21e}', // 
+            "rss"           => '\u{f09e}', // 
+            "rtf"           => '\u{f718}', // 
+            "ru"            => '\u{e21e}', // 
+            "rubydoc"       => '\u{e73b}', // 
+            "sass"          => '\u{e603}', // 
+            "scala"         => '\u{e737}', // 
+            "scss"          => '\u{e749}', // 
+            "sh"            => '\u{f489}', // 
+            "shell"         => '\u{f489}', // 
+            "slim"          => '\u{e73b}', // 
+            "sln"           => '\u{e70c}', // 
+            "so"            => '\u{f17c}', // 
+            "sql"           => '\u{f1c0}', // 
+            "sqlite3"       => '\u{e7c4}', // 
+            "styl"          => '\u{e600}', // 
+            "stylus"        => '\u{e600}', // 
+            "svg"           => '\u{f1c5}', // 
+            "swift"         => '\u{e755}', // 
+            "tar"           => '\u{f410}', // 
+            "taz"           => '\u{f410}', // 
+            "tbz"           => '\u{f410}', // 
+            "tbz2"          => '\u{f410}', // 
+            "tex"           => '\u{e600}', // 
+            "tiff"          => '\u{f1c5}', // 
+            "toml"          => '\u{e615}', // 
+            "ts"            => '\u{e628}', // 
+            "tsv"           => '\u{f1c3}', // 
+            "tsx"           => '\u{e7ba}', // 
+            "ttf"           => '\u{f031}', // 
+            "twig"          => '\u{e61c}', // 
+            "txt"           => '\u{f15c}', // 
+            "tz"            => '\u{f410}', // 
+            "tzo"           => '\u{f410}', // 
+            "video"         => '\u{f03d}', // 
+            "vim"           => '\u{e62b}', // 
+            "vue"           => '\u{fd42}', // ﵂
+            "war"           => '\u{e256}', // 
+            "wav"           => '\u{f001}', // 
+            "webm"          => '\u{f03d}', // 
+            "webp"          => '\u{f1c5}', // 
+            "windows"       => '\u{f17a}', // 
+            "woff"          => '\u{f031}', // 
+            "woff2"         => '\u{f031}', // 
+            "xhtml"         => '\u{f13b}', // 
+            "xls"           => '\u{f1c3}', // 
+            "xlsx"          => '\u{f1c3}', // 
+            "xml"           => '\u{fabf}', // 謹
+            "xul"           => '\u{fabf}', // 謹
+            "xz"            => '\u{f410}', // 
+            "yaml"          => '\u{f481}', // 
+            "yml"           => '\u{f481}', // 
+            "zip"           => '\u{f410}', // 
+            "zsh"           => '\u{f489}', // 
+            "zsh-theme"     => '\u{f489}', // 
+            "zshrc"         => '\u{f489}', // 
+            _               => '\u{f15b}'  // 
         }
     }
     else {

+ 4 - 1
src/output/lines.rs

@@ -3,6 +3,7 @@ use std::io::{self, Write};
 use ansi_term::ANSIStrings;
 
 use crate::fs::File;
+use crate::fs::filter::FileFilter;
 use crate::output::cell::TextCellContents;
 use crate::output::file_name::{Options as FileStyle};
 use crate::theme::Theme;
@@ -13,10 +14,12 @@ pub struct Render<'a> {
     pub files: Vec<File<'a>>,
     pub theme: &'a Theme,
     pub file_style: &'a FileStyle,
+    pub filter: &'a FileFilter,
 }
 
 impl<'a> Render<'a> {
-    pub fn render<W: Write>(&self, w: &mut W) -> io::Result<()> {
+    pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {
+        self.filter.sort_files(&mut self.files);
         for file in &self.files {
             let name_cell = self.render_file(file);
             writeln!(w, "{}", ANSIStrings(&name_cell))?;

+ 1 - 1
src/output/table.rs

@@ -107,7 +107,7 @@ impl Columns {
             columns.push(Column::Timestamp(TimeType::Accessed));
         }
 
-        if cfg!(feature = "git") && self.git && actually_enable_git {
+        if self.git && actually_enable_git {
             columns.push(Column::GitStatus);
         }
 

+ 1 - 2
xtests/grid-view.toml

@@ -102,8 +102,7 @@ tags = [ 'env', 'grid' ]
 
 
 # columns and width tests with files
-# (these rely on bash’s glob sort order)
-# (some of the output files also have trailing whitespace)
+# (warning: some of the output files have trailing whitespace)
 
 [[cmd]]
 name = "‘COLUMNS=100 exa’ with file arguments produces a grid of 3 columns, with full paths"

+ 6 - 0
xtests/outputs/exts_grid_sort_name_reverse.ansitxt

@@ -0,0 +1,6 @@
+video.wmv     lossless.flac  crypto.signature   compressed.tar.gz  backup~
+VIDEO.AVI     image.svg      crypto.asc         compressed.deb     #SAVEFILE#
+MUSIC.OGG     IMAGE.PNG      COMPRESSED.ZIP     compiled.o         
+music.mp3     file.tmp       compressed.txz     compiled.js        
+Makefile      DOCUMENT.XLSX  compressed.tgz     compiled.coffee    
+lossless.wav  document.pdf   compressed.tar.xz  compiled.class     

+ 26 - 0
xtests/outputs/exts_oneline_sort_name_reverse.ansitxt

@@ -0,0 +1,26 @@
+video.wmv
+VIDEO.AVI
+MUSIC.OGG
+music.mp3
+Makefile
+lossless.wav
+lossless.flac
+image.svg
+IMAGE.PNG
+file.tmp
+DOCUMENT.XLSX
+document.pdf
+crypto.signature
+crypto.asc
+COMPRESSED.ZIP
+compressed.txz
+compressed.tgz
+compressed.tar.xz
+compressed.tar.gz
+compressed.deb
+compiled.o
+compiled.js
+compiled.coffee
+compiled.class
+backup~
+#SAVEFILE#

+ 13 - 13
xtests/outputs/files_paths_grid_3col.ansitxt

@@ -1,13 +1,13 @@
-/testcases/files/10_bytes  /testcases/files/1_KiB    /testcases/files/5_MiB
-/testcases/files/10_KiB    /testcases/files/1_MiB    /testcases/files/6_bytes
-/testcases/files/10_MiB    /testcases/files/2_bytes  /testcases/files/6_KiB
-/testcases/files/11_bytes  /testcases/files/2_KiB    /testcases/files/6_MiB
-/testcases/files/11_KiB    /testcases/files/2_MiB    /testcases/files/7_bytes
-/testcases/files/11_MiB    /testcases/files/3_bytes  /testcases/files/7_KiB
-/testcases/files/12_bytes  /testcases/files/3_KiB    /testcases/files/7_MiB
-/testcases/files/12_KiB    /testcases/files/3_MiB    /testcases/files/8_bytes
-/testcases/files/12_MiB    /testcases/files/4_bytes  /testcases/files/8_KiB
-/testcases/files/13_bytes  /testcases/files/4_KiB    /testcases/files/8_MiB
-/testcases/files/13_KiB    /testcases/files/4_MiB    /testcases/files/9_bytes
-/testcases/files/13_MiB    /testcases/files/5_bytes  /testcases/files/9_KiB
-/testcases/files/1_bytes   /testcases/files/5_KiB    /testcases/files/9_MiB
+/testcases/files/1_bytes  /testcases/files/5_KiB    /testcases/files/9_MiB
+/testcases/files/1_KiB    /testcases/files/5_MiB    /testcases/files/10_bytes
+/testcases/files/1_MiB    /testcases/files/6_bytes  /testcases/files/10_KiB
+/testcases/files/2_bytes  /testcases/files/6_KiB    /testcases/files/10_MiB
+/testcases/files/2_KiB    /testcases/files/6_MiB    /testcases/files/11_bytes
+/testcases/files/2_MiB    /testcases/files/7_bytes  /testcases/files/11_KiB
+/testcases/files/3_bytes  /testcases/files/7_KiB    /testcases/files/11_MiB
+/testcases/files/3_KiB    /testcases/files/7_MiB    /testcases/files/12_bytes
+/testcases/files/3_MiB    /testcases/files/8_bytes  /testcases/files/12_KiB
+/testcases/files/4_bytes  /testcases/files/8_KiB    /testcases/files/12_MiB
+/testcases/files/4_KiB    /testcases/files/8_MiB    /testcases/files/13_bytes
+/testcases/files/4_MiB    /testcases/files/9_bytes  /testcases/files/13_KiB
+/testcases/files/5_bytes  /testcases/files/9_KiB    /testcases/files/13_MiB

+ 8 - 8
xtests/outputs/files_paths_grid_5col.ansitxt

@@ -1,8 +1,8 @@
-/testcases/files/10_bytes  /testcases/files/12_MiB    /testcases/files/2_KiB    /testcases/files/5_bytes  /testcases/files/7_MiB
-/testcases/files/10_KiB    /testcases/files/13_bytes  /testcases/files/2_MiB    /testcases/files/5_KiB    /testcases/files/8_bytes
-/testcases/files/10_MiB    /testcases/files/13_KiB    /testcases/files/3_bytes  /testcases/files/5_MiB    /testcases/files/8_KiB
-/testcases/files/11_bytes  /testcases/files/13_MiB    /testcases/files/3_KiB    /testcases/files/6_bytes  /testcases/files/8_MiB
-/testcases/files/11_KiB    /testcases/files/1_bytes   /testcases/files/3_MiB    /testcases/files/6_KiB    /testcases/files/9_bytes
-/testcases/files/11_MiB    /testcases/files/1_KiB     /testcases/files/4_bytes  /testcases/files/6_MiB    /testcases/files/9_KiB
-/testcases/files/12_bytes  /testcases/files/1_MiB     /testcases/files/4_KiB    /testcases/files/7_bytes  /testcases/files/9_MiB
-/testcases/files/12_KiB    /testcases/files/2_bytes   /testcases/files/4_MiB    /testcases/files/7_KiB    
+/testcases/files/1_bytes  /testcases/files/3_MiB    /testcases/files/6_KiB    /testcases/files/9_bytes   /testcases/files/11_MiB
+/testcases/files/1_KiB    /testcases/files/4_bytes  /testcases/files/6_MiB    /testcases/files/9_KiB     /testcases/files/12_bytes
+/testcases/files/1_MiB    /testcases/files/4_KiB    /testcases/files/7_bytes  /testcases/files/9_MiB     /testcases/files/12_KiB
+/testcases/files/2_bytes  /testcases/files/4_MiB    /testcases/files/7_KiB    /testcases/files/10_bytes  /testcases/files/12_MiB
+/testcases/files/2_KiB    /testcases/files/5_bytes  /testcases/files/7_MiB    /testcases/files/10_KiB    /testcases/files/13_bytes
+/testcases/files/2_MiB    /testcases/files/5_KiB    /testcases/files/8_bytes  /testcases/files/10_MiB    /testcases/files/13_KiB
+/testcases/files/3_bytes  /testcases/files/5_MiB    /testcases/files/8_KiB    /testcases/files/11_bytes  /testcases/files/13_MiB
+/testcases/files/3_KiB    /testcases/files/6_bytes  /testcases/files/8_MiB    /testcases/files/11_KiB    

+ 6 - 6
xtests/outputs/files_paths_grid_7col.ansitxt

@@ -1,6 +1,6 @@
-/testcases/files/10_bytes  /testcases/files/12_bytes  /testcases/files/1_bytes  /testcases/files/3_bytes  /testcases/files/5_bytes  /testcases/files/7_bytes  /testcases/files/9_bytes
-/testcases/files/10_KiB    /testcases/files/12_KiB    /testcases/files/1_KiB    /testcases/files/3_KiB    /testcases/files/5_KiB    /testcases/files/7_KiB    /testcases/files/9_KiB
-/testcases/files/10_MiB    /testcases/files/12_MiB    /testcases/files/1_MiB    /testcases/files/3_MiB    /testcases/files/5_MiB    /testcases/files/7_MiB    /testcases/files/9_MiB
-/testcases/files/11_bytes  /testcases/files/13_bytes  /testcases/files/2_bytes  /testcases/files/4_bytes  /testcases/files/6_bytes  /testcases/files/8_bytes  
-/testcases/files/11_KiB    /testcases/files/13_KiB    /testcases/files/2_KiB    /testcases/files/4_KiB    /testcases/files/6_KiB    /testcases/files/8_KiB    
-/testcases/files/11_MiB    /testcases/files/13_MiB    /testcases/files/2_MiB    /testcases/files/4_MiB    /testcases/files/6_MiB    /testcases/files/8_MiB    
+/testcases/files/1_bytes  /testcases/files/3_bytes  /testcases/files/5_bytes  /testcases/files/7_bytes  /testcases/files/9_bytes   /testcases/files/11_bytes  /testcases/files/13_bytes
+/testcases/files/1_KiB    /testcases/files/3_KiB    /testcases/files/5_KiB    /testcases/files/7_KiB    /testcases/files/9_KiB     /testcases/files/11_KiB    /testcases/files/13_KiB
+/testcases/files/1_MiB    /testcases/files/3_MiB    /testcases/files/5_MiB    /testcases/files/7_MiB    /testcases/files/9_MiB     /testcases/files/11_MiB    /testcases/files/13_MiB
+/testcases/files/2_bytes  /testcases/files/4_bytes  /testcases/files/6_bytes  /testcases/files/8_bytes  /testcases/files/10_bytes  /testcases/files/12_bytes  
+/testcases/files/2_KiB    /testcases/files/4_KiB    /testcases/files/6_KiB    /testcases/files/8_KiB    /testcases/files/10_KiB    /testcases/files/12_KiB    
+/testcases/files/2_MiB    /testcases/files/4_MiB    /testcases/files/6_MiB    /testcases/files/8_MiB    /testcases/files/10_MiB    /testcases/files/12_MiB    

+ 19 - 0
xtests/sorting.toml

@@ -136,3 +136,22 @@ stdout = { string = "plum\npear\npeach" }
 stderr = { empty = true }
 status = 0
 tags = [ 'oneline', 'sort', 'dates' ]
+
+# sorting with arguments specified
+
+[[cmd]]
+name = "‘exa -G --sort=name -r’ with file arguments sorts by file name in reverse order"
+shell = "cd /testcases/file-names-exts; exa -G --sort=name -r *"
+environment = { COLUMNS = "80" }
+stdout = { file = "outputs/exts_grid_sort_name_reverse.ansitxt" }
+stderr = { empty = true }
+status = 0
+tags = [ 'grid', 'sort', 'reverse' ]
+
+[[cmd]]
+name = "‘exa -1 --sort=name -r’ with file arguments sorts by file name in reverse order"
+shell = "cd /testcases/file-names-exts; exa -1 --sort=name -r *"
+stdout = { file = "outputs/exts_oneline_sort_name_reverse.ansitxt" }
+stderr = { empty = true }
+status = 0
+tags = [ 'oneline', 'sort', 'reverse' ]