Full Code of ogham/exa for AI

master 3d1edbb47052 cached
250 files
720.4 KB
285.4k tokens
738 symbols
1 requests
Download .txt
Showing preview only (782K chars total). Download the full file or copy to clipboard to get everything.
Repository: ogham/exa
Branch: master
Commit: 3d1edbb47052
Files: 250
Total size: 720.4 KB

Directory structure:
gitextract_pkcv6wm8/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   └── bug_report.md
│   └── workflows/
│       └── unit-tests.yml
├── .gitignore
├── .rustfmt.toml
├── Cargo.toml
├── Justfile
├── LICENCE
├── README.md
├── Vagrantfile
├── build.rs
├── completions/
│   ├── bash/
│   │   └── exa
│   ├── fish/
│   │   └── exa.fish
│   └── zsh/
│       └── _exa
├── devtools/
│   ├── README.md
│   ├── dev-bash.sh
│   ├── dev-create-test-filesystem.sh
│   ├── dev-fixtures.sh
│   ├── dev-help.sh
│   ├── dev-package-for-linux.sh
│   ├── dev-run-debug.sh
│   ├── dev-run-release.sh
│   ├── dev-set-up-environment.sh
│   ├── dev-versions.sh
│   └── local-package-for-macos.sh
├── man/
│   ├── exa.1.md
│   └── exa_colors.5.md
├── rust-toolchain.toml
├── snap/
│   ├── .gitignore
│   └── snapcraft.yaml
├── src/
│   ├── fs/
│   │   ├── dir.rs
│   │   ├── dir_action.rs
│   │   ├── feature/
│   │   │   ├── git.rs
│   │   │   ├── mod.rs
│   │   │   └── xattr.rs
│   │   ├── fields.rs
│   │   ├── file.rs
│   │   ├── filter.rs
│   │   └── mod.rs
│   ├── info/
│   │   ├── filetype.rs
│   │   ├── mod.rs
│   │   └── sources.rs
│   ├── logger.rs
│   ├── main.rs
│   ├── options/
│   │   ├── dir_action.rs
│   │   ├── error.rs
│   │   ├── file_name.rs
│   │   ├── filter.rs
│   │   ├── flags.rs
│   │   ├── help.rs
│   │   ├── mod.rs
│   │   ├── parser.rs
│   │   ├── theme.rs
│   │   ├── vars.rs
│   │   ├── version.rs
│   │   └── view.rs
│   ├── output/
│   │   ├── cell.rs
│   │   ├── details.rs
│   │   ├── escape.rs
│   │   ├── file_name.rs
│   │   ├── grid.rs
│   │   ├── grid_details.rs
│   │   ├── icons.rs
│   │   ├── lines.rs
│   │   ├── mod.rs
│   │   ├── render/
│   │   │   ├── blocks.rs
│   │   │   ├── filetype.rs
│   │   │   ├── git.rs
│   │   │   ├── groups.rs
│   │   │   ├── inode.rs
│   │   │   ├── links.rs
│   │   │   ├── mod.rs
│   │   │   ├── octal.rs
│   │   │   ├── permissions.rs
│   │   │   ├── size.rs
│   │   │   ├── times.rs
│   │   │   └── users.rs
│   │   ├── table.rs
│   │   ├── time.rs
│   │   └── tree.rs
│   └── theme/
│       ├── default_theme.rs
│       ├── lsc.rs
│       ├── mod.rs
│       └── ui_styles.rs
└── xtests/
    ├── README.md
    ├── attributes.toml
    ├── colour-term.toml
    ├── debug-logging.toml
    ├── details-view-dates.toml
    ├── details-view-filesizes.toml
    ├── details-view-passwd.toml
    ├── details-view-permissions.toml
    ├── details-view.toml
    ├── dotfiles.toml
    ├── errors.toml
    ├── features/
    │   ├── none.toml
    │   └── outputs/
    │       └── disabled_git.txt
    ├── git-ignore.toml
    ├── git.toml
    ├── grid-details-view.toml
    ├── grid-view.toml
    ├── help.toml
    ├── icons.toml
    ├── ignore-glob.toml
    ├── input-options.toml
    ├── lines-view.toml
    ├── outputs/
    │   ├── attributes_files_xattrs_tree.ansitxt
    │   ├── attributes_xattrs_long_tree.ansitxt
    │   ├── attributes_xattrs_tree.ansitxt
    │   ├── dates_long_currentyear_localefr.ansitxt
    │   ├── dates_long_localefr.ansitxt
    │   ├── dates_long_localejp.ansitxt
    │   ├── dates_long_time_accessed.ansitxt
    │   ├── dates_long_time_created.ansitxt
    │   ├── dates_long_time_modified.ansitxt
    │   ├── dates_long_timestyle_fulliso.ansitxt
    │   ├── dates_long_timestyle_iso.ansitxt
    │   ├── dates_long_timestyle_longiso.ansitxt
    │   ├── dev_long.ansitxt
    │   ├── dirs_grid.ansitxt
    │   ├── dirs_oneline.ansitxt
    │   ├── error_columns_invalid.ansitxt
    │   ├── error_columns_nines.ansitxt
    │   ├── error_grid_rows_invalid.ansitxt
    │   ├── error_grid_rows_nines.ansitxt
    │   ├── error_icon_spacing_invalid.ansitxt
    │   ├── error_icon_spacing_nines.ansitxt
    │   ├── error_invalid_option.ansitxt
    │   ├── error_level_invalid.ansitxt
    │   ├── error_level_nines.ansitxt
    │   ├── error_tree_all_all.ansitxt
    │   ├── exts_compressed_paths_themed.ansitxt
    │   ├── exts_compressed_paths_themed_reset.ansitxt
    │   ├── exts_grid_monochrome.ansitxt
    │   ├── exts_grid_sort_name_reverse.ansitxt
    │   ├── exts_oneline_icons.ansitxt
    │   ├── exts_oneline_sort_ext.ansitxt
    │   ├── exts_oneline_sort_extcase.ansitxt
    │   ├── exts_oneline_sort_name.ansitxt
    │   ├── exts_oneline_sort_name_reverse.ansitxt
    │   ├── exts_oneline_sort_namecase.ansitxt
    │   ├── exts_themed_reset.ansitxt
    │   ├── far_dates_long.ansitxt
    │   ├── files_grid_13col.ansitxt
    │   ├── files_grid_20col.ansitxt
    │   ├── files_grid_4col.ansitxt
    │   ├── files_grid_8col.ansitxt
    │   ├── files_grid_icons.ansitxt
    │   ├── files_grid_monochrome.ansitxt
    │   ├── files_long.ansitxt
    │   ├── files_long_binary.ansitxt
    │   ├── files_long_bytes.ansitxt
    │   ├── files_long_colourscale.ansitxt
    │   ├── files_long_colourscale_binary.ansitxt
    │   ├── files_long_colourscale_bytes.ansitxt
    │   ├── files_long_grid_1col.ansitxt
    │   ├── files_long_grid_2col.ansitxt
    │   ├── files_long_grid_3col.ansitxt
    │   ├── files_long_grid_4col.ansitxt
    │   ├── files_long_grid_exa_grid_rows_2_3files.ansitxt
    │   ├── files_long_grid_exa_grid_rows_5_15files.ansitxt
    │   ├── files_long_grid_exa_grid_rows_6_15files.ansitxt
    │   ├── files_long_grid_header_1file.ansitxt
    │   ├── files_long_grid_header_2files.ansitxt
    │   ├── files_long_grid_icons.ansitxt
    │   ├── files_long_header.ansitxt
    │   ├── files_long_header_binary.ansitxt
    │   ├── files_long_header_bytes.ansitxt
    │   ├── files_long_icons.ansitxt
    │   ├── files_long_monochrome.ansitxt
    │   ├── files_long_tree_icons.ansitxt
    │   ├── files_oneline_icons.ansitxt
    │   ├── files_paths_grid_3col.ansitxt
    │   ├── files_paths_grid_5col.ansitxt
    │   ├── files_paths_grid_7col.ansitxt
    │   ├── files_paths_long_grid_1col.ansitxt
    │   ├── files_paths_long_grid_2col.ansitxt
    │   ├── files_paths_long_grid_3col.ansitxt
    │   ├── files_tree_icons.ansitxt
    │   ├── git1+2_long.ansitxt
    │   ├── git1+2_long_directories.ansitxt
    │   ├── git1+2_long_nested.ansitxt
    │   ├── git1_long.ansitxt
    │   ├── git1_long_additions.ansitxt
    │   ├── git1_long_edits.ansitxt
    │   ├── git1_long_moves.ansitxt
    │   ├── git1_long_multiple.ansitxt
    │   ├── git1_long_recurse.ansitxt
    │   ├── git1_long_tree.ansitxt
    │   ├── git1_paths_long_grid.ansitxt
    │   ├── git2_ignoreds_grid_gitignore.ansitxt
    │   ├── git2_ignoreds_lines_gitignore.ansitxt
    │   ├── git2_ignoreds_long_gitignore.ansitxt
    │   ├── git2_ignoreds_long_grid_gitignore.ansitxt
    │   ├── git2_ignoreds_long_recurse_gitignore.ansitxt
    │   ├── git2_ignoreds_long_tree_gitignore.ansitxt
    │   ├── git2_ignoreds_tree_gitignore.ansitxt
    │   ├── git2_long.ansitxt
    │   ├── git2_long_ignoredcontent.ansitxt
    │   ├── git2_long_ignoreddir.ansitxt
    │   ├── git2_long_ignorednested.ansitxt
    │   ├── git2_long_multiple.ansitxt
    │   ├── git2_long_nested.ansitxt
    │   ├── git2_long_recurse.ansitxt
    │   ├── git2_long_recurse_gitignore.ansitxt
    │   ├── git2_long_tree.ansitxt
    │   ├── git2_long_tree_gitignore.ansitxt
    │   ├── git2_tree_gitignore.ansitxt
    │   ├── git3_long.ansitxt
    │   ├── git4_long.ansitxt
    │   ├── help.ansitxt
    │   ├── hiddens_grid.ansitxt
    │   ├── hiddens_grid_all.ansitxt
    │   ├── hiddens_grid_all_all.ansitxt
    │   ├── hiddens_long.ansitxt
    │   ├── hiddens_long_all.ansitxt
    │   ├── hiddens_long_all_all.ansitxt
    │   ├── links_grid.ansitxt
    │   ├── links_grid_monochrome.ansitxt
    │   ├── links_lines.ansitxt
    │   ├── links_long_classify.ansitxt
    │   ├── links_oneline_icons.ansitxt
    │   ├── links_oneline_icons_width0.ansitxt
    │   ├── links_oneline_icons_width2.ansitxt
    │   ├── links_oneline_icons_width3.ansitxt
    │   ├── links_oneline_sort_type.ansitxt
    │   ├── links_oneline_themed.ansitxt
    │   ├── links_paths_lines.ansitxt
    │   ├── links_tree.ansitxt
    │   ├── links_xattrs_tree.ansitxt
    │   ├── names_grid.ansitxt
    │   ├── names_grid_across.ansitxt
    │   ├── names_grid_recurse.ansitxt
    │   ├── names_lines.ansitxt
    │   ├── names_tree.ansitxt
    │   ├── passwd_long_group_header.ansitxt
    │   ├── permissions_long_group_header.ansitxt
    │   ├── permissions_long_group_header_sudo.ansitxt
    │   ├── permissions_long_themed.ansitxt
    │   ├── permissions_oneline_icons.ansitxt
    │   ├── proc_1_root.ansitxt
    │   ├── proc_1_root_xattrs.ansitxt
    │   ├── specials_long.ansitxt
    │   ├── specials_long_classify.ansitxt
    │   ├── specials_oneline_sort_type.ansitxt
    │   └── specials_oneline_themed.ansitxt
    ├── run.sh
    ├── sorting.toml
    ├── themes.toml
    └── tree-view.toml

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
github: ogham


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: exa is unmaintained
about: Please use the active fork eza instead. <https://github.com/eza-community/eza>
---

exa is unmaintained, please use the active fork eza instead. <https://github.com/eza-community/eza>

---


================================================
FILE: .github/workflows/unit-tests.yml
================================================
name: Unit tests

on:
  push:
    branches: [ master ]
    paths:
      - '.github/workflows/*'
      - 'src/**'
      - 'Cargo.*'
      - build.rs
  pull_request:
    branches: [ master ]
    paths:
      - '.github/workflows/*'
      - 'src/**'
      - 'Cargo.*'
      - build.rs

env:
  CARGO_TERM_COLOR: always

jobs:
  unit-tests:
    runs-on: ${{ matrix.os }}

    continue-on-error: ${{ matrix.rust == 'nightly' }}

    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        rust: [1.66.1, stable, beta, nightly]

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: ${{ matrix.rust }}

      - name: Install cargo-hack
        run: cargo install cargo-hack@0.5.27

      - name: Run unit tests
        run: cargo hack test --feature-powerset


================================================
FILE: .gitignore
================================================
# Rust stuff
target

# Vagrant stuff
.vagrant
*.log

# Compiled artifacts
# (see devtools/*-package-for-*.sh)
/exa-linux-x86_64
/exa-linux-x86_64-*.zip
/exa-macos-x86_64
/exa-macos-x86_64-*.zip
/MD5SUMS
/SHA1SUMS

# Snap stuff
parts
prime
stage
*.snap


================================================
FILE: .rustfmt.toml
================================================
disable_all_formatting = true


================================================
FILE: Cargo.toml
================================================
[package]
name = "exa"
description = "A modern replacement for ls"
authors = ["Benjamin Sago <ogham@bsago.me>"]
categories = ["command-line-utilities"]
edition = "2021"
rust-version = "1.66.1"
exclude = ["/devtools/*", "/Justfile", "/Vagrantfile", "/screenshots.png"]
readme = "README.md"
homepage = "https://the.exa.website/"
license = "MIT"
repository = "https://github.com/ogham/exa"
version = "0.10.1"


[[bin]]
name = "exa"


[dependencies]
ansi_term = "0.12"
glob = "0.3"
lazy_static = "1.3"
libc = "0.2"
locale = "0.2"
log = "0.4"
natord = "1.0"
num_cpus = "1.10"
number_prefix = "0.4"
scoped_threadpool = "0.1"
term_grid = "0.2.0"
terminal_size = "0.1.16"
unicode-width = "0.1"
zoneinfo_compiled = "0.5.1"

[target.'cfg(unix)'.dependencies]
users = "0.11"

[dependencies.datetime]
version = "0.5.2"
default-features = false
features = ["format"]

[dependencies.git2]
version = "0.13"
optional = true
default-features = false

[build-dependencies.datetime]
version = "0.5.2"
default-features = false

[features]
default = [ "git" ]
git = [ "git2" ]
vendored-openssl = ["git2/vendored-openssl"]


# make dev builds faster by excluding debug symbols
[profile.dev]
debug = false

# use LTO for smaller binaries (that take longer to build)
[profile.release]
lto = true


[package.metadata.deb]
license-file = [ "LICENCE", "4" ]
depends = "$auto"
extended-description = """
exa is a replacement for ls written in Rust.
"""
section = "utils"
priority = "optional"
assets = [
    [ "target/release/exa", "/usr/bin/exa", "0755" ],
    [ "target/release/../man/exa.1", "/usr/share/man/man1/exa.1", "0644" ],
    [ "target/release/../man/exa_colors.5", "/usr/share/man/man5/exa_colors.5", "0644" ],
    [ "completions/bash/exa", "/usr/share/bash-completion/completions/exa", "0644" ],
    [ "completions/zsh/_exa", "/usr/share/zsh/site-functions/_exa", "0644" ],
    [ "completions/fish/exa.fish", "/usr/share/fish/vendor_completions.d/exa.fish", "0644" ],
]


================================================
FILE: Justfile
================================================
all: build test
all-release: build-release test-release


#----------#
# building #
#----------#

# compile the exa binary
@build:
    cargo build

# compile the exa binary (in release mode)
@build-release:
    cargo build --release --verbose

# produce an HTML chart of compilation timings
@build-time:
    cargo +nightly clean
    cargo +nightly build -Z timings

# check that the exa binary can compile
@check:
    cargo check


#---------------#
# running tests #
#---------------#

# run unit tests
@test:
    cargo test --workspace -- --quiet

# run unit tests (in release mode)
@test-release:
    cargo test --workspace --release --verbose


#------------------------#
# running extended tests #
#------------------------#

# run extended tests
@xtests:
    xtests/run.sh

# run extended tests (using the release mode exa)
@xtests-release:
    xtests/run.sh --release

# display the number of extended tests that get run
@count-xtests:
    grep -F '[[cmd]]' -R xtests | wc -l


#-----------------------#
# code quality and misc #
#-----------------------#

# lint the code
@clippy:
    touch src/main.rs
    cargo clippy

# update dependency versions, and checks for outdated ones
@update-deps:
    cargo update
    command -v cargo-outdated >/dev/null || (echo "cargo-outdated not installed" && exit 1)
    cargo outdated

# list unused dependencies
@unused-deps:
    command -v cargo-udeps >/dev/null || (echo "cargo-udeps not installed" && exit 1)
    cargo +nightly udeps

# check that every combination of feature flags is successful
@check-features:
    command -v cargo-hack >/dev/null || (echo "cargo-hack not installed" && exit 1)
    cargo hack check --feature-powerset

# build exa and run extended tests with features disabled
@feature-checks *args:
    cargo build --no-default-features
    specsheet xtests/features/none.toml -shide {{args}} \
        -O cmd.target.exa="${CARGO_TARGET_DIR:-../../target}/debug/exa"

# print versions of the necessary build tools
@versions:
    rustc --version
    cargo --version


#---------------#
# documentation #
#---------------#

# build the man pages
@man:
    mkdir -p "${CARGO_TARGET_DIR:-target}/man"
    pandoc --standalone -f markdown -t man man/exa.1.md        > "${CARGO_TARGET_DIR:-target}/man/exa.1"
    pandoc --standalone -f markdown -t man man/exa_colors.5.md > "${CARGO_TARGET_DIR:-target}/man/exa_colors.5"

# build and preview the main man page (exa.1)
@man-1-preview: man
    man "${CARGO_TARGET_DIR:-target}/man/exa.1"

# build and preview the colour configuration man page (exa_colors.5)
@man-5-preview: man
    man "${CARGO_TARGET_DIR:-target}/man/exa_colors.5"


================================================
FILE: LICENCE
================================================
The MIT License (MIT)

Copyright (c) 2014 Benjamin Sago

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# exa is unmaintained, use the [fork eza](https://github.com/eza-community/eza) instead.

(This repository isn’t archived because the only person with the rights to do so is unreachable).

---

<div align="center">

# exa

[exa](https://the.exa.website/) is a modern replacement for _ls_.

**README Sections:** [Options](#options) — [Installation](#installation) — [Development](#development)

[![Unit tests](https://github.com/ogham/exa/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/ogham/exa/actions/workflows/unit-tests.yml)
</div>

![Screenshots of exa](screenshots.png)

---

**exa** is a modern replacement for the venerable file-listing command-line program `ls` that ships with Unix and Linux operating systems, giving it more features and better defaults.
It uses colours to distinguish file types and metadata.
It knows about symlinks, extended attributes, and Git.
And it’s **small**, **fast**, and just **one single binary**.

By deliberately making some decisions differently, exa attempts to be a more featureful, more user-friendly version of `ls`.
For more information, see [exa’s website](https://the.exa.website/).


---

<a id="options">
<h1>Command-line options</h1>
</a>

exa’s options are almost, but not quite, entirely unlike `ls`’s.

### Display options

- **-1**, **--oneline**: display one entry per line
- **-G**, **--grid**: display entries as a grid (default)
- **-l**, **--long**: display extended details and attributes
- **-R**, **--recurse**: recurse into directories
- **-T**, **--tree**: recurse into directories as a tree
- **-x**, **--across**: sort the grid across, rather than downwards
- **-F**, **--classify**: display type indicator by file names
- **--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

- **-a**, **--all**: show hidden and 'dot' files
- **-d**, **--list-dirs**: list directories like regular files
- **-L**, **--level=(depth)**: limit the depth of recursion
- **-r**, **--reverse**: reverse the sort order
- **-s**, **--sort=(field)**: which field to sort by
- **--group-directories-first**: list directories before other files
- **-D**, **--only-dirs**: list only directories
- **--git-ignore**: ignore files mentioned in `.gitignore`
- **-I**, **--ignore-glob=(globs)**: glob patterns (pipe-separated) of files to ignore

Pass the `--all` option twice to also show the `.` and `..` directories.

### Long view options

These options are available when running with `--long` (`-l`):

- **-b**, **--binary**: list file sizes with binary prefixes
- **-B**, **--bytes**: list file sizes in bytes, without any prefixes
- **-g**, **--group**: list each file’s group
- **-h**, **--header**: add a header row to each column
- **-H**, **--links**: list each file’s number of hard links
- **-i**, **--inode**: list each file’s inode number
- **-m**, **--modified**: use the modified timestamp field
- **-S**, **--blocks**: list each file’s number of file system blocks
- **-t**, **--time=(field)**: which timestamp field to use
- **-u**, **--accessed**: use the accessed timestamp field
- **-U**, **--created**: use the created timestamp field
- **-@**, **--extended**: list each file’s extended attributes and sizes
- **--changed**: use the changed timestamp field
- **--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

Some of the options accept parameters:

- Valid **--color** options are **always**, **automatic**, and **never**.
- Valid sort fields are **accessed**, **changed**, **created**, **extension**, **Extension**, **inode**, **modified**, **name**, **Name**, **size**, **type**, and **none**. Fields starting with a capital letter sort uppercase before lowercase. The modified field has the aliases **date**, **time**, and **newest**, while its reverse has the aliases **age** and **oldest**.
- Valid time fields are **modified**, **changed**, **accessed**, and **created**.
- Valid time styles are **default**, **iso**, **long-iso**, and **full-iso**.


---

<a id="installation">
<h1>Installation</h1>
</a>

exa is available for macOS and Linux.
More information on how to install exa is available on [the Installation page](https://the.exa.website/install).

### Alpine Linux

On Alpine Linux, [enable community repository](https://wiki.alpinelinux.org/wiki/Enable_Community_Repository) and install the [`exa`](https://pkgs.alpinelinux.org/package/edge/community/x86_64/exa) package.

    apk add exa

### Arch Linux

On Arch, install the [`exa`](https://www.archlinux.org/packages/community/x86_64/exa/) package.

    pacman -S exa

### Android / Termux

On Android / Termux, install the [`exa`](https://github.com/termux/termux-packages/tree/master/packages/exa) package.

    pkg install exa

### Debian

On Debian, install the [`exa`](https://packages.debian.org/stable/exa) package.

    apt install exa

### Fedora

On Fedora, install the [`exa`](https://src.fedoraproject.org/modules/exa) package.

    dnf install exa

### Gentoo

On Gentoo, install the [`sys-apps/exa`](https://packages.gentoo.org/packages/sys-apps/exa) package.

    emerge sys-apps/exa

### Homebrew

If you’re using [Homebrew](https://brew.sh/) on macOS, install the [`exa`](http://formulae.brew.sh/formula/exa) formula.

    brew install exa

### MacPorts

If you're using [MacPorts](https://www.macports.org/) on macOS, install the [`exa`](https://ports.macports.org/port/exa/summary) port.

    port install exa

### Nix

On nixOS, install the [`exa`](https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/misc/exa/default.nix) package.

    nix-env -i exa

### openSUSE

On openSUSE, install the [`exa`](https://software.opensuse.org/package/exa) package.

    zypper install exa

### Ubuntu

On Ubuntu 20.10 (Groovy Gorilla) and later, install the [`exa`](https://packages.ubuntu.com/jammy/exa) package.

    sudo apt install exa

### Void Linux

On Void Linux, install the [`exa`](https://github.com/void-linux/void-packages/blob/master/srcpkgs/exa/template) package.

    xbps-install -S exa

### Manual installation from GitHub

Compiled binary versions of exa are uploaded to GitHub when a release is made.
You can install exa manually by [downloading a release](https://github.com/ogham/exa/releases), extracting it, and copying the binary to a directory in your `$PATH`, such as `/usr/local/bin`.

For more information, see the [Manual Installation page](https://the.exa.website/install/linux#manual).

### Cargo

If you already have a Rust environment set up, you can use the `cargo install` command:

    cargo install exa

Cargo will build the `exa` binary and place it in `$HOME/.cargo`.

To build without Git support, run `cargo install --no-default-features exa` is also available, if the requisite dependencies are not installed.


---

<a id="development">
<h1>Development

<a href="https://blog.rust-lang.org/2023/01/10/Rust-1.66.1.html">
    <img src="https://img.shields.io/badge/rustc-1.66.1+-lightgray.svg" alt="Rust 1.66.1+" />
</a>

<a href="https://github.com/ogham/exa/blob/master/LICENCE">
    <img src="https://img.shields.io/badge/licence-MIT-green" alt="MIT Licence" />
</a>
</h1></a>

exa is written in [Rust](https://www.rust-lang.org/).
You will need rustc version 1.66.1 or higher.
The recommended way to install Rust for development is from the [official download page](https://www.rust-lang.org/tools/install), using rustup.

Once Rust is installed, you can compile exa with Cargo:

    cargo build
    cargo test

- The [just](https://github.com/casey/just) command runner can be used to run some helpful development commands, in a manner similar to `make`.
Run `just --list` to get an overview of what’s available.

- If you are compiling a copy for yourself, be sure to run `cargo build --release` or `just build-release` to benefit from release-mode optimisations.
Copy the resulting binary, which will be in the `target/release` directory, into a folder in your `$PATH`.
`/usr/local/bin` is usually a good choice.

- To compile and install the manual pages, you will need [pandoc](https://pandoc.org/).
The `just man` command will compile the Markdown into manual pages, which it will place in the `target/man` directory.
To use them, copy them into a directory that `man` will read.
`/usr/local/share/man` is usually a good choice.

- exa depends on [libgit2](https://github.com/rust-lang/git2-rs) for certain features.
If you’re unable to compile libgit2, you can opt out of Git support by running `cargo build --no-default-features`.

- If you intend to compile for musl, you will need to use the flag `vendored-openssl` if you want to get the Git feature working.
The full command is `cargo build --release --target=x86_64-unknown-linux-musl --features vendored-openssl,git`.

For more information, see the [Building from Source page](https://the.exa.website/install/source).


### Testing with Vagrant

exa uses [Vagrant][] to configure virtual machines for testing.

Programs such as exa that are basically interfaces to the system are [notoriously difficult to test][testing].
Although the internal components have unit tests, it’s impossible to do a complete end-to-end test without mandating the current user’s name, the time zone, the locale, and directory structure to test.
(And yes, these tests are worth doing. I have missed an edge case on many an occasion.)

The initial attempt to solve the problem was just to create a directory of “awkward” test cases, run exa on it, and make sure it produced the correct output.
But even this output would change if, say, the user’s locale formats dates in a different way.
These can be mocked inside the code, but at the cost of making that code more complicated to read and understand.

An alternative solution is to fake *everything*: create a virtual machine with a known state and run the tests on *that*.
This is what Vagrant does.
Although it takes a while to download and set up, it gives everyone the same development environment to test for any obvious regressions.

[Vagrant]: https://www.vagrantup.com/
[testing]: https://eev.ee/blog/2016/08/22/testing-for-people-who-hate-testing/#troublesome-cases

First, initialise the VM:

    host$ vagrant up

The first command downloads the virtual machine image, and then runs our provisioning script, which installs Rust and exa’s build-time dependencies, configures the environment, and generates some awkward files and folders to use as test cases.
Once this is done, you can SSH in, and build and test:

    host$ vagrant ssh
    vm$ cd /vagrant
    vm$ cargo build
    vm$ ./xtests/run
    All the tests passed!

Of course, the drawback of having a standard development environment is that you stop noticing bugs that occur outside of it.
For this reason, Vagrant isn’t a *necessary* development step — it’s there if you’d like to use it, but exa still gets used and tested on other platforms.
It can still be built and compiled on any target triple that it supports, VM or no VM, with `cargo build` and `cargo test`.


================================================
FILE: Vagrantfile
================================================
Vagrant.configure(2) do |config|

  # We use Ubuntu instead of Debian because the image comes with two-way
  # shared folder support by default.
  UBUNTU = 'hashicorp/bionic64'

  config.vm.define(:exa) do |config|
    config.vm.provider :virtualbox do |v|
      v.name = 'exa'
      v.memory = 2048
      v.cpus = `nproc`.chomp.to_i
    end

    config.vm.provider :vmware_desktop do |v|
      v.vmx['memsize'] = '2048'
      v.vmx['numvcpus'] = `nproc`.chomp
    end

    config.vm.box = UBUNTU
    config.vm.hostname = 'exa'


    # Make sure we know the VM image’s default user name. The ‘cassowary’ user
    # (specified later) is used for most of the test *output*, but we still
    # need to know where the ‘target’ and ‘.cargo’ directories go.
    developer = 'vagrant'


    # Install the dependencies needed for exa to build, as quietly as
    # apt can do.
    config.vm.provision :shell, privileged: true, inline: <<-EOF
      if hash fish &>/dev/null; then
        echo "Tools are already installed"
      else
        trap 'exit' ERR
        echo "Installing tools"
        apt-get update -qq
        apt-get install -qq -o=Dpkg::Use-Pty=0 \
          git gcc curl attr libgit2-dev zip \
          fish zsh bash bash-completion
      fi
    EOF


    # Install Rust.
    # This is done as vagrant, not root, because it’s vagrant
    # who actually uses it. Sent to /dev/null because the progress
    # bar produces a ton of output.
    config.vm.provision :shell, privileged: false, inline: <<-EOF
      if hash rustc &>/dev/null; then
        echo "Rust is already installed"
      else
        trap 'exit' ERR
        echo "Installing Rust"
        curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --profile minimal --component rustc,rust-std,cargo,clippy -y > /dev/null
        source $HOME/.cargo/env
        echo "Installing cargo-hack"
        cargo install -q cargo-hack
        echo "Installing specsheet"
        cargo install -q --git https://github.com/ogham/specsheet
      fi
    EOF


    # Privileged installation and setup scripts.
    config.vm.provision :shell, privileged: true, inline: <<-EOF

      # Install Just, the command runner.
      if hash just &>/dev/null; then
        echo "just is already installed"
      else
        trap 'exit' ERR
        echo "Installing just"
        wget -q "https://github.com/casey/just/releases/download/v0.8.3/just-v0.8.3-x86_64-unknown-linux-musl.tar.gz"
        tar -xf "just-v0.8.3-x86_64-unknown-linux-musl.tar.gz"
        cp just /usr/local/bin
      fi

      # Guarantee that the timezone is UTC — some of the tests
      # depend on this (for now).
      timedatectl set-timezone UTC


      # Use a different ‘target’ directory on the VM than on the host.
      # By default it just uses the one in /vagrant/target, which can
      # cause problems if it has different permissions than the other
      # directories, or contains object files compiled for the host.
      echo 'PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/#{developer}/.cargo/bin"' > /etc/environment
      echo 'CARGO_TARGET_DIR="/home/#{developer}/target"'                                                     >> /etc/environment


      # Create a variety of misc scripts.

      ln -sf /vagrant/devtools/dev-run-debug.sh   /usr/bin/exa
      ln -sf /vagrant/devtools/dev-run-release.sh /usr/bin/rexa

      echo -e "#!/bin/sh\ncargo build --manifest-path /vagrant/Cargo.toml \\$@" > /usr/bin/build-exa
      ln -sf /usr/bin/build-exa /usr/bin/b

      echo -e "#!/bin/sh\ncargo test --manifest-path /vagrant/Cargo.toml \\$@ -- --quiet" > /usr/bin/test-exa
      ln -sf /usr/bin/test-exa /usr/bin/t

      echo -e "#!/bin/sh\n/vagrant/xtests/run.sh" > /usr/bin/run-xtests
      ln -sf /usr/bin/run-xtests /usr/bin/x

      echo -e "#!/bin/sh\nbuild-exa && test-exa && run-xtests" > /usr/bin/compile-exa
      ln -sf /usr/bin/compile-exa /usr/bin/c

      echo -e "#!/bin/sh\nbash /vagrant/devtools/dev-package-for-linux.sh \\$@" > /usr/bin/package-exa
      echo -e "#!/bin/sh\ncat /etc/motd" > /usr/bin/halp

      chmod +x /usr/bin/{exa,rexa,b,t,x,c,build-exa,test-exa,run-xtests,compile-exa,package-exa,halp}


      # Configure the welcoming text that gets shown:

      # Capture the help text so it gets displayed first
      rm -f /etc/update-motd.d/*
      bash /vagrant/devtools/dev-help.sh > /etc/motd

      # Tell bash to execute a bunch of stuff when a session starts
      echo "source /vagrant/devtools/dev-bash.sh" > /home/#{developer}/.bash_profile
      chown #{developer} /home/#{developer}/.bash_profile

      # Disable last login date in sshd
      sed -i '/PrintLastLog yes/c\PrintLastLog no' /etc/ssh/sshd_config
      systemctl restart sshd


      # Link the completion files so they’re “installed”:

      # bash
      test -h /etc/bash_completion.d/exa \
        || ln -s /vagrant/contrib/completions.bash /etc/bash_completion.d/exa

      # zsh
      test -h /usr/share/zsh/vendor-completions/_exa \
        || ln -s /vagrant/contrib/completions.zsh /usr/share/zsh/vendor-completions/_exa

      # fish
      test -h /usr/share/fish/completions/exa.fish \
        || ln -s /vagrant/contrib/completions.fish /usr/share/fish/completions/exa.fish
    EOF


    # Install kcov for test coverage
    # This doesn’t run coverage over the xtests so it’s less useful for now
    if ENV.key?('INSTALL_KCOV')
      config.vm.provision :shell, privileged: false, inline: <<-EOF
        trap 'exit' ERR

        test -e ~/.cargo/bin/cargo-kcov \
          || cargo install cargo-kcov

        sudo apt-get install -qq -o=Dpkg::Use-Pty=0 -y \
          cmake g++ pkg-config \
          libcurl4-openssl-dev libdw-dev binutils-dev libiberty-dev

        cargo kcov --print-install-kcov-sh | sudo sh
      EOF
    end

    config.vm.provision :shell, privileged: true,  path: 'devtools/dev-set-up-environment.sh'
    config.vm.provision :shell, privileged: false, path: 'devtools/dev-create-test-filesystem.sh'
  end
end


================================================
FILE: build.rs
================================================
/// The version string isn’t the simplest: we want to show the version,
/// current Git hash, and compilation date when building *debug* versions, but
/// just the version for *release* versions so the builds are reproducible.
///
/// This script generates the string from the environment variables that Cargo
/// adds (http://doc.crates.io/environment-variables.html) and runs `git` to
/// get the SHA1 hash. It then writes the string into a file, which exa then
/// includes at build-time.
///
/// - https://stackoverflow.com/q/43753491/3484614
/// - https://crates.io/crates/vergen

use std::env;
use std::fs::File;
use std::io::{self, Write};
use std::path::PathBuf;

use datetime::{LocalDateTime, ISO};


/// The build script entry point.
fn main() -> io::Result<()> {
    #![allow(clippy::write_with_newline)]

    let tagline = "exa - list files on the command-line";
    let url     = "https://the.exa.website/";

    let ver =
        if is_debug_build() {
            format!("{}\nv{} \\1;31m(pre-release debug build!)\\0m\n\\1;4;34m{}\\0m", tagline, version_string(), url)
        }
        else if is_development_version() {
            format!("{}\nv{} [{}] built on {} \\1;31m(pre-release!)\\0m\n\\1;4;34m{}\\0m", tagline, version_string(), git_hash(), build_date(), url)
        }
        else {
            format!("{}\nv{}\n\\1;4;34m{}\\0m", tagline, version_string(), url)
        };

    // We need to create these files in the Cargo output directory.
    let out = PathBuf::from(env::var("OUT_DIR").unwrap());
    let path = &out.join("version_string.txt");

    // Bland version text
    let mut f = File::create(path).unwrap_or_else(|_| { panic!("{}", path.to_string_lossy().to_string()) });
    writeln!(f, "{}", strip_codes(&ver))?;

    Ok(())
}

/// Removes escape codes from a string.
fn strip_codes(input: &str) -> String {
    input.replace("\\0m", "")
         .replace("\\1;31m", "")
         .replace("\\1;4;34m", "")
}

/// Retrieve the project’s current Git hash, as a string.
fn git_hash() -> String {
    use std::process::Command;

    String::from_utf8_lossy(
        &Command::new("git")
            .args(&["rev-parse", "--short", "HEAD"])
            .output().unwrap()
            .stdout).trim().to_string()
}

/// Whether we should show pre-release info in the version string.
///
/// Both weekly releases and actual releases are --release releases,
/// but actual releases will have a proper version number.
fn is_development_version() -> bool {
    cargo_version().ends_with("-pre") || env::var("PROFILE").unwrap() == "debug"
}

/// Whether we are building in debug mode.
fn is_debug_build() -> bool {
    env::var("PROFILE").unwrap() == "debug"
}

/// Retrieves the [package] version in Cargo.toml as a string.
fn cargo_version() -> String {
    env::var("CARGO_PKG_VERSION").unwrap()
}

/// Returns the version and build parameters string.
fn version_string() -> String {
    let mut ver = cargo_version();

    let feats = nonstandard_features_string();
    if ! feats.is_empty() {
        ver.push_str(&format!(" [{}]", &feats));
    }

    ver
}

/// Finds whether a feature is enabled by examining the Cargo variable.
fn feature_enabled(name: &str) -> bool {
    env::var(&format!("CARGO_FEATURE_{}", name))
        .map(|e| ! e.is_empty())
        .unwrap_or(false)
}

/// A comma-separated list of non-standard feature choices.
fn nonstandard_features_string() -> String {
    let mut s = Vec::new();

    if feature_enabled("GIT") {
        s.push("+git");
    }
    else {
        s.push("-git");
    }

    s.join(", ")
}

/// Formats the current date as an ISO 8601 string.
fn build_date() -> String {
    let now = LocalDateTime::now();
    format!("{}", now.date().iso())
}


================================================
FILE: completions/bash/exa
================================================
_exa()
{
    cur=${COMP_WORDS[COMP_CWORD]}
    prev=${COMP_WORDS[COMP_CWORD-1]}

    case "$prev" in
        -'?'|--help|-v|--version)
            return
            ;;

        --colour)
            COMPREPLY=( $( compgen -W 'always auto never' -- "$cur" ) )
            return
            ;;

        -L|--level)
            COMPREPLY=( $( compgen -W '{0..9}' -- "$cur" ) )
            return
            ;;

        -s|--sort)
            COMPREPLY=( $( compgen -W 'name filename Name Filename size filesize extension Extension date time modified changed accessed created type inode oldest newest age none --' -- "$cur" ) )
            return
            ;;

        -t|--time)
            COMPREPLY=( $( compgen -W 'modified changed accessed created --' -- "$cur" ) )
            return
            ;;

        --time-style)
            COMPREPLY=( $( compgen -W 'default iso long-iso full-iso --' -- "$cur" ) )
            return
            ;;
    esac

    case "$cur" in
        # _parse_help doesn’t pick up short options when they are on the same line than long options
        --*)
            # colo[u]r isn’t parsed correctly so we filter these options out and add them by hand
            parse_help=$( exa --help | grep -oE ' (\-\-[[:alnum:]@-]+)' | tr -d ' ' | grep -v '\-\-colo' )
            completions=$( echo '--color --colour --color-scale --colour-scale' $parse_help )
            COMPREPLY=( $( compgen -W "$completions" -- "$cur" ) )
            ;;

        -*)
            completions=$( exa --help | grep -oE ' (\-[[:alnum:]@])' | tr -d ' ' )
            COMPREPLY=( $( compgen -W "$completions" -- "$cur" ) )
            ;;

        *)
            _filedir
            ;;
    esac
} &&
complete -o filenames -o bashdefault -F _exa exa


================================================
FILE: completions/fish/exa.fish
================================================
# Meta-stuff
complete -c exa -s 'v' -l 'version' -d "Show version of exa"
complete -c exa -s '?' -l 'help'    -d "Show list of command-line options"

# Display options
complete -c exa -s '1' -l 'oneline'      -d "Display one entry per line"
complete -c exa -s 'l' -l 'long'         -d "Display extended file metadata as a table"
complete -c exa -s 'G' -l 'grid'         -d "Display entries in a grid"
complete -c exa -s 'x' -l 'across'       -d "Sort the grid across, rather than downwards"
complete -c exa -s 'R' -l 'recurse'      -d "Recurse into directories"
complete -c exa -s 'T' -l 'tree'         -d "Recurse into directories as a tree"
complete -c exa -s 'F' -l 'classify'     -d "Display type indicator by file names"
complete -c exa        -l 'color' \
                       -l 'colour'       -d "When to use terminal colours" -x -a "
    always\t'Always use colour'
    auto\t'Use colour if standard output is a terminal'
    never\t'Never use colour'
"
complete -c exa        -l 'color-scale' \
                       -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"
complete -c exa -l 'git-ignore'           -d "Ignore files mentioned in '.gitignore'"
complete -c exa -s 'a' -l 'all'       -d "Show hidden and 'dot' files"
complete -c exa -s 'd' -l 'list-dirs' -d "List directories like regular files"
complete -c exa -s 'L' -l 'level'     -d "Limit the depth of recursion" -x -a "1 2 3 4 5 6 7 8 9"
complete -c exa -s 'r' -l 'reverse'   -d "Reverse the sort order"
complete -c exa -s 's' -l 'sort'      -d "Which field to sort by" -x -a "
    accessed\t'Sort by file accessed time'
    age\t'Sort by file modified time (newest first)'
    changed\t'Sort by changed time'
    created\t'Sort by file modified time'
    date\t'Sort by file modified time'
    ext\t'Sort by file extension'
    Ext\t'Sort by file extension (uppercase first)'
    extension\t'Sort by file extension'
    Extension\t'Sort by file extension (uppercase first)'
    filename\t'Sort by filename'
    Filename\t'Sort by filename (uppercase first)'
    inode\t'Sort by file inode'
    modified\t'Sort by file modified time'
    name\t'Sort by filename'
    Name\t'Sort by filename (uppercase first)'
    newest\t'Sort by file modified time (newest first)'
    none\t'Do not sort files at all'
    oldest\t'Sort by file modified time'
    size\t'Sort by file size'
    time\t'Sort by file modified time'
    type\t'Sort by file type'
"

complete -c exa -s 'I' -l 'ignore-glob' -d "Ignore files that match these glob patterns" -r
complete -c exa -s 'D' -l 'only-dirs'   -d "List only directories"

# Long view options
complete -c exa -s 'b' -l 'binary'   -d "List file sizes with binary prefixes"
complete -c exa -s 'B' -l 'bytes'    -d "List file sizes in bytes, without any prefixes"
complete -c exa -s 'g' -l 'group'    -d "List each file's group"
complete -c exa -s 'h' -l 'header'   -d "Add a header row to each column"
complete -c exa -s 'H' -l 'links'    -d "List each file's number of hard links"
complete -c exa -s 'i' -l 'inode'    -d "List each file's inode number"
complete -c exa -s 'S' -l 'blocks'   -d "List each file's number of filesystem blocks"
complete -c exa -s 't' -l 'time'     -d "Which timestamp field to list" -x -a "
    modified\t'Display modified time'
    changed\t'Display changed time'
    accessed\t'Display accessed time'
    created\t'Display created time'
"
complete -c exa -s 'm' -l 'modified'      -d "Use the modified timestamp field"
complete -c exa -s 'n' -l 'numeric'       -d "List numeric user and group IDs."
complete -c exa        -l 'changed'       -d "Use the changed timestamp field"
complete -c exa -s 'u' -l 'accessed'      -d "Use the accessed timestamp field"
complete -c exa -s 'U' -l 'created'       -d "Use the created timestamp field"
complete -c exa        -l 'time-style'    -d "How to format timestamps" -x -a "
    default\t'Use the default time style'
    iso\t'Display brief ISO timestamps'
    long-iso\t'Display longer ISO timestaps, up to the minute'
    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"

# Optional extras
complete -c exa -l 'git' -d "List each file's Git status, if tracked"
complete -c exa -s '@' -l 'extended' -d "List each file's extended attributes and sizes"


================================================
FILE: completions/zsh/_exa
================================================
#compdef exa

# Save this file as _exa in /usr/local/share/zsh/site-functions or in any
# other folder in $fpath.  E.g. save it in a folder called ~/.zfunc and add a
# line containing `fpath=(~/.zfunc $fpath)` somewhere before `compinit` in your
# ~/.zshrc.

__exa() {
    # Give completions using the `_arguments` utility function with
    # `-s` for option stacking like `exa -ab` for `exa -a -b` and
    # `-S` for delimiting options with `--` like in `exa -- -a`.
    _arguments -s -S \
        "(- *)"{-v,--version}"[Show version of exa]" \
        "(- *)"{-'\?',--help}"[Show list of command-line options]" \
        {-1,--oneline}"[Display one entry per line]" \
        {-l,--long}"[Display extended file metadata as a table]" \
        {-G,--grid}"[Display entries as a grid]" \
        {-x,--across}"[Sort the grid across, rather than downwards]" \
        {-R,--recurse}"[Recurse into directories]" \
        {-T,--tree}"[Recurse into directories as a tree]" \
        {-F,--classify}"[Display type indicator by file names]" \
        --colo{,u}r="[When to use terminal colours]:(when):(always auto never)" \
        --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]" \
        {-d,--list-dirs}"[List directories like regular files]" \
        {-D,--only-dirs}"[List only directories]" \
        {-L,--level}"+[Limit the depth of recursion]" \
        {-r,--reverse}"[Reverse the sort order]" \
        {-s,--sort}="[Which field to sort by]:(sort field):(accessed age changed created date extension Extension filename Filename inode modified oldest name Name newest none size time type)" \
        {-I,--ignore-glob}"[Ignore files that match these glob patterns]" \
        {-b,--binary}"[List file sizes with binary prefixes]" \
        {-B,--bytes}"[List file sizes in bytes, without any prefixes]" \
        --changed"[Use the changed timestamp field]" \
        {-g,--group}"[List each file's group]" \
        {-h,--header}"[Add a header row to each column]" \
        {-H,--links}"[List each file's number of hard links]" \
        {-i,--inode}"[List each file's inode number]" \
        {-m,--modified}"[Use the modified timestamp field]" \
        {-n,--numeric}"[List numeric user and group IDs.]" \
        {-S,--blocks}"[List each file's number of filesystem blocks]" \
        {-t,--time}="[Which time field to show]:(time field):(accessed changed created modified)" \
        --time-style="[How to format timestamps]:(time style):(default iso long-iso full-iso)" \
        --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]" \
        {-u,--accessed}"[Use the accessed timestamp field]" \
        {-U,--created}"[Use the created timestamp field]" \
        --git"[List each file's Git status, if tracked]" \
        {-@,--extended}"[List each file's extended attributes and sizes]" \
        '*:filename:_files'
}

__exa


================================================
FILE: devtools/README.md
================================================
## exa › development tools

These scripts deal with things like packaging release-worthy versions of exa.

They are **not general-purpose scripts** that you’re able to run from your main computer! They’re intended to be run from the Vagrant machine.


================================================
FILE: devtools/dev-bash.sh
================================================
# This file gets executed when a user starts a `bash` shell, usually because
# they’ve just started a new Vagrant session with `vagrant ssh`. It configures
# some (but not all) of the commands that you can use.


# Display the installed versions of tools.
# help banner
bash /vagrant/devtools/dev-versions.sh


# Configure the Cool Prompt™ (not actually trademarked).
# The Cool Prompt tells you whether you’re in debug or strict mode, whether
# you have colours configured, and whether your last command failed.
nonzero_return() { RETVAL=$?; [ "$RETVAL" -ne 0 ] && echo "$RETVAL "; }
debug_mode()  { [ "$EXA_DEBUG" == "trace" ] && echo -n "trace-"; [ -n "$EXA_DEBUG" ] && echo "debug "; }
strict_mode() { [ -n "$EXA_STRICT" ] && echo "strict "; }
lsc_mode()    { [ -n "$LS_COLORS" ]  && echo "lsc "; }
exac_mode()   { [ -n "$EXA_COLORS" ] && echo "exac "; }
export PS1="\[\e[1;36m\]\h \[\e[32m\]\w \[\e[31m\]\`nonzero_return\`\[\e[35m\]\`debug_mode\`\[\e[32m\]\`lsc_mode\`\[\e[1;32m\]\`exac_mode\`\[\e[33m\]\`strict_mode\`\[\e[36m\]\\$\[\e[0m\] "


# The ‘debug’ function lets you switch debug mode on and off.
# Turn it on if you need to see exa’s debugging logs.
debug() {
  case "$1" in
    ""|"on")  export EXA_DEBUG=1 ;;
    "off")    export EXA_DEBUG= ;;
    "trace")  export EXA_DEBUG=trace ;;
    "status") [ -n "$EXA_DEBUG" ] && echo "debug on" || echo "debug off" ;;
    *)        echo "Usage: debug on|off|trace|status"; return 1 ;;
  esac;
}

# The ‘strict’ function lets you switch strict mode on and off.
# Turn it on if you’d like exa’s command-line arguments checked.
strict() {
  case "$1" in
    "on")  export EXA_STRICT=1 ;;
    "off") export EXA_STRICT= ;;
    "")    [ -n "$EXA_STRICT" ] && echo "strict on" || echo "strict off" ;;
    *)     echo "Usage: strict on|off"; return 1 ;;
  esac;
}

# The ‘colors’ function sets or unsets the ‘LS_COLORS’ and ‘EXA_COLORS’
# environment variables. There’s also a ‘hacker’ theme which turns everything
# green, which is usually used for checking that all colour codes work, and
# for looking cool while you phreak some mainframes or whatever.
colors() {
  case "$1" in
    "ls")
      export LS_COLORS="di=34:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34;43:su=30;41:sg=30;46:tw=30;42:ow=30;43"
      export EXA_COLORS="" ;;
    "hacker")
      export LS_COLORS="di=32:ex=32:fi=32:pi=32:so=32:bd=32:cd=32:ln=32:or=32:mi=32"
      export EXA_COLORS="ur=32:uw=32:ux=32:ue=32:gr=32:gw=32:gx=32:tr=32:tw=32:tx=32:su=32:sf=32:xa=32:sn=32:sb=32:df=32:ds=32:uu=32:un=32:gu=32:gn=32:lc=32:lm=32:ga=32:gm=32:gd=32:gv=32:gt=32:xx=32:da=32:in=32:bl=32:hd=32:lp=32:cc=32:" ;;
    "off")
      export LS_COLORS=
      export EXA_COLORS= ;;
    "")
      [ -n "$LS_COLORS" ]  && echo "LS_COLORS=$LS_COLORS"   || echo "ls-colors off"
      [ -n "$EXA_COLORS" ] && echo "EXA_COLORS=$EXA_COLORS" || echo "exa-colors off" ;;
    *) echo "Usage: ls-colors ls|hacker|off"; return 1 ;;
  esac;
}


================================================
FILE: devtools/dev-create-test-filesystem.sh
================================================
#!/bin/bash
# This script creates a bunch of awkward test case files. It gets
# automatically run as part of Vagrant provisioning.
trap 'exit' ERR

if [[ ! -d "/vagrant" ]]; then
    echo "This script should be run in the Vagrant environment"
    exit 1
fi

source "/vagrant/devtools/dev-fixtures.sh"


# Delete old testcases if they exist already, then create a
# directory to house new ones.
if [[ -d "$TEST_ROOT" ]]; then
    echo -e "\033[1m[ 0/13]\033[0m Deleting existing test cases directory"
    sudo rm -rf "$TEST_ROOT"
fi

sudo mkdir "$TEST_ROOT"
sudo chmod 777 "$TEST_ROOT"
sudo mkdir "$TEST_ROOT/empty"


# Awkward file size testcases.
# This needs sudo to set the files’ users at the very end.
mkdir "$TEST_ROOT/files"
echo -e "\033[1m[ 1/13]\033[0m Creating file size testcases"
for i in {1..13}; do
  fallocate -l "$i" "$TEST_ROOT/files/$i"_bytes
  fallocate -l "$i"KiB "$TEST_ROOT/files/$i"_KiB
  fallocate -l "$i"MiB "$TEST_ROOT/files/$i"_MiB
done

touch -t $FIXED_DATE "$TEST_ROOT/files/"*
touch -t $FIXED_DATE "$TEST_ROOT/files/"
chmod 644 "$TEST_ROOT/files/"*
sudo chown $FIXED_USER:$FIXED_USER "$TEST_ROOT/files/"*


# File name extension testcases.
# These aren’t tested in details view, but we set timestamps on them to
# test that various sort options work.
mkdir "$TEST_ROOT/file-names-exts"
echo -e "\033[1m[ 2/13]\033[0m Creating file name extension testcases"

touch "$TEST_ROOT/file-names-exts/Makefile"

touch "$TEST_ROOT/file-names-exts/IMAGE.PNG"
touch "$TEST_ROOT/file-names-exts/image.svg"

touch "$TEST_ROOT/file-names-exts/VIDEO.AVI"
touch "$TEST_ROOT/file-names-exts/video.wmv"

touch "$TEST_ROOT/file-names-exts/music.mp3"
touch "$TEST_ROOT/file-names-exts/MUSIC.OGG"

touch "$TEST_ROOT/file-names-exts/lossless.flac"
touch "$TEST_ROOT/file-names-exts/lossless.wav"

touch "$TEST_ROOT/file-names-exts/crypto.asc"
touch "$TEST_ROOT/file-names-exts/crypto.signature"

touch "$TEST_ROOT/file-names-exts/document.pdf"
touch "$TEST_ROOT/file-names-exts/DOCUMENT.XLSX"

touch "$TEST_ROOT/file-names-exts/COMPRESSED.ZIP"
touch "$TEST_ROOT/file-names-exts/compressed.tar.gz"
touch "$TEST_ROOT/file-names-exts/compressed.tgz"
touch "$TEST_ROOT/file-names-exts/compressed.tar.xz"
touch "$TEST_ROOT/file-names-exts/compressed.txz"
touch "$TEST_ROOT/file-names-exts/compressed.deb"

touch "$TEST_ROOT/file-names-exts/backup~"
touch "$TEST_ROOT/file-names-exts/#SAVEFILE#"
touch "$TEST_ROOT/file-names-exts/file.tmp"

touch "$TEST_ROOT/file-names-exts/compiled.class"
touch "$TEST_ROOT/file-names-exts/compiled.o"
touch "$TEST_ROOT/file-names-exts/compiled.js"
touch "$TEST_ROOT/file-names-exts/compiled.coffee"


# File name testcases.
# bash really doesn’t want you to create a file with escaped characters
# in its name, so we have to resort to the echo builtin and touch!
mkdir "$TEST_ROOT/file-names"
echo -e "\033[1m[ 3/13]\033[0m Creating file names testcases"

echo -ne "$TEST_ROOT/file-names/ascii: hello" | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/emoji: [🆒]"  | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/utf-8: pâté"  | xargs -0 touch

echo -ne "$TEST_ROOT/file-names/bell: [\a]"         | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/backspace: [\b]"    | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/form-feed: [\f]"    | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/new-line: [\n]"     | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/return: [\r]"       | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/tab: [\t]"          | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/vertical-tab: [\v]" | xargs -0 touch

echo -ne "$TEST_ROOT/file-names/escape: [\033]"               | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/ansi: [\033[34mblue\033[0m]" | xargs -0 touch

echo -ne "$TEST_ROOT/file-names/invalid-utf8-1: [\xFF]"                | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/invalid-utf8-2: [\xc3\x28]"           | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/invalid-utf8-3: [\xe2\x82\x28]"      | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/invalid-utf8-4: [\xf0\x28\x8c\x28]" | xargs -0 touch

echo -ne "$TEST_ROOT/file-names/new-line-dir: [\n]"                | xargs -0 mkdir
echo -ne "$TEST_ROOT/file-names/new-line-dir: [\n]/subfile"        | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/new-line-dir: [\n]/another: [\n]" | xargs -0 touch
echo -ne "$TEST_ROOT/file-names/new-line-dir: [\n]/broken"         | xargs -0 touch

mkdir "$TEST_ROOT/file-names/links"
ln -s "$TEST_ROOT/file-names/new-line-dir"*/* "$TEST_ROOT/file-names/links"

echo -ne "$TEST_ROOT/file-names/new-line-dir: [\n]/broken" | xargs -0 rm


# Special file testcases.
mkdir "$TEST_ROOT/specials"
echo -e "\033[1m[ 4/13]\033[0m Creating special file kind testcases"

sudo mknod "$TEST_ROOT/specials/block-device" b  3 60
sudo mknod "$TEST_ROOT/specials/char-device"  c 14 40
sudo mknod "$TEST_ROOT/specials/named-pipe"   p

sudo touch -t $FIXED_DATE "$TEST_ROOT/specials/"*


# Awkward symlink testcases.
mkdir "$TEST_ROOT/links"
echo -e "\033[1m[ 5/13]\033[0m Creating symlink testcases"

ln -s /            "$TEST_ROOT/links/root"
ln -s /usr         "$TEST_ROOT/links/usr"
ln -s nowhere      "$TEST_ROOT/links/broken"
ln -s /proc/1/root "$TEST_ROOT/links/forbidden"

touch "$TEST_ROOT/links/some_file"
ln -s "$TEST_ROOT/links/some_file" "$TEST_ROOT/links/some_file_absolute"
(cd "$TEST_ROOT/links"; ln -s "some_file" "some_file_relative")
(cd "$TEST_ROOT/links"; ln -s "."         "current_dir")
(cd "$TEST_ROOT/links"; ln -s ".."        "parent_dir")
(cd "$TEST_ROOT/links"; ln -s "itself"    "itself")


# Awkward passwd testcases.
# sudo is needed for these because we technically aren’t a member
# of the groups (because they don’t exist), and chown and chgrp
# are smart enough to disallow it!
mkdir "$TEST_ROOT/passwd"
echo -e "\033[1m[ 6/13]\033[0m Creating user and group testcases"

touch -t $FIXED_DATE                  "$TEST_ROOT/passwd/unknown-uid"
chmod 644                             "$TEST_ROOT/passwd/unknown-uid"
sudo chown $FIXED_BAD_UID:$FIXED_USER "$TEST_ROOT/passwd/unknown-uid"

touch -t $FIXED_DATE                  "$TEST_ROOT/passwd/unknown-gid"
chmod 644                             "$TEST_ROOT/passwd/unknown-gid"
sudo chown $FIXED_USER:$FIXED_BAD_GID "$TEST_ROOT/passwd/unknown-gid"


# Awkward permission testcases.
# Differences in the way ‘chmod’ handles setting ‘setuid’ and ‘setgid’
# when you don’t already own the file mean that we need to use ‘sudo’
# to change permissions to those.
mkdir "$TEST_ROOT/permissions"
echo -e "\033[1m[ 7/13]\033[0m Creating file permission testcases"

mkdir                              "$TEST_ROOT/permissions/forbidden-directory"
chmod 000                          "$TEST_ROOT/permissions/forbidden-directory"
touch -t $FIXED_DATE               "$TEST_ROOT/permissions/forbidden-directory"
sudo chown $FIXED_USER:$FIXED_USER "$TEST_ROOT/permissions/forbidden-directory"

for perms in 000 001 002 004 010 020 040 100 200 400 644 755 777 1000 1001 2000 2010 4000 4100 7666 7777; do
    touch                              "$TEST_ROOT/permissions/$perms"
    sudo chown $FIXED_USER:$FIXED_USER "$TEST_ROOT/permissions/$perms"
    sudo chmod $perms                  "$TEST_ROOT/permissions/$perms"
    sudo touch -t $FIXED_DATE          "$TEST_ROOT/permissions/$perms"
done


# Awkward date and time testcases.
mkdir "$TEST_ROOT/dates"
echo -e "\033[1m[ 8/13]\033[0m Creating date and time testcases"

# created dates
# there’s no way to touch the created date of a file...
# so we have to do this the old-fashioned way!
# (and make sure these don't actually get listed)
touch -t $FIXED_OLD_DATE    "$TEST_ROOT/dates/peach";  sleep 1
touch -t $FIXED_MED_DATE    "$TEST_ROOT/dates/plum";   sleep 1
touch -t $FIXED_NEW_DATE    "$TEST_ROOT/dates/pear"

# modified dates
touch -t $FIXED_OLD_DATE -m "$TEST_ROOT/dates/pear"
touch -t $FIXED_MED_DATE -m "$TEST_ROOT/dates/peach"
touch -t $FIXED_NEW_DATE -m "$TEST_ROOT/dates/plum"

# accessed dates
touch -t $FIXED_OLD_DATE -a "$TEST_ROOT/dates/plum"
touch -t $FIXED_MED_DATE -a "$TEST_ROOT/dates/pear"
touch -t $FIXED_NEW_DATE -a "$TEST_ROOT/dates/peach"

sudo chown $FIXED_USER:$FIXED_USER -R "$TEST_ROOT/dates"

mkdir "$TEST_ROOT/far-dates"
touch -t $FIXED_PAST_DATE    "$TEST_ROOT/far-dates/the-distant-past"
touch -t $FIXED_FUTURE_DATE  "$TEST_ROOT/far-dates/beyond-the-future"


# Awkward extended attribute testcases.
# We need to test combinations of various numbers of files *and*
# extended attributes in directories. Turns out, the easiest way to
# do this is to generate all combinations of files with “one-xattr”
# or “two-xattrs” in their name and directories with “empty” or
# “one-file” in their name, then just give the right number of
# xattrs and children to those.
mkdir "$TEST_ROOT/attributes"
echo -e "\033[1m[ 9/13]\033[0m Creating extended attribute testcases"

mkdir "$TEST_ROOT/attributes/files"
touch "$TEST_ROOT/attributes/files/"{no-xattrs,one-xattr,two-xattrs}{,_forbidden}

mkdir "$TEST_ROOT/attributes/dirs"
mkdir "$TEST_ROOT/attributes/dirs/"{no-xattrs,one-xattr,two-xattrs}_{empty,one-file,two-files}{,_forbidden}

setfattr -n user.greeting         -v hello "$TEST_ROOT/attributes"/**/*{one-xattr,two-xattrs}*
setfattr -n user.another_greeting -v hi    "$TEST_ROOT/attributes"/**/*two-xattrs*

for dir in "$TEST_ROOT/attributes/dirs/"*one-file*; do
    touch $dir/file-in-question
done

for dir in "$TEST_ROOT/attributes/dirs/"*two-files*; do
    touch $dir/this-file
    touch $dir/that-file
done

find "$TEST_ROOT/attributes" -exec touch {} -t $FIXED_DATE \;

# I want to use the following to test,
# but it only works on macos:
#chmod +a "$FIXED_USER deny readextattr" "$TEST_ROOT/attributes"/**/*_forbidden

sudo chmod 000                        "$TEST_ROOT/attributes"/**/*_forbidden
sudo chown $FIXED_USER:$FIXED_USER -R "$TEST_ROOT/attributes"


# A sample Git repository
# This uses cd because it's easier than telling Git where to go each time
echo -e "\033[1m[10/13]\033[0m Creating Git testcases (1/4)"
mkdir "$TEST_ROOT/git"
cd    "$TEST_ROOT/git"
git init >/dev/null

mkdir edits additions moves

echo "original content" | tee edits/{staged,unstaged,both} >/dev/null
echo "this file gets moved" > moves/hither

git add edits moves
git config --global user.email "exa@exa.exa"
git config --global user.name "Exa Exa"
git commit -m "Automated test commit" >/dev/null

echo "modifications!" | tee edits/{staged,both} >/dev/null
touch additions/{staged,edited}
mv moves/{hither,thither}

git add edits moves additions
echo "more modifications!" | tee edits/unstaged edits/both additions/edited >/dev/null
touch additions/unstaged

find "$TEST_ROOT/git" -exec touch {} -t $FIXED_DATE \;
sudo chown $FIXED_USER:$FIXED_USER -R "$TEST_ROOT/git"


# A second Git repository
# for testing two at once
echo -e "\033[1m[11/13]\033[0m Creating Git testcases (2/4)"
mkdir -p "$TEST_ROOT/git2/deeply/nested/directory"
cd       "$TEST_ROOT/git2"
git init >/dev/null

touch "deeply/nested/directory/upd8d"
git add "deeply/nested/directory/upd8d"
git commit -m "Automated test commit" >/dev/null

echo "Now with contents" > "deeply/nested/directory/upd8d"
touch "deeply/nested/directory/l8st"

echo -e "target\n*.mp3" > ".gitignore"
mkdir "ignoreds"
touch "ignoreds/music.mp3"
touch "ignoreds/music.m4a"
mkdir "ignoreds/nested"
touch "ignoreds/nested/70s grove.mp3"
touch "ignoreds/nested/funky chicken.m4a"
mkdir "ignoreds/nested2"
touch "ignoreds/nested2/ievan polkka.mp3"

mkdir "target"
touch "target/another ignored file"

mkdir "deeply/nested/repository"
cd    "deeply/nested/repository"
git init >/dev/null
touch subfile
# This file, ‘subfile’, should _not_ be marked as a new file by exa, because
# it’s in the sub-repository but hasn’t been added to it. Were the sub-repo not
# present, it would be marked as a new file, as the top-level repo knows about
# the ‘deeply’ directory.

find "$TEST_ROOT/git2" -exec touch {} -t $FIXED_DATE \;
sudo chown $FIXED_USER:$FIXED_USER -R "$TEST_ROOT/git2"


# A third Git repository
# Regression test for https://github.com/ogham/exa/issues/526
echo -e "\033[1m[12/13]\033[0m Creating Git testcases (3/4)"
mkdir -p "$TEST_ROOT/git3"
cd       "$TEST_ROOT/git3"
git init >/dev/null

# Create a symbolic link pointing to a non-existing file
ln -s aaa/aaa/a b

# This normally fails with:
find "$TEST_ROOT/git3" -exec touch {} -h -t $FIXED_DATE \;
sudo chown $FIXED_USER:$FIXED_USER -R "$TEST_ROOT/git3"


# A fourth Git repository
# Regression test for https://github.com/ogham/exa/issues/698
echo -e "\033[1m[12/13]\033[0m Creating Git testcases (4/4)"
mkdir -p "$TEST_ROOT/git4"
cd       "$TEST_ROOT/git4"
git init >/dev/null

# Create a non UTF-8 file
touch 'P'$'\b\211''UUU'

find "$TEST_ROOT/git4" -exec touch {} -h -t $FIXED_DATE \;
sudo chown $FIXED_USER:$FIXED_USER -R "$TEST_ROOT/git4"


# Hidden and dot file testcases.
# We need to set the permissions of `.` and `..` because they actually
# get displayed in the output here, so this has to come last.
echo -e "\033[1m[13/13]\033[0m Creating hidden and dot file testcases"
shopt -u dotglob
GLOBIGNORE=".:.."

mkdir "$TEST_ROOT/hiddens"
cd    "$TEST_ROOT/hiddens"
touch "$TEST_ROOT/hiddens/visible"
touch "$TEST_ROOT/hiddens/.hidden"
touch "$TEST_ROOT/hiddens/..extra-hidden"

# ./hiddens/
touch -t $FIXED_DATE               "$TEST_ROOT/hiddens/"*
chmod 644                          "$TEST_ROOT/hiddens/"*
sudo chown $FIXED_USER:$FIXED_USER "$TEST_ROOT/hiddens/"*

# .
touch -t $FIXED_DATE               "$TEST_ROOT/hiddens"
chmod 755                          "$TEST_ROOT/hiddens"
sudo chown $FIXED_USER:$FIXED_USER "$TEST_ROOT/hiddens"

# ..
sudo touch -t $FIXED_DATE          "$TEST_ROOT"
sudo chmod 755                     "$TEST_ROOT"
sudo chown $FIXED_USER:$FIXED_USER "$TEST_ROOT"


================================================
FILE: devtools/dev-fixtures.sh
================================================
#!/bin/bash
# This file contains the text fixtures — the known, constant data — that are
# used when setting up the environment that exa’s tests get run in.


# The directory that all the test files are created under.
export TEST_ROOT=/testcases


# Because the timestamps are formatted differently depending on whether
# they’re in the current year or not (see `details.rs`), we have to make
# sure that the files are created in the current year, so they get shown
# in the format we expect.
export CURRENT_YEAR=$(date "+%Y")
export FIXED_DATE="${CURRENT_YEAR}01011234.56"  # 1st January, 12:34:56


# We also need an UID and a GID that are guaranteed to not exist, to
# test what happen when they don’t.
export FIXED_BAD_UID=666
export FIXED_BAD_GID=616


# We create two users that own the test files.
#
# The first one just owns the ordinary ones, because we don’t want the
# test outputs to depend on “vagrant” or “ubuntu” existing.
#
# The second one has a long name, to test that the file owner column
# widens correctly. The benefit of Vagrant is that we don’t need to
# set this up on the *actual* system!
export FIXED_USER="cassowary"
export FIXED_LONG_USER="antidisestablishmentarienism"


# A couple of dates, for date-time testing.
export FIXED_OLD_DATE='200303030000.00'
export FIXED_MED_DATE='200606152314.29'   # the june gets used for fr_FR locale tests
export FIXED_NEW_DATE='200912221038.53'   # and the december for ja_JP local tests

# Dates that extend beyond 32-bit timespace.
export FIXED_PAST_DATE='170001010000.00'
export FIXED_FUTURE_DATE='230001010000.00'


================================================
FILE: devtools/dev-help.sh
================================================
# This file prints out some help text that says which commands are available
# in the VM. It gets executed during Vagrant provisioning and its output gets
# dumped into /etc/motd, to print it when a user starts a new Vagrant session.


echo -e "
\033[1;33mThe exa development environment!\033[0m
exa's source is available at \033[33m/vagrant\033[0m.
Binaries get built into \033[33m/home/vagrant/target\033[0m.

\033[4mCommands\033[0m
\033[32;1mexa\033[0m to run the built version of exa
\033[32;1mbuild-exa\033[0m (or \033[32;1mb\033[0m) to run \033[1mcargo build\033[0m
\033[32;1mtest-exa\033[0m (or \033[32;1mt\033[0m) to run \033[1mcargo test\033[0m
\033[32;1mrun-xtests\033[0m (or \033[32;1mx\033[0m) to run the extended tests
\033[32;1mcompile-exa\033[0m (or \033[32;1mc\033[0m) to run the above three
\033[32;1mdebug\033[0m to toggle printing logs
\033[32;1mstrict\033[0m to toggle strict mode
\033[32;1mcolors\033[0m to toggle custom colours
\033[32;1mhalp\033[0m to show all this again
"


================================================
FILE: devtools/dev-package-for-linux.sh
================================================
set -e

# This script builds a publishable release-worthy version of exa.
# It gets the version number, builds exa using cargo, tests it, strips the
# binary, compresses it into a zip, then puts it in /vagrant so it’s
# accessible from the host machine.
#
# If you’re in the VM, you can run it using the ‘package-exa’ command.


# Linux check!
uname=$(uname -s)
if [[ "$uname" != "Linux" ]]; then
  echo "Gotta be on Linux to run this (detected '$uname')!"
  exit 1
fi

# First, we need to get the version number to figure out what to call the zip.
# We do this by getting the first line from the Cargo.toml that matches
# /version/, removing its whitespace, and building a command out of it, so the
# shell executes something like `exa_version="0.8.0"`, which it understands as
# a variable definition. Hey, it’s not a hack if it works.
toml_file="/vagrant/Cargo.toml"
eval exa_$(grep version $toml_file | head -n 1 | sed "s/ //g")
if [ -z "$exa_version" ]; then
  echo "Failed to parse version number! Can't build exa!"
  exit 1
fi

# Weekly builds have a bit more information in their version number (see build.rs).
if [[ "$1" == "--weekly" ]]; then
  git_hash=$(GIT_DIR=/vagrant/.git git rev-parse --short --verify HEAD)
  date=$(date +"%Y-%m-%d")
  echo "Building exa weekly v$exa_version, date $date, Git hash $git_hash"
else
  echo "Building exa v$exa_version"
fi

# Compilation is done in --release mode, which takes longer but produces a
# faster binary. This binary gets built to a different place, so the extended
# tests script needs to be told which one to use.
echo -e "\n\033[4mCompiling release version of exa...\033[0m"
exa_linux_binary="/vagrant/exa-linux-x86_64"
rm -vf "$exa_linux_binary"
cargo build --release --manifest-path "$toml_file"
cargo test --release --manifest-path "$toml_file" --lib -- --quiet
/vagrant/xtests/run.sh --release
cp /home/vagrant/target/release/exa "$exa_linux_binary"

# Stripping the binary before distributing it removes a bunch of debugging
# symbols, saving some space.
echo -e "\n\033[4mStripping binary...\033[0m"
strip -v "$exa_linux_binary"

# Compress the binary for upload. The ‘-j’ flag is necessary to avoid the
# /vagrant path being in the zip too. Only the zip gets the version number, so
# the binaries can have consistent names, and it’s still possible to tell
# different *downloads* apart.
echo -e "\n\033[4mZipping binary...\033[0m"
if [[ "$1" == "--weekly" ]]; then
  exa_linux_zip="/vagrant/exa-linux-x86_64-${exa_version}-${date}-${git_hash}.zip"
else
  exa_linux_zip="/vagrant/exa-linux-x86_64.zip"
fi
rm -vf "$exa_linux_zip"
zip -j "$exa_linux_zip" "$exa_linux_binary"

# There was a problem a while back where a library was getting unknowingly
# *dynamically* linked, which broke the whole ‘self-contained binary’ concept.
# So dump the linker table, in case anything unscrupulous shows up.
echo -e "\n\033[4mLibraries linked:\033[0m"
ldd "$exa_linux_binary" | sed "s/\t//"

# Might as well use it to test itself, right?
echo -e "\n\033[4mAll done! Files produced:\033[0m"
"$exa_linux_binary" "$exa_linux_binary" "$exa_linux_zip" -lB


================================================
FILE: devtools/dev-run-debug.sh
================================================
#!/bin/bash
if [[ -f ~/target/debug/exa ]]; then
  ~/target/debug/exa "$@"
else
  echo -e "Debug exa binary does not exist!"
  echo -e "Run \033[32;1mb\033[0m or \033[32;1mbuild-exa\033[0m to create it"
fi


================================================
FILE: devtools/dev-run-release.sh
================================================
#!/bin/bash
if [[ -f ~/target/release/exa ]]; then
  ~/target/release/exa "$@"
else
  echo -e "Release exa binary does not exist!"
  echo -e "Run \033[32;1mb --release\033[0m or \033[32;1mbuild-exa --release\033[0m to create it"
fi


================================================
FILE: devtools/dev-set-up-environment.sh
================================================
#!/bin/bash

if [[ ! -d "/vagrant" ]]; then
    echo "This script should be run in the Vagrant environment"
    exit 1
fi

if [[ $EUID -ne 0 ]]; then
    echo "This script should be run as root"
    exit 1
fi

source "/vagrant/devtools/dev-fixtures.sh"


# create our test users

if id -u $FIXED_USER &>/dev/null; then
    echo "Normal user already exists"
else
    echo "Creating normal user"
    useradd $FIXED_USER
fi

if id -u $FIXED_LONG_USER &>/dev/null; then
    echo "Long user already exists"
else
    echo "Creating long user"
    useradd $FIXED_LONG_USER
fi


# locale generation

# remove most of this file, it slows down locale-gen
if grep -F -q "en_GB.UTF-8 UTF-8" /var/lib/locales/supported.d/en; then
    echo "Removing existing locales"
    echo "en_US.UTF-8 UTF-8" > /var/lib/locales/supported.d/en
fi

# uncomment these from the config file
if grep -F -q "# fr_FR.UTF-8" /etc/locale.gen; then
    sed -i '/fr_FR.UTF-8/s/^# //g' /etc/locale.gen
fi
if grep -F -q "# ja_JP.UTF-8" /etc/locale.gen; then
    sed -i '/ja_JP.UTF-8/s/^# //g' /etc/locale.gen
fi

# only regenerate locales if the config files are newer than the locale archive
if [[ ( /var/lib/locales/supported.d/en -nt /usr/lib/locale/locale-archive ) || \
      ( /etc/locale_gen                 -nt /usr/lib/locale/locale-archive ) ]]; then
    locale-gen
else
    echo "Locales already generated"
fi


================================================
FILE: devtools/dev-versions.sh
================================================
# Displays the installed versions of Rust and Cargo.
# This gets run from ‘dev-bash.sh’, which gets run from ‘~/.bash_profile’, so
# the versions gets displayed after the help text for a new Vagrant session.

echo -e "\\033[4mVersions\\033[0m"
rustc --version
cargo --version
echo


================================================
FILE: devtools/local-package-for-macos.sh
================================================
set -e

# This script builds a publishable release-worthy version of exa.
# It gets the version number, builds exa using cargo, tests it, strips the
# binary, and compresses it into a zip.
#
# It’s *mostly* the same as dev-package-for-linux.sh, except with some
# Mach-specific things (otool instead of ldd), BSD-coreutils-specific things,
# and it doesn’t run the xtests.


# Virtualising macOS is a legal minefield, so this script is ‘local’ instead
# of ‘dev’: I run it from my actual machine, rather than from a VM.
uname=$(uname -s)
if [[ "$uname" != "Darwin" ]]; then
  echo "Gotta be on Darwin to run this (detected '$uname')!"
  exit 1
fi

# First, we need to get the version number to figure out what to call the zip.
# We do this by getting the first line from the Cargo.toml that matches
# /version/, removing its whitespace, and building a command out of it, so the
# shell executes something like `exa_version="0.8.0"`, which it understands as
# a variable definition. Hey, it’s not a hack if it works.
#
# Because this can’t use the absolute /vagrant path, this has to use what this
# SO answer calls a “quoting disaster”: https://stackoverflow.com/a/20196098/3484614
# You will also need GNU coreutils: https://stackoverflow.com/a/4031502/3484614
exa_root="$(dirname "$(dirname "$(greadlink -fm "$0")")")"
toml_file="$exa_root"/Cargo.toml
eval exa_$(grep version $toml_file | head -n 1 | sed "s/ //g")
if [ -z "$exa_version" ]; then
  echo "Failed to parse version number! Can't build exa!"
  exit 1
fi

# Weekly builds have a bit more information in their version number (see build.rs).
if [[ "$1" == "--weekly" ]]; then
  git_hash=$(GIT_DIR=$exa_root/.git git rev-parse --short --verify HEAD)
  date=$(date +"%Y-%m-%d")
  echo "Building exa weekly v$exa_version, date $date, Git hash $git_hash"
else
  echo "Building exa v$exa_version"
fi

# Compilation is done in --release mode, which takes longer but produces a
# faster binary.
echo -e "\n\033[4mCompiling release version of exa...\033[0m"
exa_macos_binary="$exa_root/exa-macos-x86_64"
rm -vf "$exa_macos_binary" | sed 's/^/removing /'
cargo build --release --manifest-path "$toml_file"
cargo test --release --manifest-path "$toml_file" --lib -- --quiet
# we can’t run the xtests outside the VM!
#/vagrant/xtests/run.sh --release
cp "$exa_root"/target/release/exa "$exa_macos_binary"

# Stripping the binary before distributing it removes a bunch of debugging
# symbols, saving some space.
echo -e "\n\033[4mStripping binary...\033[0m"
strip "$exa_macos_binary"
echo "strip $exa_macos_binary"

# Compress the binary for upload. The ‘-j’ flag is necessary to avoid the
# current path being in the zip too. Only the zip gets the version number, so
# the binaries can have consistent names, and it’s still possible to tell
# different *downloads* apart.
echo -e "\n\033[4mZipping binary...\033[0m"
if [[ "$1" == "--weekly" ]]; then
  exa_macos_zip="$exa_root/exa-macos-x86_64-${exa_version}-${date}-${git_hash}.zip"
else
  exa_macos_zip="$exa_root/exa-macos-x86_64-${exa_version}.zip"
fi
rm -vf "$exa_macos_zip" | sed 's/^/removing /'
zip -j "$exa_macos_zip" "$exa_macos_binary"

# There was a problem a while back where a library was getting unknowingly
# *dynamically* linked, which broke the whole ‘self-contained binary’ concept.
# So dump the linker table, in case anything unscrupulous shows up.
echo -e "\n\033[4mLibraries linked:\033[0m"
otool -L "$exa_macos_binary" | sed 's/^[[:space:]]*//'

# Might as well use it to test itself, right?
echo -e "\n\033[4mAll done! Files produced:\033[0m"
"$exa_macos_binary" "$exa_macos_binary" "$exa_macos_zip" -lB


================================================
FILE: man/exa.1.md
================================================
% exa(1) v0.9.0

<!-- This is the exa(1) man page, written in Markdown. -->
<!-- To generate the roff version, run `just man`, -->
<!-- and the man page will appear in the ‘target’ directory. -->


NAME
====

exa — a modern replacement for ls


SYNOPSIS
========

`exa [options] [files...]`

**exa** is a modern replacement for `ls`.
It uses colours for information by default, helping you distinguish between many types of files, such as whether you are the owner, or in the owning group.

It also has extra features not present in the original `ls`, such as viewing the Git status for a directory, or recursing into directories with a tree view.


EXAMPLES
========

`exa`
: Lists the contents of the current directory in a grid.

`exa --oneline --reverse --sort=size`
: Displays a list of files with the largest at the top.

`exa --long --header --inode --git`
: Displays a table of files with a header, showing each file’s metadata, inode, and Git status.

`exa --long --tree --level=3`
: Displays a tree of files, three levels deep, as well as each file’s metadata.


DISPLAY OPTIONS
===============

`-1`, `--oneline`
: Display one entry per line.

`-F`, `--classify`
: Display file kind indicators next to file names.

`-G`, `--grid`
: Display entries as a grid (default).

`-l`, `--long`
: Display extended file metadata as a table.

`-R`, `--recurse`
: Recurse into directories.

`-T`, `--tree`
: Recurse into directories as a tree.

`-x`, `--across`
: Sort the grid across, rather than downwards.

`--color`, `--colour=WHEN`
: When to use terminal colours.
Valid settings are ‘`always`’, ‘`automatic`’, and ‘`never`’.

`--color-scale`, `--colour-scale`
: Colour file sizes on a scale.

`--icons`
: Display icons next to file names.

`--no-icons`
: Don't display icons. (Always overrides --icons)


FILTERING AND SORTING OPTIONS
=============================

`-a`, `--all`
: Show hidden and “dot” files.
Use this twice to also show the ‘`.`’ and ‘`..`’ directories.

`-d`, `--list-dirs`
: List directories as regular files, rather than recursing and listing their contents.

`-L`, `--level=DEPTH`
: Limit the depth of recursion.

`-r`, `--reverse`
: Reverse the sort order.

`-s`, `--sort=SORT_FIELD`
: Which field to sort by.

Valid sort fields are ‘`name`’, ‘`Name`’, ‘`extension`’, ‘`Extension`’, ‘`size`’, ‘`modified`’, ‘`changed`’, ‘`accessed`’, ‘`created`’, ‘`inode`’, ‘`type`’, and ‘`none`’.

The `modified` sort field has the aliases ‘`date`’, ‘`time`’, and ‘`newest`’, and its reverse order has the aliases ‘`age`’ and ‘`oldest`’.

Sort fields starting with a capital letter will sort uppercase before lowercase: ‘A’ then ‘B’ then ‘a’ then ‘b’. Fields starting with a lowercase letter will mix them: ‘A’ then ‘a’ then ‘B’ then ‘b’.

`-I`, `--ignore-glob=GLOBS`
: Glob patterns, pipe-separated, of files to ignore.

`--git-ignore` [if exa was built with git support]
: Do not list files that are ignored by Git.

`--group-directories-first`
: List directories before other files.

`-D`, `--only-dirs`
: List only directories, not files.


LONG VIEW OPTIONS
=================

These options are available when running with `--long` (`-l`):

`-b`, `--binary`
: List file sizes with binary prefixes.

`-B`, `--bytes`
: List file sizes in bytes, without any prefixes.

`--changed`
: Use the changed timestamp field.

`-g`, `--group`
: List each file’s group.

`-h`, `--header`
: Add a header row to each column.

`-H`, `--links`
: List each file’s number of hard links.

`-i`, `--inode`
: List each file’s inode number.

`-m`, `--modified`
: Use the modified timestamp field.

`-n`, `--numeric`
: List numeric user and group IDs.

`-S`, `--blocks`
: List each file’s number of file system blocks.

`-t`, `--time=WORD`
: Which timestamp field to list.

: Valid timestamp fields are ‘`modified`’, ‘`changed`’, ‘`accessed`’, and ‘`created`’.

`--time-style=STYLE`
: How to format timestamps.

: Valid timestamp styles are ‘`default`’, ‘`iso`’, ‘`long-iso`’, and ‘`full-iso`’.

`-u`, `--accessed`
: Use the accessed timestamp field.

`-U`, `--created`
: Use the created timestamp field.

`--no-permissions`
: Suppress the permissions field.

`--no-filesize`
: Suppress the file size field.

`--no-user`
: Suppress the user field.

`--no-time`
: Suppress the time field.

`-@`, `--extended`
: List each file’s extended attributes and sizes.

`--git`  [if exa was built with git support]
: List each file’s Git status, if tracked.

This adds a two-character column indicating the staged and unstaged statuses respectively. The status character can be ‘`-`’ for not modified, ‘`M`’ for a modified file, ‘`N`’ for a new file, ‘`D`’ for deleted, ‘`R`’ for renamed, ‘`T`’ for type-change, ‘`I`’ for ignored, and ‘`U`’ for conflicted.

Directories will be shown to have the status of their contents, which is how ‘deleted’ is possible: if a directory contains a file that has a certain status, it will be shown to have that status.


ENVIRONMENT VARIABLES
=====================

exa responds to the following environment variables:

## `COLUMNS`

Overrides the width of the terminal, in characters.

For example, ‘`COLUMNS=80 exa`’ will show a grid view with a maximum width of 80 characters.

This option won’t do anything when exa’s output doesn’t wrap, such as when using the `--long` view.

## `EXA_STRICT`

Enables _strict mode_, which will make exa error when two command-line options are incompatible.

Usually, options can override each other going right-to-left on the command line, so that exa can be given aliases: creating an alias ‘`exa=exa --sort=ext`’ then running ‘`exa --sort=size`’ with that alias will run ‘`exa --sort=ext --sort=size`’, and the sorting specified by the user will override the sorting specified by the alias.

In strict mode, the two options will not co-operate, and exa will error.

This option is intended for use with automated scripts and other situations where you want to be certain you’re typing in the right command.

## `EXA_GRID_ROWS`

Limits the grid-details view (‘`exa --grid --long`’) so it’s only activated when at least the given number of rows of output would be generated.

With widescreen displays, it’s possible for the grid to look very wide and sparse, on just one or two lines with none of the columns lining up.
By specifying a minimum number of rows, you can only use the view if it’s going to be worth using.

## `EXA_ICON_SPACING`

Specifies the number of spaces to print between an icon (see the ‘`--icons`’ option) and its file name.

Different terminals display icons differently, as they usually take up more than one character width on screen, so there’s no “standard” number of spaces that exa can use to separate an icon from text. One space may place the icon too close to the text, and two spaces may place it too far away. So the choice is left up to the user to configure depending on their terminal emulator.

## `NO_COLOR`

Disables colours in the output (regardless of its value). Can be overridden by `--color` option.

See `https://no-color.org/` for details.

## `LS_COLORS`, `EXA_COLORS`

Specifies the colour scheme used to highlight files based on their name and kind, as well as highlighting metadata and parts of the UI.

For more information on the format of these environment variables, see the `exa_colors(5)` manual page.


EXIT STATUSES
=============

0
: If everything goes OK.

1
: If there was an I/O error during operation.

3
: If there was a problem with the command-line arguments.


AUTHOR
======

exa is maintained by Benjamin ‘ogham’ Sago and many other contributors.

**Website:** `https://the.exa.website/` \
**Source code:** `https://github.com/ogham/exa` \
**Contributors:** `https://github.com/ogham/exa/graphs/contributors`


SEE ALSO
========

- `exa_colors(5)`


================================================
FILE: man/exa_colors.5.md
================================================
% exa_colors(5) v0.9.0

<!-- This is the exa_colors(5) man page, written in Markdown. -->
<!-- To generate the roff version, run `just man`, -->
<!-- and the man page will appear in the ‘target’ directory. -->


NAME
====

exa_colors — customising the file and UI colours of exa


SYNOPSIS
========

The `EXA_COLORS` environment variable can be used to customise the colours that `exa` uses to highlight file names, file metadata, and parts of the UI.

You can use the `dircolors` program to generate a script that sets the variable from an input file, or if you don’t mind editing long strings of text, you can just type it out directly. These variables have the following structure:

- A list of key-value pairs separated by ‘`=`’, such as ‘`*.txt=32`’.
- Multiple ANSI formatting codes are separated by ‘`;`’, such as ‘`*.txt=32;1;4`’.
- Finally, multiple pairs are separated by ‘`:`’, such as ‘`*.txt=32:*.mp3=1;35`’.

The key half of the pair can either be a two-letter code or a file glob, and anything that’s not a valid code will be treated as a glob, including keys that happen to be two letters long.


EXAMPLES
========

`EXA_COLORS="uu=0:gu=0"`
: Disable the “current user” highlighting

`EXA_COLORS="da=32"`
: Turn the date column green

`EXA_COLORS="Vagrantfile=1;4;33"`
: Highlight Vagrantfiles

`EXA_COLORS="*.zip=38;5;125"`
: Override the existing zip colour

`EXA_COLORS="*.md=38;5;121:*.log=38;5;248"`
: Markdown files a shade of green, log files a shade of grey


LIST OF CODES
=============

`LS_COLORS` can use these ten codes:

`di`
: directories

`ex`
: executable files

`fi`
: regular files

`pi`
: named pipes

`so`
: sockets

`bd`
: block devices

`cd`
: character devices

`ln`
: symlinks

`or`
: symlinks with no target


`EXA_COLORS` can use many more:

`ur`
: the user-read permission bit

`uw`
: the user-write permission bit

`ux`
: the user-execute permission bit for regular files

`ue`
: the user-execute for other file kinds

`gr`
: the group-read permission bit

`gw`
: the group-write permission bit

`gx`
: the group-execute permission bit

`tr`
: the others-read permission bit

`tw`
: the others-write permission bit

`tx`
: the others-execute permission bit

`su`
: setuid, setgid, and sticky permission bits for files

`sf`
: setuid, setgid, and sticky for other file kinds

`xa`
: the extended attribute indicator

`sn`
: the numbers of a file’s size (sets `nb`, `nk`, `nm`, `ng` and `nh`)

`nb`
: the numbers of a file’s size if it is lower than 1 KB/Kib

`nk`
: the numbers of a file’s size if it is between 1 KB/KiB and 1 MB/MiB

`nm`
: the numbers of a file’s size if it is between 1 MB/MiB and 1 GB/GiB

`ng`
: the numbers of a file’s size if it is between 1 GB/GiB and 1 TB/TiB

`nt`
: the numbers of a file’s size if it is 1 TB/TiB or higher

`sb`
: the units of a file’s size (sets `ub`, `uk`, `um`, `ug` and `uh`)

`ub`
: the units of a file’s size if it is lower than 1 KB/Kib

`uk`
: the units of a file’s size if it is between 1 KB/KiB and 1 MB/MiB

`um`
: the units of a file’s size if it is between 1 MB/MiB and 1 GB/GiB

`ug`
: the units of a file’s size if it is between 1 GB/GiB and 1 TB/TiB

`ut`
: the units of a file’s size if it is 1 TB/TiB or higher

`df`
: a device’s major ID

`ds`
: a device’s minor ID

`uu`
: a user that’s you

`un`
: a user that’s someone else

`gu`
: a group that you belong to

`gn`
: a group you aren’t a member of

`lc`
: a number of hard links

`lm`
: a number of hard links for a regular file with at least two

`ga`
: a new flag in Git

`gm`
: a modified flag in Git

`gd`
: a deleted flag in Git

`gv`
: a renamed flag in Git

`gt`
: a modified metadata flag in Git

`xx`
: “punctuation”, including many background UI elements

`da`
: a file’s date

`in`
: a file’s inode number

`bl`
: a file’s number of blocks

`hd`
: the header row of a table

`lp`
: the path of a symlink

`cc`
: an escaped character in a filename

`bO`
: the overlay style for broken symlink paths

Values in `EXA_COLORS` override those given in `LS_COLORS`, so you don’t need to re-write an existing `LS_COLORS` variable with proprietary extensions.


LIST OF STYLES
==============

Unlike some versions of `ls`, the given ANSI values must be valid colour codes: exa won’t just print out whichever characters are given.

The codes accepted by exa are:

`1`
: for bold

`4`
: for underline

`31`
: for red text

`32`
: for green text

`33`
: for yellow text

`34`
: for blue text

`35`
: for purple text

`36`
: for cyan text

`37`
: for white text

`38;5;nnn`
: for a colour from 0 to 255 (replace the `nnn` part)

Many terminals will treat bolded text as a different colour, or at least provide the option to.

exa provides its own built-in set of file extension mappings that cover a large range of common file extensions, including documents, archives, media, and temporary files.
Any mappings in the environment variables will override this default set: running exa with `LS_COLORS="*.zip=32"` will turn zip files green but leave the colours of other compressed files alone.

You can also disable this built-in set entirely by including a `reset` entry at the beginning of `EXA_COLORS`.
So setting `EXA_COLORS="reset:*.txt=31"` will highlight only text files; setting `EXA_COLORS="reset"` will highlight nothing.


AUTHOR
======

exa is maintained by Benjamin ‘ogham’ Sago and many other contributors.

**Website:** `https://the.exa.website/` \
**Source code:** `https://github.com/ogham/exa` \
**Contributors:** `https://github.com/ogham/exa/graphs/contributors`


SEE ALSO
========

- `exa(1)`


================================================
FILE: rust-toolchain.toml
================================================
[toolchain]
channel = "1.66.1"


================================================
FILE: snap/.gitignore
================================================
.snapcraft


================================================
FILE: snap/snapcraft.yaml
================================================
name: exa
version: 'latest'
summary: Replacement for 'ls' written in Rust
description: |
  It uses colours for information by default, helping you distinguish between
  many types of files, such as whether you are the owner, or in the owning
  group. It also has extra features not present in the original ls, such as
  viewing the Git status for a directory, or recursing into directories with a
  tree view. exa is written in Rust, so it’s small, fast, and portable.

grade: stable
confinement: classic

apps:
  exa:
    command: exa

parts:
  exa:
    plugin: rust
    source: .
    stage-packages:
      - libgit2-24
      - cmake
      - libz-dev


================================================
FILE: src/fs/dir.rs
================================================
use crate::fs::feature::git::GitCache;
use crate::fs::fields::GitStatus;
use std::io;
use std::fs;
use std::path::{Path, PathBuf};
use std::slice::Iter as SliceIter;

use log::*;

use crate::fs::File;


/// A **Dir** provides a cached list of the file paths in a directory that’s
/// being listed.
///
/// This object gets passed to the Files themselves, in order for them to
/// check the existence of surrounding files, then highlight themselves
/// accordingly. (See `File#get_source_files`)
pub struct Dir {

    /// A vector of the files that have been read from this directory.
    contents: Vec<PathBuf>,

    /// The path that was read.
    pub path: PathBuf,
}

impl Dir {

    /// Create a new Dir object filled with all the files in the directory
    /// pointed to by the given path. Fails if the directory can’t be read, or
    /// isn’t actually a directory, or if there’s an IO error that occurs at
    /// any point.
    ///
    /// The `read_dir` iterator doesn’t actually yield the `.` and `..`
    /// entries, so if the user wants to see them, we’ll have to add them
    /// ourselves after the files have been read.
    pub fn read_dir(path: PathBuf) -> io::Result<Self> {
        info!("Reading directory {:?}", &path);

        let contents = fs::read_dir(&path)?
                          .map(|result| result.map(|entry| entry.path()))
                          .collect::<Result<_, _>>()?;

        Ok(Self { contents, path })
    }

    /// Produce an iterator of IO results of trying to read all the files in
    /// this directory.
    pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, git: Option<&'ig GitCache>, git_ignoring: bool) -> Files<'dir, 'ig> {
        Files {
            inner:     self.contents.iter(),
            dir:       self,
            dotfiles:  dots.shows_dotfiles(),
            dots:      dots.dots(),
            git,
            git_ignoring,
        }
    }

    /// Whether this directory contains a file with the given path.
    pub fn contains(&self, path: &Path) -> bool {
        self.contents.iter().any(|p| p.as_path() == path)
    }

    /// Append a path onto the path specified by this directory.
    pub fn join(&self, child: &Path) -> PathBuf {
        self.path.join(child)
    }
}


/// Iterator over reading the contents of a directory as `File` objects.
pub struct Files<'dir, 'ig> {

    /// The internal iterator over the paths that have been read already.
    inner: SliceIter<'dir, PathBuf>,

    /// The directory that begat those paths.
    dir: &'dir Dir,

    /// Whether to include dotfiles in the list.
    dotfiles: bool,

    /// Whether the `.` or `..` directories should be produced first, before
    /// any files have been listed.
    dots: DotsNext,

    git: Option<&'ig GitCache>,

    git_ignoring: bool,
}

impl<'dir, 'ig> Files<'dir, 'ig> {
    fn parent(&self) -> PathBuf {
        // We can’t use `Path#parent` here because all it does is remove the
        // last path component, which is no good for us if the path is
        // relative. For example, while the parent of `/testcases/files` is
        // `/testcases`, the parent of `.` is an empty path. Adding `..` on
        // the end is the only way to get to the *actual* parent directory.
        self.dir.path.join("..")
    }

    /// Go through the directory until we encounter a file we can list (which
    /// varies depending on the dotfile visibility flag)
    fn next_visible_file(&mut self) -> Option<Result<File<'dir>, (PathBuf, io::Error)>> {
        loop {
            if let Some(path) = self.inner.next() {
                let filename = File::filename(path);
                if ! self.dotfiles && filename.starts_with('.') {
                    continue;
                }

                // Also hide _prefix files on Windows because it's used by old applications
                // as an alternative to dot-prefix files.
                #[cfg(windows)]
                if ! self.dotfiles && filename.starts_with('_') {
                    continue;
                }

                if self.git_ignoring {
                    let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
                    if git_status.unstaged == GitStatus::Ignored {
                         continue;
                    }
                }

                return Some(File::from_args(path.clone(), self.dir, filename)
                                 .map_err(|e| (path.clone(), e)))
            }

            return None
        }
    }
}

/// The dot directories that need to be listed before actual files, if any.
/// If these aren’t being printed, then `FilesNext` is used to skip them.
enum DotsNext {

    /// List the `.` directory next.
    Dot,

    /// List the `..` directory next.
    DotDot,

    /// Forget about the dot directories and just list files.
    Files,
}

impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
    type Item = Result<File<'dir>, (PathBuf, io::Error)>;

    fn next(&mut self) -> Option<Self::Item> {
        match self.dots {
            DotsNext::Dot => {
                self.dots = DotsNext::DotDot;
                Some(File::new_aa_current(self.dir)
                          .map_err(|e| (Path::new(".").to_path_buf(), e)))
            }

            DotsNext::DotDot => {
                self.dots = DotsNext::Files;
                Some(File::new_aa_parent(self.parent(), self.dir)
                          .map_err(|e| (self.parent(), e)))
            }

            DotsNext::Files => {
                self.next_visible_file()
            }
        }
    }
}


/// Usually files in Unix use a leading dot to be hidden or visible, but two
/// entries in particular are “extra-hidden”: `.` and `..`, which only become
/// visible after an extra `-a` option.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum DotFilter {

    /// Shows files, dotfiles, and `.` and `..`.
    DotfilesAndDots,

    /// Show files and dotfiles, but hide `.` and `..`.
    Dotfiles,

    /// Just show files, hiding anything beginning with a dot.
    JustFiles,
}

impl Default for DotFilter {
    fn default() -> Self {
        Self::JustFiles
    }
}

impl DotFilter {

    /// Whether this filter should show dotfiles in a listing.
    fn shows_dotfiles(self) -> bool {
        match self {
            Self::JustFiles       => false,
            Self::Dotfiles        => true,
            Self::DotfilesAndDots => true,
        }
    }

    /// Whether this filter should add dot directories to a listing.
    fn dots(self) -> DotsNext {
        match self {
            Self::JustFiles        => DotsNext::Files,
            Self::Dotfiles         => DotsNext::Files,
            Self::DotfilesAndDots  => DotsNext::Dot,
        }
    }
}


================================================
FILE: src/fs/dir_action.rs
================================================
//! What to do when encountering a directory?

/// The action to take when trying to list a file that turns out to be a
/// directory.
///
/// By default, exa will display the information about files passed in as
/// command-line arguments, with one file per entry. However, if a directory
/// is passed in, exa assumes that the user wants to see its contents, rather
/// than the directory itself.
///
/// This can get annoying sometimes: if a user does `exa ~/Downloads/img-*`
/// to see the details of every file starting with `img-`, any directories
/// that happen to start with the same will be listed after the files at
/// the end in a separate block. By listing directories as files, their
/// directory status will be ignored, and both will be listed side-by-side.
///
/// These two modes have recursive analogues in the “recurse” and “tree”
/// modes. Here, instead of just listing the directories, exa will descend
/// into them and print out their contents. The recurse mode does this by
/// having extra output blocks at the end, while the tree mode will show
/// directories inline, with their contents immediately underneath.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum DirAction {

    /// This directory should be listed along with the regular files, instead
    /// of having its contents queried.
    AsFile,

    /// This directory should not be listed, and should instead be opened and
    /// *its* files listed separately. This is the default behaviour.
    List,

    /// This directory should be listed along with the regular files, and then
    /// its contents should be listed afterward. The recursive contents of
    /// *those* contents are dictated by the options argument.
    Recurse(RecurseOptions),
}

impl DirAction {

    /// Gets the recurse options, if this dir action has any.
    pub fn recurse_options(self) -> Option<RecurseOptions> {
        match self {
            Self::Recurse(o)  => Some(o),
            _                 => None,
        }
    }

    /// Whether to treat directories as regular files or not.
    pub fn treat_dirs_as_files(self) -> bool {
        match self {
            Self::AsFile      => true,
            Self::Recurse(o)  => o.tree,
            Self::List        => false,
        }
    }
}


/// The options that determine how to recurse into a directory.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub struct RecurseOptions {

    /// Whether recursion should be done as a tree or as multiple individual
    /// views of files.
    pub tree: bool,

    /// The maximum number of times that recursion should descend to, if one
    /// is specified.
    pub max_depth: Option<usize>,
}

impl RecurseOptions {

    /// Returns whether a directory of the given depth would be too deep.
    pub fn is_too_deep(self, depth: usize) -> bool {
        match self.max_depth {
            None     => false,
            Some(d)  => d <= depth
        }
    }
}


================================================
FILE: src/fs/feature/git.rs
================================================
//! Getting the Git status of files and directories.

use std::ffi::OsStr;
#[cfg(target_family = "unix")]
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::sync::Mutex;

use log::*;

use crate::fs::fields as f;


/// A **Git cache** is assembled based on the user’s input arguments.
///
/// This uses vectors to avoid the overhead of hashing: it’s not worth it when the
/// expected number of Git repositories per exa invocation is 0 or 1...
pub struct GitCache {

    /// A list of discovered Git repositories and their paths.
    repos: Vec<GitRepo>,

    /// Paths that we’ve confirmed do not have Git repositories underneath them.
    misses: Vec<PathBuf>,
}

impl GitCache {
    pub fn has_anything_for(&self, index: &Path) -> bool {
        self.repos.iter().any(|e| e.has_path(index))
    }

    pub fn get(&self, index: &Path, prefix_lookup: bool) -> f::Git {
        self.repos.iter()
            .find(|e| e.has_path(index))
            .map(|repo| repo.search(index, prefix_lookup))
            .unwrap_or_default()
    }
}

use std::iter::FromIterator;
impl FromIterator<PathBuf> for GitCache {
    fn from_iter<I>(iter: I) -> Self
    where I: IntoIterator<Item=PathBuf>
    {
        let iter = iter.into_iter();
        let mut git = Self {
            repos: Vec::with_capacity(iter.size_hint().0),
            misses: Vec::new(),
        };

        for path in iter {
            if git.misses.contains(&path) {
                debug!("Skipping {:?} because it already came back Gitless", path);
            }
            else if git.repos.iter().any(|e| e.has_path(&path)) {
                debug!("Skipping {:?} because we already queried it", path);
            }
            else {
                match GitRepo::discover(path) {
                    Ok(r) => {
                        if let Some(r2) = git.repos.iter_mut().find(|e| e.has_workdir(&r.workdir)) {
                            debug!("Adding to existing repo (workdir matches with {:?})", r2.workdir);
                            r2.extra_paths.push(r.original_path);
                            continue;
                        }

                        debug!("Discovered new Git repo");
                        git.repos.push(r);
                    }
                    Err(miss) => {
                        git.misses.push(miss)
                    }
                }
            }
        }

        git
    }
}


/// A **Git repository** is one we’ve discovered somewhere on the filesystem.
pub struct GitRepo {

    /// The queryable contents of the repository: either a `git2` repo, or the
    /// cached results from when we queried it last time.
    contents: Mutex<GitContents>,

    /// The working directory of this repository.
    /// This is used to check whether two repositories are the same.
    workdir: PathBuf,

    /// The path that was originally checked to discover this repository.
    /// This is as important as the extra_paths (it gets checked first), but
    /// is separate to avoid having to deal with a non-empty Vec.
    original_path: PathBuf,

    /// Any other paths that were checked only to result in this same
    /// repository.
    extra_paths: Vec<PathBuf>,
}

/// A repository’s queried state.
enum GitContents {

    /// All the interesting Git stuff goes through this.
    Before {
        repo: git2::Repository,
    },

    /// Temporary value used in `repo_to_statuses` so we can move the
    /// repository out of the `Before` variant.
    Processing,

    /// The data we’ve extracted from the repository, but only after we’ve
    /// actually done so.
    After {
        statuses: Git,
    },
}

impl GitRepo {

    /// Searches through this repository for a path (to a file or directory,
    /// depending on the prefix-lookup flag) and returns its Git status.
    ///
    /// Actually querying the `git2` repository for the mapping of paths to
    /// Git statuses is only done once, and gets cached so we don’t need to
    /// re-query the entire repository the times after that.
    ///
    /// The temporary `Processing` enum variant is used after the `git2`
    /// repository is moved out, but before the results have been moved in!
    /// See <https://stackoverflow.com/q/45985827/3484614>
    fn search(&self, index: &Path, prefix_lookup: bool) -> f::Git {
        use std::mem::replace;

        let mut contents = self.contents.lock().unwrap();
        if let GitContents::After { ref statuses } = *contents {
            debug!("Git repo {:?} has been found in cache", &self.workdir);
            return statuses.status(index, prefix_lookup);
        }

        debug!("Querying Git repo {:?} for the first time", &self.workdir);
        let repo = replace(&mut *contents, GitContents::Processing).inner_repo();
        let statuses = repo_to_statuses(&repo, &self.workdir);
        let result = statuses.status(index, prefix_lookup);
        let _processing = replace(&mut *contents, GitContents::After { statuses });
        result
    }

    /// Whether this repository has the given working directory.
    fn has_workdir(&self, path: &Path) -> bool {
        self.workdir == path
    }

    /// Whether this repository cares about the given path at all.
    fn has_path(&self, path: &Path) -> bool {
        path.starts_with(&self.original_path) || self.extra_paths.iter().any(|e| path.starts_with(e))
    }

    /// Searches for a Git repository at any point above the given path.
    /// Returns the original buffer if none is found.
    fn discover(path: PathBuf) -> Result<Self, PathBuf> {
        info!("Searching for Git repository above {:?}", path);
        let repo = match git2::Repository::discover(&path) {
            Ok(r) => r,
            Err(e) => {
                error!("Error discovering Git repositories: {:?}", e);
                return Err(path);
            }
        };

        if let Some(workdir) = repo.workdir() {
            let workdir = workdir.to_path_buf();
            let contents = Mutex::new(GitContents::Before { repo });
            Ok(Self { contents, workdir, original_path: path, extra_paths: Vec::new() })
        }
        else {
            warn!("Repository has no workdir?");
            Err(path)
        }
    }
}


impl GitContents {
    /// Assumes that the repository hasn’t been queried, and extracts it
    /// (consuming the value) if it has. This is needed because the entire
    /// enum variant gets replaced when a repo is queried (see above).
    fn inner_repo(self) -> git2::Repository {
        if let Self::Before { repo } = self {
            repo
        }
        else {
            unreachable!("Tried to extract a non-Repository")
        }
    }
}

/// Iterates through a repository’s statuses, consuming it and returning the
/// mapping of files to their Git status.
/// We will have already used the working directory at this point, so it gets
/// passed in rather than deriving it from the `Repository` again.
fn repo_to_statuses(repo: &git2::Repository, workdir: &Path) -> Git {
    let mut statuses = Vec::new();

    info!("Getting Git statuses for repo with workdir {:?}", workdir);
    match repo.statuses(None) {
        Ok(es) => {
            for e in es.iter() {
                #[cfg(target_family = "unix")]
                let path = workdir.join(Path::new(OsStr::from_bytes(e.path_bytes())));
                // TODO: handle non Unix systems better:
                // https://github.com/ogham/exa/issues/698
                #[cfg(not(target_family = "unix"))]
                let path = workdir.join(Path::new(e.path().unwrap()));
                let elem = (path, e.status());
                statuses.push(elem);
            }
        }
        Err(e) => {
            error!("Error looking up Git statuses: {:?}", e);
        }
    }

    Git { statuses }
}

// The `repo.statuses` call above takes a long time. exa debug output:
//
//   20.311276  INFO:exa::fs::feature::git: Getting Git statuses for repo with workdir "/vagrant/"
//   20.799610  DEBUG:exa::output::table: Getting Git status for file "./Cargo.toml"
//
// Even inserting another logging line immediately afterwards doesn’t make it
// look any faster.


/// Container of Git statuses for all the files in this folder’s Git repository.
struct Git {
    statuses: Vec<(PathBuf, git2::Status)>,
}

impl Git {

    /// Get either the file or directory status for the given path.
    /// “Prefix lookup” means that it should report an aggregate status of all
    /// paths starting with the given prefix (in other words, a directory).
    fn status(&self, index: &Path, prefix_lookup: bool) -> f::Git {
        if prefix_lookup { self.dir_status(index) }
                    else { self.file_status(index) }
    }

    /// Get the user-facing status of a file.
    /// We check the statuses directly applying to a file, and for the ignored
    /// status we check if any of its parents directories is ignored by git.
    fn file_status(&self, file: &Path) -> f::Git {
        let path = reorient(file);

        let s = self.statuses.iter()
            .filter(|p| if p.1 == git2::Status::IGNORED {
                path.starts_with(&p.0)
            } else {
                p.0 == path
            })
            .fold(git2::Status::empty(), |a, b| a | b.1);

        let staged = index_status(s);
        let unstaged = working_tree_status(s);
        f::Git { staged, unstaged }
    }

    /// Get the combined, user-facing status of a directory.
    /// Statuses are aggregating (for example, a directory is considered
    /// modified if any file under it has the status modified), except for
    /// ignored status which applies to files under (for example, a directory
    /// is considered ignored if one of its parent directories is ignored).
    fn dir_status(&self, dir: &Path) -> f::Git {
        let path = reorient(dir);

        let s = self.statuses.iter()
            .filter(|p| if p.1 == git2::Status::IGNORED {
                path.starts_with(&p.0)
            } else {
                p.0.starts_with(&path)
            })
            .fold(git2::Status::empty(), |a, b| a | b.1);

        let staged = index_status(s);
        let unstaged = working_tree_status(s);
        f::Git { staged, unstaged }
    }
}


/// Converts a path to an absolute path based on the current directory.
/// Paths need to be absolute for them to be compared properly, otherwise
/// you’d ask a repo about “./README.md” but it only knows about
/// “/vagrant/README.md”, prefixed by the workdir.
#[cfg(unix)]
fn reorient(path: &Path) -> PathBuf {
    use std::env::current_dir;

    // TODO: I’m not 100% on this func tbh
    let path = match current_dir() {
        Err(_)   => Path::new(".").join(&path),
        Ok(dir)  => dir.join(&path),
    };

    path.canonicalize().unwrap_or(path)
}

#[cfg(windows)]
fn reorient(path: &Path) -> PathBuf {
    let unc_path = path.canonicalize().unwrap();
    // On Windows UNC path is returned. We need to strip the prefix for it to work.
    let normal_path = unc_path.as_os_str().to_str().unwrap().trim_left_matches("\\\\?\\");
    return PathBuf::from(normal_path);
}

/// The character to display if the file has been modified, but not staged.
fn working_tree_status(status: git2::Status) -> f::GitStatus {
    match status {
        s if s.contains(git2::Status::WT_NEW)         => f::GitStatus::New,
        s if s.contains(git2::Status::WT_MODIFIED)    => f::GitStatus::Modified,
        s if s.contains(git2::Status::WT_DELETED)     => f::GitStatus::Deleted,
        s if s.contains(git2::Status::WT_RENAMED)     => f::GitStatus::Renamed,
        s if s.contains(git2::Status::WT_TYPECHANGE)  => f::GitStatus::TypeChange,
        s if s.contains(git2::Status::IGNORED)        => f::GitStatus::Ignored,
        s if s.contains(git2::Status::CONFLICTED)     => f::GitStatus::Conflicted,
        _                                             => f::GitStatus::NotModified,
    }
}

/// The character to display if the file has been modified and the change
/// has been staged.
fn index_status(status: git2::Status) -> f::GitStatus {
    match status {
        s if s.contains(git2::Status::INDEX_NEW)         => f::GitStatus::New,
        s if s.contains(git2::Status::INDEX_MODIFIED)    => f::GitStatus::Modified,
        s if s.contains(git2::Status::INDEX_DELETED)     => f::GitStatus::Deleted,
        s if s.contains(git2::Status::INDEX_RENAMED)     => f::GitStatus::Renamed,
        s if s.contains(git2::Status::INDEX_TYPECHANGE)  => f::GitStatus::TypeChange,
        _                                                => f::GitStatus::NotModified,
    }
}


================================================
FILE: src/fs/feature/mod.rs
================================================
pub mod xattr;

#[cfg(feature = "git")]
pub mod git;

#[cfg(not(feature = "git"))]
pub mod git {
    use std::iter::FromIterator;
    use std::path::{Path, PathBuf};

    use crate::fs::fields as f;


    pub struct GitCache;

    impl FromIterator<PathBuf> for GitCache {
        fn from_iter<I>(_iter: I) -> Self
        where I: IntoIterator<Item=PathBuf>
        {
            Self
        }
    }

    impl GitCache {
        pub fn has_anything_for(&self, _index: &Path) -> bool {
            false
        }

        pub fn get(&self, _index: &Path, _prefix_lookup: bool) -> f::Git {
            unreachable!();
        }
    }
}


================================================
FILE: src/fs/feature/xattr.rs
================================================
//! Extended attribute support for Darwin and Linux systems.

#![allow(trivial_casts)]  // for ARM

use std::cmp::Ordering;
use std::io;
use std::path::Path;


pub const ENABLED: bool = cfg!(any(target_os = "macos", target_os = "linux"));


pub trait FileAttributes {
    fn attributes(&self) -> io::Result<Vec<Attribute>>;
    fn symlink_attributes(&self) -> io::Result<Vec<Attribute>>;
}

#[cfg(any(target_os = "macos", target_os = "linux"))]
impl FileAttributes for Path {
    fn attributes(&self) -> io::Result<Vec<Attribute>> {
        list_attrs(&lister::Lister::new(FollowSymlinks::Yes), self)
    }

    fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {
        list_attrs(&lister::Lister::new(FollowSymlinks::No), self)
    }
}

#[cfg(not(any(target_os = "macos", target_os = "linux")))]
impl FileAttributes for Path {
    fn attributes(&self) -> io::Result<Vec<Attribute>> {
        Ok(Vec::new())
    }

    fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {
        Ok(Vec::new())
    }
}


/// Attributes which can be passed to `Attribute::list_with_flags`
#[cfg(any(target_os = "macos", target_os = "linux"))]
#[derive(Copy, Clone)]
pub enum FollowSymlinks {
    Yes,
    No,
}

/// Extended attribute
#[derive(Debug, Clone)]
pub struct Attribute {
    pub name: String,
    pub size: usize,
}


#[cfg(any(target_os = "macos", target_os = "linux"))]
pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {
    use std::ffi::CString;

    let c_path = match path.to_str().and_then(|s| CString::new(s).ok()) {
        Some(cstring) => cstring,
        None => {
            return Err(io::Error::new(io::ErrorKind::Other, "Error: path somehow contained a NUL?"));
        }
    };

    let bufsize = lister.listxattr_first(&c_path);
    match bufsize.cmp(&0) {
        Ordering::Less     => return Err(io::Error::last_os_error()),
        Ordering::Equal    => return Ok(Vec::new()),
        Ordering::Greater  => {},
    }

    let mut buf = vec![0_u8; bufsize as usize];
    let err = lister.listxattr_second(&c_path, &mut buf, bufsize);

    match err.cmp(&0) {
        Ordering::Less     => return Err(io::Error::last_os_error()),
        Ordering::Equal    => return Ok(Vec::new()),
        Ordering::Greater  => {},
    }

    let mut names = Vec::new();
    if err > 0 {
        // End indices of the attribute names
        // the buffer contains 0-terminated c-strings
        let idx = buf.iter().enumerate().filter_map(|(i, v)|
            if *v == 0 { Some(i) } else { None }
        );
        let mut start = 0;

        for end in idx {
            let c_end = end + 1; // end of the c-string (including 0)
            let size = lister.getxattr(&c_path, &buf[start..c_end]);

            if size > 0 {
                names.push(Attribute {
                    name: lister.translate_attribute_name(&buf[start..end]),
                    size: size as usize,
                });
            }

            start = c_end;
        }
    }

    Ok(names)
}


#[cfg(target_os = "macos")]
mod lister {
    use super::FollowSymlinks;
    use libc::{c_int, size_t, ssize_t, c_char, c_void};
    use std::ffi::CString;
    use std::ptr;

    extern "C" {
        fn listxattr(
            path: *const c_char,
            namebuf: *mut c_char,
            size: size_t,
            options: c_int,
        ) -> ssize_t;

        fn getxattr(
            path: *const c_char,
            name: *const c_char,
            value: *mut c_void,
            size: size_t,
            position: u32,
            options: c_int,
        ) -> ssize_t;
    }

    pub struct Lister {
        c_flags: c_int,
    }

    impl Lister {
        pub fn new(do_follow: FollowSymlinks) -> Self {
            let c_flags: c_int = match do_follow {
                FollowSymlinks::Yes  => 0x0001,
                FollowSymlinks::No   => 0x0000,
            };

            Self { c_flags }
        }

        pub fn translate_attribute_name(&self, input: &[u8]) -> String {
            unsafe { std::str::from_utf8_unchecked(input).into() }
        }

        pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
            unsafe {
                listxattr(
                    c_path.as_ptr(),
                    ptr::null_mut(),
                    0,
                    self.c_flags,
                )
            }
        }

        pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t {
            unsafe {
                listxattr(
                    c_path.as_ptr(),
                    buf.as_mut_ptr().cast::<c_char>(),
                    bufsize as size_t,
                    self.c_flags,
                )
            }
        }

        pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
            unsafe {
                getxattr(
                    c_path.as_ptr(),
                    buf.as_ptr().cast::<c_char>(),
                    ptr::null_mut(),
                    0,
                    0,
                    self.c_flags,
                )
            }
        }
    }
}


#[cfg(target_os = "linux")]
mod lister {
    use std::ffi::CString;
    use libc::{size_t, ssize_t, c_char, c_void};
    use super::FollowSymlinks;
    use std::ptr;

    extern "C" {
        fn listxattr(
            path: *const c_char,
            list: *mut c_char,
            size: size_t,
        ) -> ssize_t;

        fn llistxattr(
            path: *const c_char,
            list: *mut c_char,
            size: size_t,
        ) -> ssize_t;

        fn getxattr(
            path: *const c_char,
            name: *const c_char,
            value: *mut c_void,
            size: size_t,
        ) -> ssize_t;

        fn lgetxattr(
            path: *const c_char,
            name: *const c_char,
            value: *mut c_void,
            size: size_t,
        ) -> ssize_t;
    }

    pub struct Lister {
        follow_symlinks: FollowSymlinks,
    }

    impl Lister {
        pub fn new(follow_symlinks: FollowSymlinks) -> Lister {
            Lister { follow_symlinks }
        }

        pub fn translate_attribute_name(&self, input: &[u8]) -> String {
            String::from_utf8_lossy(input).into_owned()
        }

        pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
            let listxattr = match self.follow_symlinks {
                FollowSymlinks::Yes  => listxattr,
                FollowSymlinks::No   => llistxattr,
            };

            unsafe {
                listxattr(
                    c_path.as_ptr().cast(),
                    ptr::null_mut(),
                    0,
                )
            }
        }

        pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t {
            let listxattr = match self.follow_symlinks {
                FollowSymlinks::Yes  => listxattr,
                FollowSymlinks::No   => llistxattr,
            };

            unsafe {
                listxattr(
                    c_path.as_ptr().cast(),
                    buf.as_mut_ptr().cast(),
                    bufsize as size_t,
                )
            }
        }

        pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
            let getxattr = match self.follow_symlinks {
                FollowSymlinks::Yes  => getxattr,
                FollowSymlinks::No   => lgetxattr,
            };

            unsafe {
                getxattr(
                    c_path.as_ptr().cast(),
                    buf.as_ptr().cast(),
                    ptr::null_mut(),
                    0,
                )
            }
        }
    }
}


================================================
FILE: src/fs/fields.rs
================================================
//! Wrapper types for the values returned from `File`s.
//!
//! The methods of `File` that return information about the entry on the
//! filesystem -- size, modification date, block count, or Git status -- used
//! to just return these as formatted strings, but this became inflexible once
//! customisable output styles landed.
//!
//! Instead, they will return a wrapper type from this module, which tags the
//! type with what field it is while containing the actual raw value.
//!
//! The `output::details` module, among others, uses these types to render and
//! display the information as formatted strings.

// C-style `blkcnt_t` types don’t follow Rust’s rules!
#![allow(non_camel_case_types)]
#![allow(clippy::struct_excessive_bools)]


/// The type of a file’s block count.
pub type blkcnt_t = u64;

/// The type of a file’s group ID.
pub type gid_t = u32;

/// The type of a file’s inode.
pub type ino_t = u64;

/// The type of a file’s number of links.
pub type nlink_t = u64;

/// The type of a file’s timestamp (creation, modification, access, etc).
pub type time_t = i64;

/// The type of a file’s user ID.
pub type uid_t = u32;


/// The file’s base type, which gets displayed in the very first column of the
/// details output.
///
/// This type is set entirely by the filesystem, rather than relying on a
/// file’s contents. So “link” is a type, but “image” is just a type of
/// regular file. (See the `filetype` module for those checks.)
///
/// Its ordering is used when sorting by type.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
pub enum Type {
    Directory,
    File,
    Link,
    Pipe,
    Socket,
    CharDevice,
    BlockDevice,
    Special,
}

impl Type {
    pub fn is_regular_file(self) -> bool {
        matches!(self, Self::File)
    }
}


/// The file’s Unix permission bitfield, with one entry per bit.
#[derive(Copy, Clone)]
pub struct Permissions {
    pub user_read:      bool,
    pub user_write:     bool,
    pub user_execute:   bool,

    pub group_read:     bool,
    pub group_write:    bool,
    pub group_execute:  bool,

    pub other_read:     bool,
    pub other_write:    bool,
    pub other_execute:  bool,

    pub sticky:         bool,
    pub setgid:         bool,
    pub setuid:         bool,
}

/// The file's FileAttributes field, available only on Windows.
#[derive(Copy, Clone)]
pub struct Attributes {
    pub archive:         bool,
    pub directory:       bool,
    pub readonly:        bool,
    pub hidden:          bool,
    pub system:          bool,
    pub reparse_point:   bool,
}

/// The three pieces of information that are displayed as a single column in
/// the details view. These values are fused together to make the output a
/// little more compressed.
#[derive(Copy, Clone)]
pub struct PermissionsPlus {
    pub file_type:   Type,
    #[cfg(unix)]
    pub permissions: Permissions,
    #[cfg(windows)]
    pub attributes:  Attributes,
    pub xattrs:      bool,
}


/// The permissions encoded as octal values
#[derive(Copy, Clone)]
pub struct OctalPermissions {
    pub permissions: Permissions,
}

/// A file’s number of hard links on the filesystem.
///
/// Under Unix, a file can exist on the filesystem only once but appear in
/// multiple directories. However, it’s rare (but occasionally useful!) for a
/// regular file to have a link count greater than 1, so we highlight the
/// block count specifically for this case.
#[derive(Copy, Clone)]
pub struct Links {

    /// The actual link count.
    pub count: nlink_t,

    /// Whether this file is a regular file with more than one hard link.
    pub multiple: bool,
}


/// A file’s inode. Every directory entry on a Unix filesystem has an inode,
/// including directories and links, so this is applicable to everything exa
/// can deal with.
#[derive(Copy, Clone)]
pub struct Inode(pub ino_t);


/// The number of blocks that a file takes up on the filesystem, if any.
#[derive(Copy, Clone)]
pub enum Blocks {

    /// This file has the given number of blocks.
    Some(blkcnt_t),

    /// This file isn’t of a type that can take up blocks.
    None,
}


/// The ID of the user that owns a file. This will only ever be a number;
/// looking up the username is done in the `display` module.
#[derive(Copy, Clone)]
pub struct User(pub uid_t);

/// The ID of the group that a file belongs to.
#[derive(Copy, Clone)]
pub struct Group(pub gid_t);


/// A file’s size, in bytes. This is usually formatted by the `number_prefix`
/// crate into something human-readable.
#[derive(Copy, Clone)]
pub enum Size {

    /// This file has a defined size.
    Some(u64),

    /// This file has no size, or has a size but we aren’t interested in it.
    ///
    /// Under Unix, directory entries that aren’t regular files will still
    /// have a file size. For example, a directory will just contain a list of
    /// its files as its “contents” and will be specially flagged as being a
    /// directory, rather than a file. However, seeing the “file size” of this
    /// data is rarely useful — I can’t think of a time when I’ve seen it and
    /// learnt something. So we discard it and just output “-” instead.
    ///
    /// See this answer for more: <https://unix.stackexchange.com/a/68266>
    None,

    /// This file is a block or character device, so instead of a size, print
    /// out the file’s major and minor device IDs.
    ///
    /// This is what ls does as well. Without it, the devices will just have
    /// file sizes of zero.
    DeviceIDs(DeviceIDs),
}

/// The major and minor device IDs that gets displayed for device files.
///
/// You can see what these device numbers mean:
/// - <http://www.lanana.org/docs/device-list/>
/// - <http://www.lanana.org/docs/device-list/devices-2.6+.txt>
#[derive(Copy, Clone)]
pub struct DeviceIDs {
    pub major: u8,
    pub minor: u8,
}


/// One of a file’s timestamps (created, accessed, or modified).
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Time {
    pub seconds: time_t,
    pub nanoseconds: time_t,
}


/// A file’s status in a Git repository. Whether a file is in a repository or
/// not is handled by the Git module, rather than having a “null” variant in
/// this enum.
#[derive(PartialEq, Eq, Copy, Clone)]
pub enum GitStatus {

    /// This file hasn’t changed since the last commit.
    NotModified,

    /// This file didn’t exist for the last commit, and is not specified in
    /// the ignored files list.
    New,

    /// A file that’s been modified since the last commit.
    Modified,

    /// A deleted file. This can’t ever be shown, but it’s here anyway!
    Deleted,

    /// A file that Git has tracked a rename for.
    Renamed,

    /// A file that’s had its type (such as the file permissions) changed.
    TypeChange,

    /// A file that’s ignored (that matches a line in .gitignore)
    Ignored,

    /// A file that’s updated but unmerged.
    Conflicted,
}


/// A file’s complete Git status. It’s possible to make changes to a file, add
/// it to the staging area, then make *more* changes, so we need to list each
/// file’s status for both of these.
#[derive(Copy, Clone)]
pub struct Git {
    pub staged:   GitStatus,
    pub unstaged: GitStatus,
}

impl Default for Git {

    /// Create a Git status for a file with nothing done to it.
    fn default() -> Self {
        Self {
            staged: GitStatus::NotModified,
            unstaged: GitStatus::NotModified,
        }
    }
}


================================================
FILE: src/fs/file.rs
================================================
//! Files, and methods and fields to access their metadata.

use std::io;
#[cfg(unix)]
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
#[cfg(windows)]
use std::os::windows::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use log::*;

use crate::fs::dir::Dir;
use crate::fs::fields as f;


/// A **File** is a wrapper around one of Rust’s `PathBuf` values, along with
/// associated data about the file.
///
/// Each file is definitely going to have its filename displayed at least
/// once, have its file extension extracted at least once, and have its metadata
/// information queried at least once, so it makes sense to do all this at the
/// start and hold on to all the information.
pub struct File<'dir> {

    /// The filename portion of this file’s path, including the extension.
    ///
    /// This is used to compare against certain filenames (such as checking if
    /// it’s “Makefile” or something) and to highlight only the filename in
    /// colour when displaying the path.
    pub name: String,

    /// The file’s name’s extension, if present, extracted from the name.
    ///
    /// This is queried many times over, so it’s worth caching it.
    pub ext: Option<String>,

    /// The path that begat this file.
    ///
    /// Even though the file’s name is extracted, the path needs to be kept
    /// around, as certain operations involve looking up the file’s absolute
    /// location (such as searching for compiled files) or using its original
    /// path (following a symlink).
    pub path: PathBuf,

    /// A cached `metadata` (`stat`) call for this file.
    ///
    /// This too is queried multiple times, and is *not* cached by the OS, as
    /// it could easily change between invocations — but exa is so short-lived
    /// it’s better to just cache it.
    pub metadata: std::fs::Metadata,

    /// A reference to the directory that contains this file, if any.
    ///
    /// Filenames that get passed in on the command-line directly will have no
    /// parent directory reference — although they technically have one on the
    /// filesystem, we’ll never need to look at it, so it’ll be `None`.
    /// However, *directories* that get passed in will produce files that
    /// contain a reference to it, which is used in certain operations (such
    /// as looking up compiled files).
    pub parent_dir: Option<&'dir Dir>,

    /// Whether this is one of the two `--all all` directories, `.` and `..`.
    ///
    /// Unlike all other entries, these are not returned as part of the
    /// directory’s children, and are in fact added specifically by exa; this
    /// means that they should be skipped when recursing.
    pub is_all_all: bool,
}

impl<'dir> File<'dir> {
    pub fn from_args<PD, FN>(path: PathBuf, parent_dir: PD, filename: FN) -> io::Result<File<'dir>>
    where PD: Into<Option<&'dir Dir>>,
          FN: Into<Option<String>>
    {
        let parent_dir = parent_dir.into();
        let name       = filename.into().unwrap_or_else(|| File::filename(&path));
        let ext        = File::ext(&path);

        debug!("Statting file {:?}", &path);
        let metadata   = std::fs::symlink_metadata(&path)?;
        let is_all_all = false;

        Ok(File { name, ext, path, metadata, parent_dir, is_all_all })
    }

    pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
        let path       = parent_dir.path.clone();
        let ext        = File::ext(&path);

        debug!("Statting file {:?}", &path);
        let metadata   = std::fs::symlink_metadata(&path)?;
        let is_all_all = true;
        let parent_dir = Some(parent_dir);

        Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all })
    }

    pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
        let ext        = File::ext(&path);

        debug!("Statting file {:?}", &path);
        let metadata   = std::fs::symlink_metadata(&path)?;
        let is_all_all = true;
        let parent_dir = Some(parent_dir);

        Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all })
    }

    /// A file’s name is derived from its string. This needs to handle directories
    /// such as `/` or `..`, which have no `file_name` component. So instead, just
    /// use the last component as the name.
    pub fn filename(path: &Path) -> String {
        if let Some(back) = path.components().next_back() {
            back.as_os_str().to_string_lossy().to_string()
        }
        else {
            // use the path as fallback
            error!("Path {:?} has no last component", path);
            path.display().to_string()
        }
    }

    /// Extract an extension from a file path, if one is present, in lowercase.
    ///
    /// The extension is the series of characters after the last dot. This
    /// deliberately counts dotfiles, so the “.git” folder has the extension “git”.
    ///
    /// ASCII lowercasing is used because these extensions are only compared
    /// against a pre-compiled list of extensions which are known to only exist
    /// within ASCII, so it’s alright.
    fn ext(path: &Path) -> Option<String> {
        let name = path.file_name().map(|f| f.to_string_lossy().to_string())?;

        name.rfind('.')
            .map(|p| name[p + 1 ..]
            .to_ascii_lowercase())
    }

    /// Whether this file is a directory on the filesystem.
    pub fn is_directory(&self) -> bool {
        self.metadata.is_dir()
    }

    /// Whether this file is a directory, or a symlink pointing to a directory.
    pub fn points_to_directory(&self) -> bool {
        if self.is_directory() {
            return true;
        }

        if self.is_link() {
            let target = self.link_target();
            if let FileTarget::Ok(target) = target {
                return target.points_to_directory();
            }
        }

        false
    }

    /// If this file is a directory on the filesystem, then clone its
    /// `PathBuf` for use in one of our own `Dir` values, and read a list of
    /// its contents.
    ///
    /// Returns an IO error upon failure, but this shouldn’t be used to check
    /// if a `File` is a directory or not! For that, just use `is_directory()`.
    pub fn to_dir(&self) -> io::Result<Dir> {
        Dir::read_dir(self.path.clone())
    }

    /// Whether this file is a regular file on the filesystem — that is, not a
    /// directory, a link, or anything else treated specially.
    pub fn is_file(&self) -> bool {
        self.metadata.is_file()
    }

    /// Whether this file is both a regular file *and* executable for the
    /// current user. An executable file has a different purpose from an
    /// executable directory, so they should be highlighted differently.
    #[cfg(unix)]
    pub fn is_executable_file(&self) -> bool {
        let bit = modes::USER_EXECUTE;
        self.is_file() && (self.metadata.permissions().mode() & bit) == bit
    }

    /// Whether this file is a symlink on the filesystem.
    pub fn is_link(&self) -> bool {
        self.metadata.file_type().is_symlink()
    }

    /// Whether this file is a named pipe on the filesystem.
    #[cfg(unix)]
    pub fn is_pipe(&self) -> bool {
        self.metadata.file_type().is_fifo()
    }

    /// Whether this file is a char device on the filesystem.
    #[cfg(unix)]
    pub fn is_char_device(&self) -> bool {
        self.metadata.file_type().is_char_device()
    }

    /// Whether this file is a block device on the filesystem.
    #[cfg(unix)]
    pub fn is_block_device(&self) -> bool {
        self.metadata.file_type().is_block_device()
    }

    /// Whether this file is a socket on the filesystem.
    #[cfg(unix)]
    pub fn is_socket(&self) -> bool {
        self.metadata.file_type().is_socket()
    }


    /// Re-prefixes the path pointed to by this file, if it’s a symlink, to
    /// make it an absolute path that can be accessed from whichever
    /// directory exa is being run from.
    fn reorient_target_path(&self, path: &Path) -> PathBuf {
        if path.is_absolute() {
            path.to_path_buf()
        }
        else if let Some(dir) = self.parent_dir {
            dir.join(path)
        }
        else if let Some(parent) = self.path.parent() {
            parent.join(path)
        }
        else {
            self.path.join(path)
        }
    }

    /// Again assuming this file is a symlink, follows that link and returns
    /// the result of following it.
    ///
    /// For a working symlink that the user is allowed to follow,
    /// this will be the `File` object at the other end, which can then have
    /// its name, colour, and other details read.
    ///
    /// For a broken symlink, returns where the file *would* be, if it
    /// existed. If this file cannot be read at all, returns the error that
    /// we got when we tried to read it.
    pub fn link_target(&self) -> FileTarget<'dir> {

        // We need to be careful to treat the path actually pointed to by
        // this file — which could be absolute or relative — to the path
        // we actually look up and turn into a `File` — which needs to be
        // absolute to be accessible from any directory.
        debug!("Reading link {:?}", &self.path);
        let path = match std::fs::read_link(&self.path) {
            Ok(p)   => p,
            Err(e)  => return FileTarget::Err(e),
        };

        let absolute_path = self.reorient_target_path(&path);

        // Use plain `metadata` instead of `symlink_metadata` - we *want* to
        // follow links.
        match std::fs::metadata(&absolute_path) {
            Ok(metadata) => {
                let ext  = File::ext(&path);
                let name = File::filename(&path);
                let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false };
                FileTarget::Ok(Box::new(file))
            }
            Err(e) => {
                error!("Error following link {:?}: {:#?}", &path, e);
                FileTarget::Broken(path)
            }
        }
    }

    /// This file’s number of hard links.
    ///
    /// It also reports whether this is both a regular file, and a file with
    /// multiple links. This is important, because a file with multiple links
    /// is uncommon, while you come across directories and other types
    /// with multiple links much more often. Thus, it should get highlighted
    /// more attentively.
    #[cfg(unix)]
    pub fn links(&self) -> f::Links {
        let count = self.metadata.nlink();

        f::Links {
            count,
            multiple: self.is_file() && count > 1,
        }
    }

    /// This file’s inode.
    #[cfg(unix)]
    pub fn inode(&self) -> f::Inode {
        f::Inode(self.metadata.ino())
    }

    /// This file’s number of filesystem blocks.
    ///
    /// (Not the size of each block, which we don’t actually report on)
    #[cfg(unix)]
    pub fn blocks(&self) -> f::Blocks {
        if self.is_file() || self.is_link() {
            f::Blocks::Some(self.metadata.blocks())
        }
        else {
            f::Blocks::None
        }
    }

    /// The ID of the user that own this file.
    #[cfg(unix)]
    pub fn user(&self) -> f::User {
        f::User(self.metadata.uid())
    }

    /// The ID of the group that owns this file.
    #[cfg(unix)]
    pub fn group(&self) -> f::Group {
        f::Group(self.metadata.gid())
    }

    /// This file’s size, if it’s a regular file.
    ///
    /// For directories, no size is given. Although they do have a size on
    /// some filesystems, I’ve never looked at one of those numbers and gained
    /// any information from it. So it’s going to be hidden instead.
    ///
    /// Block and character devices return their device IDs, because they
    /// usually just have a file size of zero.
    #[cfg(unix)]
    pub fn size(&self) -> f::Size {
        if self.is_directory() {
            f::Size::None
        }
        else if self.is_char_device() || self.is_block_device() {
            let device_ids = self.metadata.rdev().to_be_bytes();

            // In C-land, getting the major and minor device IDs is done with
            // preprocessor macros called `major` and `minor` that depend on
            // the size of `dev_t`, but we just take the second-to-last and
            // last bytes.
            f::Size::DeviceIDs(f::DeviceIDs {
                major: device_ids[6],
                minor: device_ids[7],
            })
        }
        else {
            f::Size::Some(self.metadata.len())
        }
    }

    #[cfg(windows)]
    pub fn size(&self) -> f::Size {
        if self.is_directory() {
            f::Size::None
        }
        else {
            f::Size::Some(self.metadata.len())
        }
    }

    /// This file’s last modified timestamp, if available on this platform.
    pub fn modified_time(&self) -> Option<SystemTime> {
        self.metadata.modified().ok()
    }

    /// This file’s last changed timestamp, if available on this platform.
    #[cfg(unix)]
    pub fn changed_time(&self) -> Option<SystemTime> {
        let (mut sec, mut nanosec) = (self.metadata.ctime(), self.metadata.ctime_nsec());

        if sec < 0 {
            if nanosec > 0 {
                sec += 1;
                nanosec -= 1_000_000_000;
            }

            let duration = Duration::new(sec.unsigned_abs(), nanosec.unsigned_abs() as u32);
            Some(UNIX_EPOCH - duration)
        }
        else {
            let duration = Duration::new(sec as u64, nanosec as u32);
            Some(UNIX_EPOCH + duration)
        }
    }

    #[cfg(windows)]
    pub fn changed_time(&self) -> Option<SystemTime> {
        return self.modified_time()
    }

    /// This file’s last accessed timestamp, if available on this platform.
    pub fn accessed_time(&self) -> Option<SystemTime> {
        self.metadata.accessed().ok()
    }

    /// This file’s created timestamp, if available on this platform.
    pub fn created_time(&self) -> Option<SystemTime> {
        self.metadata.created().ok()
    }

    /// This file’s ‘type’.
    ///
    /// This is used a the leftmost character of the permissions column.
    /// The file type can usually be guessed from the colour of the file, but
    /// ls puts this character there.
    #[cfg(unix)]
    pub fn type_char(&self) -> f::Type {
        if self.is_file() {
            f::Type::File
        }
        else if self.is_directory() {
            f::Type::Directory
        }
        else if self.is_pipe() {
            f::Type::Pipe
        }
        else if self.is_link() {
            f::Type::Link
        }
        else if self.is_char_device() {
            f::Type::CharDevice
        }
        else if self.is_block_device() {
            f::Type::BlockDevice
        }
        else if self.is_socket() {
            f::Type::Socket
        }
        else {
            f::Type::Special
        }
    }

    #[cfg(windows)]
    pub fn type_char(&self) -> f::Type {
        if self.is_file() {
            f::Type::File
        }
        else if self.is_directory() {
            f::Type::Directory
        }
        else {
            f::Type::Special
        }
    }

    /// This file’s permissions, with flags for each bit.
    #[cfg(unix)]
    pub fn permissions(&self) -> f::Permissions {
        let bits = self.metadata.mode();
        let has_bit = |bit| bits & bit == bit;

        f::Permissions {
            user_read:      has_bit(modes::USER_READ),
            user_write:     has_bit(modes::USER_WRITE),
            user_execute:   has_bit(modes::USER_EXECUTE),

            group_read:     has_bit(modes::GROUP_READ),
            group_write:    has_bit(modes::GROUP_WRITE),
            group_execute:  has_bit(modes::GROUP_EXECUTE),

            other_read:     has_bit(modes::OTHER_READ),
            other_write:    has_bit(modes::OTHER_WRITE),
            other_execute:  has_bit(modes::OTHER_EXECUTE),

            sticky:         has_bit(modes::STICKY),
            setgid:         has_bit(modes::SETGID),
            setuid:         has_bit(modes::SETUID),
        }
    }

    #[cfg(windows)]
    pub fn attributes(&self) -> f::Attributes {
        let bits = self.metadata.file_attributes();
        let has_bit = |bit| bits & bit == bit;

        // https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
        f::Attributes {
            directory:      has_bit(0x10),
            archive:        has_bit(0x20),
            readonly:       has_bit(0x1),
            hidden:         has_bit(0x2),
            system:         has_bit(0x4),
            reparse_point:  has_bit(0x400),
        }
    }

    /// Whether this file’s extension is any of the strings that get passed in.
    ///
    /// This will always return `false` if the file has no extension.
    pub fn extension_is_one_of(&self, choices: &[&str]) -> bool {
        match &self.ext {
            Some(ext)  => choices.contains(&&ext[..]),
            None       => false,
        }
    }

    /// Whether this file’s name, including extension, is any of the strings
    /// that get passed in.
    pub fn name_is_one_of(&self, choices: &[&str]) -> bool {
        choices.contains(&&self.name[..])
    }
}


impl<'a> AsRef<File<'a>> for File<'a> {
    fn as_ref(&self) -> &File<'a> {
        self
    }
}


/// The result of following a symlink.
pub enum FileTarget<'dir> {

    /// The symlink pointed at a file that exists.
    Ok(Box<File<'dir>>),

    /// The symlink pointed at a file that does not exist. Holds the path
    /// where the file would be, if it existed.
    Broken(PathBuf),

    /// There was an IO error when following the link. This can happen if the
    /// file isn’t a link to begin with, but also if, say, we don’t have
    /// permission to follow it.
    Err(io::Error),

    // Err is its own variant, instead of having the whole thing be inside an
    // `io::Result`, because being unable to follow a symlink is not a serious
    // error — we just display the error message and move on.
}

impl<'dir> FileTarget<'dir> {

    /// Whether this link doesn’t lead to a file, for whatever reason. This
    /// gets used to determine how to highlight the link in grid views.
    pub fn is_broken(&self) -> bool {
        matches!(self, Self::Broken(_) | Self::Err(_))
    }
}


/// More readable aliases for the permission bits exposed by libc.
#[allow(trivial_numeric_casts)]
#[cfg(unix)]
mod modes {

    // The `libc::mode_t` type’s actual type varies, but the value returned
    // from `metadata.permissions().mode()` is always `u32`.
    pub type Mode = u32;

    pub const USER_READ: Mode     = libc::S_IRUSR as Mode;
    pub const USER_WRITE: Mode    = libc::S_IWUSR as Mode;
    pub const USER_EXECUTE: Mode  = libc::S_IXUSR as Mode;

    pub const GROUP_READ: Mode    = libc::S_IRGRP as Mode;
    pub const GROUP_WRITE: Mode   = libc::S_IWGRP as Mode;
    pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode;

    pub const OTHER_READ: Mode    = libc::S_IROTH as Mode;
    pub const OTHER_WRITE: Mode   = libc::S_IWOTH as Mode;
    pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode;

    pub const STICKY: Mode        = libc::S_ISVTX as Mode;
    pub const SETGID: Mode        = libc::S_ISGID as Mode;
    pub const SETUID: Mode        = libc::S_ISUID as Mode;
}


#[cfg(test)]
mod ext_test {
    use super::File;
    use std::path::Path;

    #[test]
    fn extension() {
        assert_eq!(Some("dat".to_string()), File::ext(Path::new("fester.dat")))
    }

    #[test]
    fn dotfile() {
        assert_eq!(Some("vimrc".to_string()), File::ext(Path::new(".vimrc")))
    }

    #[test]
    fn no_extension() {
        assert_eq!(None, File::ext(Path::new("jarlsberg")))
    }
}


#[cfg(test)]
mod filename_test {
    use super::File;
    use std::path::Path;

    #[test]
    fn file() {
        assert_eq!("fester.dat", File::filename(Path::new("fester.dat")))
    }

    #[test]
    fn no_path() {
        assert_eq!("foo.wha", File::filename(Path::new("/var/cache/foo.wha")))
    }

    #[test]
    fn here() {
        assert_eq!(".", File::filename(Path::new(".")))
    }

    #[test]
    fn there() {
        assert_eq!("..", File::filename(Path::new("..")))
    }

    #[test]
    fn everywhere() {
        assert_eq!("..", File::filename(Path::new("./..")))
    }

    #[test]
    #[cfg(unix)]
    fn topmost() {
        assert_eq!("/", File::filename(Path::new("/")))
    }
}


================================================
FILE: src/fs/filter.rs
================================================
//! Filtering and sorting the list of files before displaying them.

use std::cmp::Ordering;
use std::iter::FromIterator;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;

use crate::fs::DotFilter;
use crate::fs::File;


/// The **file filter** processes a list of files before displaying them to
/// the user, by removing files they don’t want to see, and putting the list
/// in the desired order.
///
/// Usually a user does not want to see *every* file in the list. The most
/// common case is to remove files starting with `.`, which are designated
/// as ‘hidden’ files.
///
/// The special files `.` and `..` files are not actually filtered out, but
/// need to be inserted into the list, in a special case.
///
/// The filter also governs sorting the list. After being filtered, pairs of
/// files are compared and sorted based on the result, with the sort field
/// performing the comparison.
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct FileFilter {

    /// Whether directories should be listed first, and other types of file
    /// second. Some users prefer it like this.
    pub list_dirs_first: bool,

    /// The metadata field to sort by.
    pub sort_field: SortField,

    /// Whether to reverse the sorting order. This would sort the largest
    /// files first, or files starting with Z, or the most-recently-changed
    /// ones, depending on the sort field.
    pub reverse: bool,

    /// Whether to only show directories.
    pub only_dirs: bool,

    /// Which invisible “dot” files to include when listing a directory.
    ///
    /// Files starting with a single “.” are used to determine “system” or
    /// “configuration” files that should not be displayed in a regular
    /// directory listing, and the directory entries “.” and “..” are
    /// considered extra-special.
    ///
    /// This came about more or less by a complete historical accident,
    /// when the original `ls` tried to hide `.` and `..`:
    ///
    /// [Linux History: How Dot Files Became Hidden Files](https://linux-audit.com/linux-history-how-dot-files-became-hidden-files/)
    pub dot_filter: DotFilter,

    /// Glob patterns to ignore. Any file name that matches *any* of these
    /// patterns won’t be displayed in the list.
    pub ignore_patterns: IgnorePatterns,

    /// Whether to ignore Git-ignored patterns.
    pub git_ignore: GitIgnore,
}

impl FileFilter {
    /// Remove every file in the given vector that does *not* pass the
    /// filter predicate for files found inside a directory.
    pub fn filter_child_files(&self, files: &mut Vec<File<'_>>) {
        files.retain(|f| ! self.ignore_patterns.is_ignored(&f.name));

        if self.only_dirs {
            files.retain(File::is_directory);
        }
    }

    /// Remove every file in the given vector that does *not* pass the
    /// filter predicate for file names specified on the command-line.
    ///
    /// The rules are different for these types of files than the other
    /// type because the ignore rules can be used with globbing. For
    /// example, running `exa -I='*.tmp' .vimrc` shouldn’t filter out the
    /// dotfile, because it’s been directly specified. But running
    /// `exa -I='*.ogg' music/*` should filter out the ogg files obtained
    /// from the glob, even though the globbing is done by the shell!
    pub fn filter_argument_files(&self, files: &mut Vec<File<'_>>) {
        files.retain(|f| {
            ! self.ignore_patterns.is_ignored(&f.name)
        });
    }

    /// Sort the files in the given vector based on the sort field option.
    pub fn sort_files<'a, F>(&self, files: &mut [F])
    where F: AsRef<File<'a>>
    {
        files.sort_by(|a, b| {
            self.sort_field.compare_files(a.as_ref(), b.as_ref())
        });

        if self.reverse {
            files.reverse();
        }

        if self.list_dirs_first {
            // This relies on the fact that `sort_by` is *stable*: it will keep
            // adjacent elements next to each other.
            files.sort_by(|a, b| {
                b.as_ref().points_to_directory()
                    .cmp(&a.as_ref().points_to_directory())
            });
        }
    }
}


/// User-supplied field to sort by.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum SortField {

    /// Don’t apply any sorting. This is usually used as an optimisation in
    /// scripts, where the order doesn’t matter.
    Unsorted,

    /// The file name. This is the default sorting.
    Name(SortCase),

    /// The file’s extension, with extensionless files being listed first.
    Extension(SortCase),

    /// The file’s size, in bytes.
    Size,

    /// The file’s inode, which usually corresponds to the order in which
    /// files were created on the filesystem, more or less.
    #[cfg(unix)]
    FileInode,

    /// The time the file was modified (the “mtime”).
    ///
    /// As this is stored as a Unix timestamp, rather than a local time
    /// instance, the time zone does not matter and will only be used to
    /// display the timestamps, not compare them.
    ModifiedDate,

    /// The time the file was accessed (the “atime”).
    ///
    /// Oddly enough, this field rarely holds the *actual* accessed time.
    /// Recording a read time means writing to the file each time it’s read
    /// slows the whole operation down, so many systems will only update the
    /// timestamp in certain circumstances. This has become common enough that
    /// it’s now expected behaviour!
    /// <https://unix.stackexchange.com/a/8842>
    AccessedDate,

    /// The time the file was changed (the “ctime”).
    ///
    /// This field is used to mark the time when a file’s metadata
    /// changed — its permissions, owners, or link count.
    ///
    /// In original Unix, this was, however, meant as creation time.
    /// <https://www.bell-labs.com/usr/dmr/www/cacm.html>
    ChangedDate,

    /// The time the file was created (the “btime” or “birthtime”).
    CreatedDate,

    /// The type of the file: directories, links, pipes, regular, files, etc.
    ///
    /// Files are ordered according to the `PartialOrd` implementation of
    /// `fs::fields::Type`, so changing that will change this.
    FileType,

    /// The “age” of the file, which is the time it was modified sorted
    /// backwards. The reverse of the `ModifiedDate` ordering!
    ///
    /// It turns out that listing the most-recently-modified files first is a
    /// common-enough use case that it deserves its own variant. This would be
    /// implemented by just using the modified date and setting the reverse
    /// flag, but this would make reversing *that* output not work, which is
    /// bad, even though that’s kind of nonsensical. So it’s its own variant
    /// that can be reversed like usual.
    ModifiedAge,

    /// The file's name, however if the name of the file begins with `.`
    /// ignore the leading `.` and then sort as Name
    NameMixHidden(SortCase),
}

/// Whether a field should be sorted case-sensitively or case-insensitively.
/// This determines which of the `natord` functions to use.
///
/// I kept on forgetting which one was sensitive and which one was
/// insensitive. Would a case-sensitive sort put capital letters first because
/// it takes the case of the letters into account, or intermingle them with
/// lowercase letters because it takes the difference between the two cases
/// into account? I gave up and just named these two variants after the
/// effects they have.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum SortCase {

    /// Sort files case-sensitively with uppercase first, with ‘A’ coming
    /// before ‘a’.
    ABCabc,

    /// Sort files case-insensitively, with ‘A’ being equal to ‘a’.
    AaBbCc,
}

impl SortField {

    /// Compares two files to determine the order they should be listed in,
    /// depending on the search field.
    ///
    /// The `natord` crate is used here to provide a more *natural* sorting
    /// order than just sorting character-by-character. This splits filenames
    /// into groups between letters and numbers, and then sorts those blocks
    /// together, so `file10` will sort after `file9`, instead of before it
    /// because of the `1`.
    pub fn compare_files(self, a: &File<'_>, b: &File<'_>) -> Ordering {
        use self::SortCase::{ABCabc, AaBbCc};

        match self {
            Self::Unsorted  => Ordering::Equal,

            Self::Name(ABCabc)  => natord::compare(&a.name, &b.name),
            Self::Name(AaBbCc)  => natord::compare_ignore_case(&a.name, &b.name),

            Self::Size          => a.metadata.len().cmp(&b.metadata.len()),
            #[cfg(unix)]
            Self::FileInode     => a.metadata.ino().cmp(&b.metadata.ino()),
            Self::ModifiedDate  => a.modified_time().cmp(&b.modified_time()),
            Self::AccessedDate  => a.accessed_time().cmp(&b.accessed_time()),
            Self::ChangedDate   => a.changed_time().cmp(&b.changed_time()),
            Self::CreatedDate   => a.created_time().cmp(&b.created_time()),
            Self::ModifiedAge   => b.modified_time().cmp(&a.modified_time()),  // flip b and a

            Self::FileType => match a.type_char().cmp(&b.type_char()) { // todo: this recomputes
                Ordering::Equal  => natord::compare(&*a.name, &*b.name),
                order            => order,
            },

            Self::Extension(ABCabc) => match a.ext.cmp(&b.ext) {
                Ordering::Equal  => natord::compare(&*a.name, &*b.name),
                order            => order,
            },

            Self::Extension(AaBbCc) => match a.ext.cmp(&b.ext) {
                Ordering::Equal  => natord::compare_ignore_case(&*a.name, &*b.name),
                order            => order,
            },

            Self::NameMixHidden(ABCabc) => natord::compare(
                Self::strip_dot(&a.name),
                Self::strip_dot(&b.name)
            ),
            Self::NameMixHidden(AaBbCc) => natord::compare_ignore_case(
                Self::strip_dot(&a.name),
                Self::strip_dot(&b.name)
            )
        }
    }

    fn strip_dot(n: &str) -> &str {
        match n.strip_prefix('.') {
            Some(s) => s,
            None    => n,
        }
    }
}


/// The **ignore patterns** are a list of globs that are tested against
/// each filename, and if any of them match, that file isn’t displayed.
/// This lets a user hide, say, text files by ignoring `*.txt`.
#[derive(PartialEq, Eq, Default, Debug, Clone)]
pub struct IgnorePatterns {
    patterns: Vec<glob::Pattern>,
}

impl FromIterator<glob::Pattern> for IgnorePatterns {

    fn from_iter<I>(iter: I) -> Self
    where I: IntoIterator<Item = glob::Pattern>
    {
        let patterns = iter.into_iter().collect();
        Self { patterns }
    }
}

impl IgnorePatterns {

    /// Create a new list from the input glob strings, turning the inputs that
    /// are valid glob patterns into an `IgnorePatterns`. The inputs that
    /// don’t parse correctly are returned separately.
    pub fn parse_from_iter<'a, I: IntoIterator<Item = &'a str>>(iter: I) -> (Self, Vec<glob::PatternError>) {
        let iter = iter.into_iter();

        // Almost all glob patterns are valid, so it’s worth pre-allocating
        // the vector with enough space for all of them.
        let mut patterns = match iter.size_hint() {
            (_, Some(count))  => Vec::with_capacity(count),
             _                => Vec::new(),
        };

        // Similarly, assume there won’t be any errors.
        let mut errors = Vec::new();

        for input in iter {
            match glob::Pattern::new(input) {
                Ok(pat) => patterns.push(pat),
                Err(e)  => errors.push(e),
            }
        }

        (Self { patterns }, errors)
    }

    /// Create a new empty set of patterns that matches nothing.
    pub fn empty() -> Self {
        Self { patterns: Vec::new() }
    }

    /// Test whether the given file should be hidden from the results.
    fn is_ignored(&self, file: &str) -> bool {
        self.patterns.iter().any(|p| p.matches(file))
    }
}


/// Whether to ignore or display files that Git would ignore.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum GitIgnore {

    /// Ignore files that Git would ignore.
    CheckAndIgnore,

    /// Display files, even if Git would ignore them.
    Off,
}



#[cfg(test)]
mod test_ignores {
    use super::*;

    #[test]
    fn empty_matches_nothing() {
        let pats = IgnorePatterns::empty();
        assert!(!pats.is_ignored("nothing"));
        assert!(!pats.is_ignored("test.mp3"));
    }

    #[test]
    fn ignores_a_glob() {
        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "*.mp3" ]);
        assert!(fails.is_empty());
        assert!(!pats.is_ignored("nothing"));
        assert!(pats.is_ignored("test.mp3"));
    }

    #[test]
    fn ignores_an_exact_filename() {
        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing" ]);
        assert!(fails.is_empty());
        assert!(pats.is_ignored("nothing"));
        assert!(!pats.is_ignored("test.mp3"));
    }

    #[test]
    fn ignores_both() {
        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing", "*.mp3" ]);
        assert!(fails.is_empty());
        assert!(pats.is_ignored("nothing"));
        assert!(pats.is_ignored("test.mp3"));
    }
}


================================================
FILE: src/fs/mod.rs
================================================
mod dir;
pub use self::dir::{Dir, DotFilter};

mod file;
pub use self::file::{File, FileTarget};

pub mod dir_action;
pub mod feature;
pub mod fields;
pub mod filter;


================================================
FILE: src/info/filetype.rs
================================================
//! Tests for various types of file (video, image, compressed, etc).
//!
//! Currently this is dependent on the file’s name and extension, because
//! those are the only metadata that we have access to without reading the
//! file’s contents.

use ansi_term::Style;

use crate::fs::File;
use crate::output::icons::FileIcon;
use crate::theme::FileColours;


#[derive(Debug, Default, PartialEq, Eq)]
pub struct FileExtensions;

impl FileExtensions {

    /// An “immediate” file is something that can be run or activated somehow
    /// in order to kick off the build of a project. It’s usually only present
    /// in directories full of source code.
    #[allow(clippy::case_sensitive_file_extension_comparisons)]
    fn is_immediate(&self, file: &File<'_>) -> bool {
        file.name.to_lowercase().starts_with("readme") ||
        file.name.ends_with(".ninja") ||
        file.name_is_one_of( &[
            "Makefile", "Cargo.toml", "SConstruct", "CMakeLists.txt",
            "build.gradle", "pom.xml", "Rakefile", "package.json", "Gruntfile.js",
            "Gruntfile.coffee", "BUILD", "BUILD.bazel", "WORKSPACE", "build.xml", "Podfile",
            "webpack.config.js", "meson.build", "composer.json", "RoboFile.php", "PKGBUILD",
            "Justfile", "Procfile", "Dockerfile", "Containerfile", "Vagrantfile", "Brewfile",
            "Gemfile", "Pipfile", "build.sbt", "mix.exs", "bsconfig.json", "tsconfig.json",
        ])
    }

    fn is_image(&self, file: &File<'_>) -> bool {
        file.extension_is_one_of( &[
            "png", "jfi", "jfif", "jif", "jpe", "jpeg", "jpg", "gif", "bmp",
            "tiff", "tif", "ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw",
            "svg", "stl", "eps", "dvi", "ps", "cbr", "jpf", "cbz", "xpm",
            "ico", "cr2", "orf", "nef", "heif", "avif", "jxl", "j2k", "jp2",
            "j2c", "jpx",
        ])
    }

    fn is_video(&self, file: &File<'_>) -> bool {
        file.extension_is_one_of( &[
            "avi", "flv", "m2v", "m4v", "mkv", "mov", "mp4", "mpeg",
            "mpg", "ogm", "ogv", "vob", "wmv", "webm", "m2ts", "heic",
        ])
    }

    fn is_music(&self, file: &File<'_>) -> bool {
        file.extension_is_one_of( &[
            "aac", "m4a", "mp3", "ogg", "wma", "mka", "opus",
        ])
    }

    // Lossless music, rather than any other kind of data...
    fn is_lossless(&self, file: &File<'_>) -> bool {
        file.extension_is_one_of( &[
            "alac", "ape", "flac", "wav",
        ])
    }

    fn is_crypto(&self, file: &File<'_>) -> bool {
        file.extension_is_one_of( &[
            "asc", "enc", "gpg", "pgp", "sig", "signature", "pfx", "p12",
        ])
    }

    fn is_document(&self, file: &File<'_>) -> bool {
        file.extension_is_one_of( &[
            "djvu", "doc", "docx", "dvi", "eml", "eps", "fotd", "key",
            "keynote", "numbers", "odp", "odt", "pages", "pdf", "ppt",
            "pptx", "rtf", "xls", "xlsx",
        ])
    }

    fn is_compressed(&self, file: &File<'_>) -> bool {
        file.extension_is_one_of( &[
            "zip", "tar", "Z", "z", "gz", "bz2", "a", "ar", "7z",
            "iso", "dmg", "tc", "rar", "par", "tgz", "xz", "txz",
            "lz", "tlz", "lzma", "deb", "rpm", "zst", "lz4", "cpio",
        ])
    }

    fn is_temp(&self, file: &File<'_>) -> bool {
        file.name.ends_with('~')
            || (file.name.starts_with('#') && file.name.ends_with('#'))
            || file.extension_is_one_of( &[ "tmp", "swp", "swo", "swn", "bak", "bkp", "bk" ])
    }

    fn is_compiled(&self, file: &File<'_>) -> bool {
        if file.extension_is_one_of( &[ "class", "elc", "hi", "o", "pyc", "zwc", "ko" ]) {
            true
        }
        else if let Some(dir) = file.parent_dir {
            file.get_source_files().iter().any(|path| dir.contains(path))
        }
        else {
            false
        }
    }
}

impl FileColours for FileExtensions {
    fn colour_file(&self, file: &File<'_>) -> Option<Style> {
        use ansi_term::Colour::*;

        Some(match file {
            f if self.is_temp(f)        => Fixed(244).normal(),
            f if self.is_immediate(f)   => Yellow.bold().underline(),
            f if self.is_image(f)       => Fixed(133).normal(),
            f if self.is_video(f)       => Fixed(135).normal(),
            f if self.is_music(f)       => Fixed(92).normal(),
            f if self.is_lossless(f)    => Fixed(93).normal(),
            f if self.is_crypto(f)      => Fixed(109).normal(),
            f if self.is_document(f)    => Fixed(105).normal(),
            f if self.is_compressed(f)  => Red.normal(),
            f if self.is_compiled(f)    => Fixed(137).normal(),
            _                           => return None,
        })
    }
}

impl FileIcon for FileExtensions {
    fn icon_file(&self, file: &File<'_>) -> Option<char> {
        use crate::output::icons::Icons;

        if self.is_music(file) || self.is_lossless(file) {
            Some(Icons::Audio.value())
        }
        else if self.is_image(file) {
            Some(Icons::Image.value())
        }
        else if self.is_video(file) {
            Some(Icons::Video.value())
        }
        else {
            None
        }
    }
}


================================================
FILE: src/info/mod.rs
================================================
//! The “info” module contains routines that aren’t about probing the
//! filesystem nor displaying output to the user, but are internal “business
//! logic” routines that are performed on a file’s already-read metadata.
//! (This counts the file name as metadata.)

pub mod filetype;
mod sources;


================================================
FILE: src/info/sources.rs
================================================
use std::path::PathBuf;

use crate::fs::File;


impl<'a> File<'a> {

    /// For this file, return a vector of alternate file paths that, if any of
    /// them exist, mean that *this* file should be coloured as “compiled”.
    ///
    /// The point of this is to highlight compiled files such as `foo.js` when
    /// their source file `foo.coffee` exists in the same directory.
    /// For example, `foo.js` is perfectly valid without `foo.coffee`, so we
    /// don’t want to always blindly highlight `*.js` as compiled.
    /// (See also `FileExtensions#is_compiled`)
    pub fn get_source_files(&self) -> Vec<PathBuf> {
        if let Some(ext) = &self.ext {
            match &ext[..] {
                "css"   => vec![self.path.with_extension("sass"), self.path.with_extension("scss"),  // SASS, SCSS
                                self.path.with_extension("styl"), self.path.with_extension("less")],  // Stylus, Less
                "js"    => vec![self.path.with_extension("coffee"), self.path.with_extension("ts")],  // CoffeeScript, TypeScript

                "aux" |                                          // TeX: auxiliary file
                "bbl" |                                          // BibTeX bibliography file
                "bcf" |                                          // biblatex control file
                "blg" |                                          // BibTeX log file
                "fdb_latexmk" |                                  // TeX latexmk file
                "fls" |                                          // TeX -recorder file
                "lof" |                                          // TeX list of figures
                "log" |                                          // TeX log file
                "lot" |                                          // TeX list of tables
                "toc" => vec![self.path.with_extension("tex")],  // TeX table of contents

                _ => vec![],  // No source files if none of the above
            }
        }
        else {
            vec![]  // No source files if there’s no extension, either!
        }
    }
}


================================================
FILE: src/logger.rs
================================================
//! Debug error logging.

use std::ffi::OsStr;

use ansi_term::{Colour, ANSIString};


/// Sets the internal logger, changing the log level based on the value of an
/// environment variable.
pub fn configure<T: AsRef<OsStr>>(ev: Option<T>) {
    let ev = match ev {
        Some(v)  => v,
        None     => return,
    };

    let env_var = ev.as_ref();
    if env_var.is_empty() {
        return;
    }

    if env_var == "trace" {
        log::set_max_level(log::LevelFilter::Trace);
    }
    else {
        log::set_max_level(log::LevelFilter::Debug);
    }

    let result = log::set_logger(GLOBAL_LOGGER);
    if let Err(e) = result {
        eprintln!("Failed to initialise logger: {}", e);
    }
}


#[derive(Debug)]
struct Logger;

const GLOBAL_LOGGER: &Logger = &Logger;

impl log::Log for Logger {
    fn enabled(&self, _: &log::Metadata<'_>) -> bool {
        true  // no need to filter after using ‘set_max_level’.
    }

    fn log(&self, record: &log::Record<'_>) {
        let open = Colour::Fixed(243).paint("[");
        let level = level(record.level());
        let close = Colour::Fixed(243).paint("]");

        eprintln!("{}{} {}{} {}", open, level, record.target(), close, record.args());
    }

    fn flush(&self) {
        // no need to flush with ‘eprintln!’.
    }
}

fn level(level: log::Level) -> ANSIString<'static> {
    match level {
        log::Level::Error  => Colour::Red.paint("ERROR"),
        log::Level::Warn   => Colour::Yellow.paint("WARN"),
        log::Level::Info   => Colour::Cyan.paint("INFO"),
        log::Level::Debug  => Colour::Blue.paint("DEBUG"),
        log::Level::Trace  => Colour::Fixed(245).paint("TRACE"),
    }
}


================================================
FILE: src/main.rs
================================================
#![warn(deprecated_in_future)]
#![warn(future_incompatible)]
#![warn(nonstandard_style)]
#![warn(rust_2018_compatibility)]
#![warn(rust_2018_idioms)]
#![warn(trivial_casts, trivial_numeric_casts)]
#![warn(unused)]

#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::enum_glob_use)]
#![allow(clippy::map_unwrap_or)]
#![allow(clippy::match_same_arms)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::non_ascii_literal)]
#![allow(clippy::option_if_let_else)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::unused_self)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::wildcard_imports)]

use std::env;
use std::ffi::{OsStr, OsString};
use std::io::{self, Write, ErrorKind};
use std::path::{Component, PathBuf};

use ansi_term::{ANSIStrings, Style};

use log::*;

use crate::fs::{Dir, File};
use crate::fs::feature::git::GitCache;
use crate::fs::filter::GitIgnore;
use crate::options::{Options, Vars, vars, OptionsResult};
use crate::output::{escape, lines, grid, grid_details, details, View, Mode};
use crate::theme::Theme;

mod fs;
mod info;
mod logger;
mod options;
mod output;
mod theme;


fn main() {
    use std::process::exit;

    #[cfg(unix)]
    unsafe {
        libc::signal(libc::SIGPIPE, libc::SIG_DFL);
    }

    logger::configure(env::var_os(vars::EXA_DEBUG));

    #[cfg(windows)]
    if let Err(e) = ansi_term::enable_ansi_support() {
        warn!("Failed to enable ANSI support: {}", e);
    }

    let args: Vec<_> = env::args_os().skip(1).collect();
    match Options::parse(args.iter().map(std::convert::AsRef::as_ref), &LiveVars) {
        OptionsResult::Ok(options, mut input_paths) => {

            // List the current directory by default.
            // (This has to be done here, otherwise git_options won’t see it.)
            if input_paths.is_empty() {
                input_paths = vec![ OsStr::new(".") ];
            }

            let git = git_options(&options, &input_paths);
            let writer = io::stdout();

            let console_width = options.view.width.actual_terminal_width();
            let theme = options.theme.to_theme(console_width.is_some());
            let exa = Exa { options, writer, input_paths, theme, console_width, git };

            match exa.run() {
                Ok(exit_status) => {
                    exit(exit_status);
                }

                Err(e) if e.kind() == ErrorKind::BrokenPipe => {
                    warn!("Broken pipe error: {}", e);
                    exit(exits::SUCCESS);
                }

                Err(e) => {
                    eprintln!("{}", e);
                    exit(exits::RUNTIME_ERROR);
                }
            }
        }

        OptionsResult::Help(help_text) => {
            print!("{}", help_text);
        }

        OptionsResult::Version(version_str) => {
            print!("{}", version_str);
        }

        OptionsResult::InvalidOptions(error) => {
            eprintln!("exa: {}", error);

            if let Some(s) = error.suggestion() {
                eprintln!("{}", s);
            }

            exit(exits::OPTIONS_ERROR);
        }
    }
}


/// The main program wrapper.
pub struct Exa<'args> {

    /// List of command-line options, having been successfully parsed.
    pub options: Options,

    /// The output handle that we write to.
    pub writer: io::Stdout,

    /// List of the free command-line arguments that should correspond to file
    /// names (anything that isn’t an option).
    pub input_paths: Vec<&'args OsStr>,

    /// The theme that has been configured from the command-line options and
    /// environment variables. If colours are disabled, this is a theme with
    /// every style set to the default.
    pub theme: Theme,

    /// The detected width of the console. This is used to determine which
    /// view to use.
    pub console_width: Option<usize>,

    /// A global Git cache, if the option was passed in.
    /// This has to last the lifetime of the program, because the user might
    /// want to list several directories in the same repository.
    pub git: Option<GitCache>,
}

/// The “real” environment variables type.
/// Instead of just calling `var_os` from within the options module,
/// the method of looking up environment variables has to be passed in.
struct LiveVars;
impl Vars for LiveVars {
    fn get(&self, name: &'static str) -> Option<OsString> {
        env::var_os(name)
    }
}

/// Create a Git cache populated with the arguments that are going to be
/// listed before they’re actually listed, if the options demand it.
fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
    if options.should_scan_for_git() {
        Some(args.iter().map(PathBuf::from).collect())
    }
    else {
        None
    }
}

impl<'args> Exa<'args> {
    /// # Errors
    ///
    /// Will return `Err` if printing to stderr fails.
    pub fn run(mut self) -> io::Result<i32> {
        debug!("Running with options: {:#?}", self.options);

        let mut files = Vec::new();
        let mut dirs = Vec::new();
        let mut exit_status = 0;

        for file_path in &self.input_paths {
            match File::from_args(PathBuf::from(file_path), None, None) {
                Err(e) => {
                    exit_status = 2;
                    writeln!(io::stderr(), "{:?}: {}", file_path, e)?;
                }

                Ok(f) => {
                    if f.points_to_directory() && ! self.options.dir_action.treat_dirs_as_files() {
                        match f.to_dir() {
                            Ok(d)   => dirs.push(d),
                            Err(e)  => writeln!(io::stderr(), "{:?}: {}", file_path, e)?,
                        }
                    }
                    else {
                        files.push(f);
                    }
                }
            }
        }

        // We want to print a directory’s name before we list it, *except* in
        // the case where it’s the only directory, *except* if there are any
        // files to print as well. (It’s a double negative)

        let no_files = files.is_empty();
        let is_only_dir = dirs.len() == 1 && no_files;

        self.options.filter.filter_argument_files(&mut files);
        self.print_files(None, files)?;

        self.print_dirs(dirs, no_files, is_only_dir, exit_status)
    }

    fn print_dirs(&mut self, dir_files: Vec<Dir>, mut first: bool, is_only_dir: bool, exit_status: i32) -> io::Result<i32> {
        for dir in dir_files {

            // Put a gap between directories, or between the list of files and
            // the first directory.
            if first {
                first = false;
            }
            else {
                writeln!(&mut self.writer)?;
            }

            if ! is_only_dir {
                let mut bits = Vec::new();
                escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default());
                writeln!(&mut self.writer, "{}:", ANSIStrings(&bits))?;
            }

            let mut children = Vec::new();
            let git_ignore = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
            for file in dir.files(self.options.filter.dot_filter, self.git.as_ref(), git_ignore) {
                match file {
                    Ok(file)        => children.push(file),
                    Err((path, e))  => writeln!(io::stderr(), "[{}: {}]", path.display(), e)?,
                }
            };

            self.options.filter.filter_child_files(&mut children);
            self.options.filter.sort_files(&mut children);

            if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
                let depth = dir.path.components().filter(|&c| c != Component::CurDir).count() + 1;
                if ! recurse_opts.tree && ! recurse_opts.is_too_deep(depth) {

                    let mut child_dirs = Vec::new();
                    for child_dir in children.iter().filter(|f| f.is_directory() && ! f.is_all_all) {
                        match child_dir.to_dir() {
                            Ok(d)   => child_dirs.push(d),
                            Err(e)  => writeln!(io::stderr(), "{}: {}", child_dir.path.display(), e)?,
                        }
                    }

                    self.print_files(Some(&dir), children)?;
                    match self.print_dirs(child_dirs, false, false, exit_status) {
                        Ok(_)   => (),
                        Err(e)  => return Err(e),
                    }
                    continue;
                }
            }

            self.print_files(Some(&dir), children)?;
        }

        Ok(exit_status)
    }

    /// Prints the list of files using whichever view is selected.
    fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File<'_>>) -> io::Result<()> {
        if files.is_empty() {
            return Ok(());
        }

        let theme = &self.theme;
        let View { ref mode, ref file_style, .. } = self.options.view;

        match (mode, self.console_width) {
            (Mode::Grid(ref opts), Some(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 filter = &self.options.filter;
                let r = lines::Render { files, theme, file_style, filter };
                r.render(&mut self.writer)
            }

            (Mode::Details(ref opts), _) => {
                let filter = &self.options.filter;
                let recurse = self.options.dir_action.recurse_options();

                let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
                let git = self.git.as_ref();
                let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git };
                r.render(&mut self.writer)
            }

            (Mode::GridDetails(ref opts), Some(console_width)) => {
                let grid = &opts.grid;
                let details = &opts.details;
                let row_threshold = opts.row_threshold;

                let filter = &self.options.filter;
                let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
                let git = self.git.as_ref();

                let r = grid_details::Render { dir, files, theme, file_style, grid, details, filter, row_threshold, git_ignoring, git, console_width };
                r.render(&mut self.writer)
            }

            (Mode::GridDetails(ref opts), None) => {
                let opts = &opts.to_details_options();
                let filter = &self.options.filter;
                let recurse = self.options.dir_action.recurse_options();
                let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;

                let git = self.git.as_ref();
                let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git };
                r.render(&mut self.writer)
            }
        }
    }
}


mod exits {

    /// Exit code for when exa runs OK.
    pub const SUCCESS: i32 = 0;

    /// Exit code for when there was at least one I/O error during execution.
    pub const RUNTIME_ERROR: i32 = 1;

    /// Exit code for when the command-line options are invalid.
    pub const OPTIONS_ERROR: i32 = 3;
}


================================================
FILE: src/options/dir_action.rs
================================================
//! Parsing the options for `DirAction`.

use crate::options::parser::MatchedFlags;
use crate::options::{flags, OptionsError, NumberSource};

use crate::fs::dir_action::{DirAction, RecurseOptions};


impl DirAction {

    /// Determine which action to perform when trying to list a directory.
    /// There are three possible actions, and they overlap somewhat: the
    /// `--tree` flag is another form of recursion, so those two are allowed
    /// to both be present, but the `--list-dirs` flag is used separately.
    pub fn deduce(matches: &MatchedFlags<'_>, can_tree: bool) -> Result<Self, OptionsError> {
        let recurse = matches.has(&flags::RECURSE)?;
        let as_file = matches.has(&flags::LIST_DIRS)?;
        let tree    = matches.has(&flags::TREE)?;

        if matches.is_strict() {
            // Early check for --level when it wouldn’t do anything
            if ! recurse && ! tree && matches.count(&flags::LEVEL) > 0 {
                return Err(OptionsError::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
            }
            else if recurse && as_file {
                return Err(OptionsError::Conflict(&flags::RECURSE, &flags::LIST_DIRS));
            }
            else if tree && as_file {
                return Err(OptionsError::Conflict(&flags::TREE, &flags::LIST_DIRS));
            }
        }

        if tree && can_tree {
            // Tree is only appropriate in details mode, so this has to
            // examine the View, which should have already been deduced by now
            Ok(Self::Recurse(RecurseOptions::deduce(matches, true)?))
        }
        else if recurse {
            Ok(Self::Recurse(RecurseOptions::deduce(matches, false)?))
        }
        else if as_file {
            Ok(Self::AsFile)
        }
        else {
            Ok(Self::List)
        }
    }
}


impl RecurseOptions {

    /// Determine which files should be recursed into, based on the `--level`
    /// flag’s value, and whether the `--tree` flag was passed, which was
    /// determined earlier. The maximum level should be a number, and this
    /// will fail with an `Err` if it isn’t.
    pub fn deduce(matches: &MatchedFlags<'_>, tree: bool) -> Result<Self, OptionsError> {
        if let Some(level) = matches.get(&flags::LEVEL)? {
            let arg_str = level.to_string_lossy();
            match arg_str.parse() {
                Ok(l) => {
                    Ok(Self { tree, max_depth: Some(l) })
                }
                Err(e) => {
                    let source = NumberSource::Arg(&flags::LEVEL);
                    Err(OptionsError::FailedParse(arg_str.to_string(), source, e))
                }
            }
        }
        else {
            Ok(Self { tree, max_depth: None })
        }
    }
}


#[cfg(test)]
mod test {
    use super::*;
    use crate::options::flags;
    use crate::options::parser::Flag;

    macro_rules! test {
        ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
            #[test]
            fn $name() {
                use crate::options::parser::Arg;
                use crate::options::test::parse_for_test;
                use crate::options::test::Strictnesses::*;

                static TEST_ARGS: &[&Arg] = &[&flags::RECURSE, &flags::LIST_DIRS, &flags::TREE, &flags::LEVEL ];
                for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, true)) {
                    assert_eq!(result, $result);
                }
            }
        };
    }


    // Default behaviour
    test!(empty:           DirAction <- [];               Both => Ok(DirAction::List));

    // Listing files as directories
    test!(dirs_short:      DirAction <- ["-d"];           Both => Ok(DirAction::AsFile));
    test!(dirs_long:       DirAction <- ["--list-dirs"];  Both => Ok(DirAction::AsFile));

    // Recursing
    use self::DirAction::Recurse;
    test!(rec_short:       DirAction <- ["-R"];                           Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));
    test!(rec_long:        DirAction <- ["--recurse"];                    Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));
    test!(rec_lim_short:   DirAction <- ["-RL4"];                         Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(4) })));
    test!(rec_lim_short_2: DirAction <- ["-RL=5"];                        Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(5) })));
    test!(rec_lim_long:    DirAction <- ["--recurse", "--level", "666"];  Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(666) })));
    test!(rec_lim_long_2:  DirAction <- ["--recurse", "--level=0118"];    Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(118) })));
    test!(tree:            DirAction <- ["--tree"];                       Both => Ok(Recurse(RecurseOptions { tree: true,  max_depth: None })));
    test!(rec_tree:        DirAction <- ["--recurse", "--tree"];          Both => Ok(Recurse(RecurseOptions { tree: true,  max_depth: None })));
    test!(rec_short_tree:  DirAction <- ["-TR"];                          Both => Ok(Recurse(RecurseOptions { tree: true,  max_depth: None })));

    // Overriding --list-dirs, --recurse, and --tree
    test!(dirs_recurse:    DirAction <- ["--list-dirs", "--recurse"];     Last => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));
    test!(dirs_tree:       DirAction <- ["--list-dirs", "--tree"];        Last => Ok(Recurse(RecurseOptions { tree: true,  max_depth: None })));
    test!(just_level:      DirAction <- ["--level=4"];                    Last => Ok(DirAction::List));

    test!(dirs_recurse_2:  DirAction <- ["--list-dirs", "--recurse"]; Complain => Err(OptionsError::Conflict(&flags::RECURSE, &flags::LIST_DIRS)));
    test!(dirs_tree_2:     DirAction <- ["--list-dirs", "--tree"];    Complain => Err(OptionsError::Conflict(&flags::TREE,    &flags::LIST_DIRS)));
    test!(just_level_2:    DirAction <- ["--level=4"];                Complain => Err(OptionsError::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE)));


    // Overriding levels
    test!(overriding_1:    DirAction <- ["-RL=6", "-L=7"];                Last => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(7) })));
    test!(overriding_2:    DirAction <- ["-RL=6", "-L=7"];            Complain => Err(OptionsError::Duplicate(Flag::Short(b'L'), Flag::Short(b'L'))));
}


================================================
FILE: src/options/error.rs
================================================
use std::ffi::OsString;
use std::fmt;
use std::num::ParseIntError;

use crate::options::flags;
use crate::options::parser::{Arg, Flag, ParseError};


/// Something wrong with the combination of options the user has picked.
#[derive(PartialEq, Eq, Debug)]
pub enum OptionsError {

    /// There was an error (from `getopts`) parsing the arguments.
    Parse(ParseError),

    /// The user supplied an illegal choice to an Argument.
    BadArgument(&'static Arg, OsString),

    /// The user supplied a set of options that are unsupported
    Unsupported(String),

    /// An option was given twice or more in strict mode.
    Duplicate(Flag, Flag),

    /// Two options were given that conflict with one another.
    Conflict(&'static Arg, &'static Arg),

    /// An option was given that does nothing when another one either is or
    /// isn’t present.
    Useless(&'static Arg, bool, &'static Arg),

    /// An option was given that does nothing when either of two other options
    /// are not present.
    Useless2(&'static Arg, &'static Arg, &'static Arg),

    /// A very specific edge case where --tree can’t be used with --all twice.
    TreeAllAll,

    /// A numeric option was given that failed to be parsed as a number.
    FailedParse(String, NumberSource, ParseIntError),

    /// A glob ignore was given that failed to be parsed as a pattern.
    FailedGlobPattern(String),
}

/// The source of a string that failed to be parsed as a number.
#[derive(PartialEq, Eq, Debug)]
pub enum NumberSource {

    /// It came... from a command-line argument!
    Arg(&'static Arg),

    /// It came... from the enviroment!
    Env(&'static str),
}

impl From<glob::PatternError> for OptionsError {
    fn from(error: glob::PatternError) -> Self {
        Self::FailedGlobPattern(error.to_string())
    }
}

impl fmt::Display for NumberSource {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Arg(arg) => write!(f, "option {}", arg),
            Self::Env(env) => write!(f, "environment variable {}", env),
        }
    }
}

impl fmt::Display for OptionsError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use crate::options::parser::TakesValue;

        match self {
            Self::BadArgument(arg, attempt) => {
                if let TakesValue::Necessary(Some(values)) = arg.takes_value {
                    write!(f, "Option {} has no {:?} setting ({})", arg, attempt, Choices(values))
                }
                else {
                    write!(f, "Option {} has no {:?} setting", arg, attempt)
                }
            }
            Self::Parse(e)                   => write!(f, "{}", e),
            Self::Unsupported(e)             => write!(f, "{}", e),
            Self::Conflict(a, b)             => write!(f, "Option {} conflicts with option {}", a, b),
            Self::Duplicate(a, b) if a == b  => write!(f, "Flag {} was given twice", a),
            Self::Duplicate(a, b)            => write!(f, "Flag {} conflicts with flag {}", a, b),
            Self::Useless(a, false, b)       => write!(f, "Option {} is useless without option {}", a, b),
            Self::Useless(a, true, b)        => write!(f, "Option {} is useless given option {}", a, b),
            Self::Useless2(a, b1, b2)        => write!(f, "Option {} is useless without options {} or {}", a, b1, b2),
            Self::TreeAllAll                 => write!(f, "Option --tree is useless given --all --all"),
            Self::FailedParse(s, n, e)       => write!(f, "Value {:?} not valid for {}: {}", s, n, e),
            Self::FailedGlobPattern(ref e)   => write!(f, "Failed to parse glob pattern: {}", e),
        }
    }
}

impl OptionsError {

    /// Try to second-guess what the user was trying to do, depending on what
    /// went wrong.
    pub fn suggestion(&self) -> Option<&'static str> {
        // ‘ls -lt’ and ‘ls -ltr’ are common combinations
        match self {
            Self::BadArgument(time, r) if *time == &flags::TIME && r == "r" => {
                Some("To sort oldest files last, try \"--sort oldest\", or just \"-sold\"")
            }
            Self::Parse(ParseError::NeedsValue { ref flag, .. }) if *flag == Flag::Short(b't') => {
                Some("To sort newest files last, try \"--sort newest\", or just \"-snew\"")
            }
            _ => {
                None
            }
        }
    }
}


/// A list of legal choices for an argument-taking option.
#[derive(PartialEq, Eq, Debug)]
pub struct Choices(pub &'static [&'static str]);

impl fmt::Display for Choices {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "choices: {}", self.0.join(", "))
    }
}


================================================
FILE: src/options/file_name.rs
================================================
use crate::options::{flags, OptionsError, NumberSource};
use crate::options::parser::MatchedFlags;
use crate::options::vars::{self, Vars};

use crate::output::file_name::{Options, Classify, ShowIcons};


impl Options {
    pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
        let classify = Classify::deduce(matches)?;
        let show_icons = ShowIcons::deduce(matches, vars)?;

        Ok(Self { classify, show_icons })
    }
}

impl Classify {
    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
        let flagged = matches.has(&flags::CLASSIFY)?;

        if flagged { Ok(Self::AddFileIndicators) }
              else { Ok(Self::JustFilenames) }
    }
}

impl ShowIcons {
    pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
        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()) {
            match columns.parse() {
                Ok(width) => {
                    Ok(Self::On(width))
                }
                Err(e) => {
                    let source = NumberSource::Env(vars::EXA_ICON_SPACING);
                    Err(OptionsError::FailedParse(columns, source, e))
                }
            }
        }
        else {
            Ok(Self::On(1))
        }
    }
}


================================================
FILE: src/options/filter.rs
================================================
//! Parsing the options for `FileFilter`.

use crate::fs::DotFilter;
use crate::fs::filter::{FileFilter, SortField, SortCase, IgnorePatterns, GitIgnore};

use crate::options::{flags, OptionsError};
use crate::options::parser::MatchedFlags;


impl FileFilter {

    /// Determines which of all the file filter options to use.
    pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
        Ok(Self {
            list_dirs_first:  matches.has(&flags::DIRS_FIRST)?,
            reverse:          matches.has(&flags::REVERSE)?,
            only_dirs:        matches.has(&flags::ONLY_DIRS)?,
            sort_field:       SortField::deduce(matches)?,
            dot_filter:       DotFilter::deduce(matches)?,
            ignore_patterns:  IgnorePatterns::deduce(matches)?,
            git_ignore:       GitIgnore::deduce(matches)?,
        })
    }
}

impl SortField {

    /// Determines which sort field to use based on the `--sort` argument.
    /// This argument’s value can be one of several flags, listed above.
    /// Returns the default sort field if none is given, or `Err` if the
    /// value doesn’t correspond to a sort field we know about.
    fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
        let word = match matches.get(&flags::SORT)? {
            Some(w)  => w,
            None     => return Ok(Self::default()),
        };

        // Get String because we can’t match an OsStr
        let word = match word.to_str() {
            Some(w)  => w,
            None     => return Err(OptionsError::BadArgument(&flags::SORT, word.into()))
        };

        let field = match word {
            "name" | "filename" => {
                Self::Name(SortCase::AaBbCc)
            }
            "Name" | "Filename" => {
                Self::Name(SortCase::ABCabc)
            }
            ".name" | ".filename" => {
                Self::NameMixHidden(SortCase::AaBbCc)
            }
            ".Name" | ".Filename" => {
                Self::NameMixHidden(SortCase::ABCabc)
            }
            "size" | "filesize" => {
                Self::Size
            }
            "ext" | "extension" => {
                Self::Extension(SortCase::AaBbCc)
            }
            "Ext" | "Extension" => {
                Self::Extension(SortCase::ABCabc)
            }

            // “new” sorts oldest at the top and newest at the bottom; “old”
            // sorts newest at the top and oldest at the bottom. I think this
            // is the right way round to do this: “size” puts the smallest at
            // the top and the largest at the bottom, doesn’t it?
            "date" | "time" | "mod" | "modified" | "new" | "newest" => {
                Self::ModifiedDate
            }

            // Similarly, “age” means that files with the least age (the
            // newest files) get sorted at the top, and files with the most
            // age (the oldest) at the bottom.
            "age" | "old" | "oldest" => {
                Self::ModifiedAge
            }

            "ch" | "changed" => {
                Self::ChangedDate
            }
            "acc" | "accessed" => {
                Self::AccessedDate
            }
            "cr" | "created" => {
                Self::CreatedDate
            }
            #[cfg(unix)]
            "inode" => {
                Self::FileInode
            }
            "type" => {
                Self::FileType
            }
            "none" => {
                Self::Unsorted
            }
            _ => {
                return Err(OptionsError::BadArgument(&flags::SORT, word.into()));
            }
        };

        Ok(field)
    }
}


// I’ve gone back and forth between whether to sort case-sensitively or
// insensitively by default. The default string sort in most programming
// languages takes each character’s ASCII value into account, sorting
// “Documents” before “apps”, but there’s usually an option to ignore
// characters’ case, putting “apps” before “Documents”.
//
// The argument for following case is that it’s easy to forget whether an item
// begins with an uppercase or lowercase letter and end up having to scan both
// the uppercase and lowercase sub-lists to find the item you want. If you
// happen to pick the sublist it’s not in, it looks like it’s missing, which
// is worse than if you just take longer to find it.
// (https://ux.stackexchange.com/a/79266)
//
// The argument for ignoring case is that it makes exa sort files differently
// from shells. A user would expect a directory’s files to be in the same
// order if they used “exa ~/directory” or “exa ~/directory/*”, but exa sorts
// them in the first case, and the shell in the second case, so they wouldn’t
// be exactly the same if exa does something non-conventional.
//
// However, exa already sorts files differently: it uses natural sorting from
// the natord crate, sorting the string “2” before “10” because the number’s
// smaller, because that’s usually what the user expects to happen. Users will
// name their files with numbers expecting them to be treated like numbers,
// rather than lists of numeric characters.
//
// In the same way, users will name their files with letters expecting the
// order of the letters to matter, rather than each letter’s character’s ASCII
// value. So exa breaks from tradition and ignores case while sorting:
// “apps” first, then “Documents”.
//
// You can get the old behaviour back by sorting with `--sort=Name`.
impl Default for SortField {
    fn default() -> Self {
        Self::Name(SortCase::AaBbCc)
    }
}


impl DotFilter {

    /// Determines the dot filter based on how many `--all` options were
    /// given: one will show dotfiles, but two will show `.` and `..` too.
    ///
    /// It also checks for the `--tree` option in strict mode, because of a
    /// special case where `--tree --all --all` won’t work: listing the
    /// parent directory in tree mode would loop onto itself!
    pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
        let count = matches.count(&flags::ALL);

        if count == 0 {
            Ok(Self::JustFiles)
        }
        else if count == 1 {
            Ok(Self::Dotfiles)
        }
        else if matches.count(&flags::TREE) > 0 {
            Err(OptionsError::TreeAllAll)
        }
        else if count >= 3 && matches.is_strict() {
            Err(OptionsError::Conflict(&flags::ALL, &flags::ALL))
        }
        else {
            Ok(Self::DotfilesAndDots)
        }
    }
}


impl IgnorePatterns {

    /// Determines the set of glob patterns to use based on the
    /// `--ignore-glob` argument’s value. This is a list of strings
    /// separated by pipe (`|`) characters, given in any order.
    pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {

        // If there are no inputs, we return a set of patterns that doesn’t
        // match anything, rather than, say, `None`.
        let inputs = match matches.get(&flags::IGNORE_GLOB)? {
            Some(is)  => is,
            None      => return Ok(Self::empty()
Download .txt
gitextract_pkcv6wm8/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   └── bug_report.md
│   └── workflows/
│       └── unit-tests.yml
├── .gitignore
├── .rustfmt.toml
├── Cargo.toml
├── Justfile
├── LICENCE
├── README.md
├── Vagrantfile
├── build.rs
├── completions/
│   ├── bash/
│   │   └── exa
│   ├── fish/
│   │   └── exa.fish
│   └── zsh/
│       └── _exa
├── devtools/
│   ├── README.md
│   ├── dev-bash.sh
│   ├── dev-create-test-filesystem.sh
│   ├── dev-fixtures.sh
│   ├── dev-help.sh
│   ├── dev-package-for-linux.sh
│   ├── dev-run-debug.sh
│   ├── dev-run-release.sh
│   ├── dev-set-up-environment.sh
│   ├── dev-versions.sh
│   └── local-package-for-macos.sh
├── man/
│   ├── exa.1.md
│   └── exa_colors.5.md
├── rust-toolchain.toml
├── snap/
│   ├── .gitignore
│   └── snapcraft.yaml
├── src/
│   ├── fs/
│   │   ├── dir.rs
│   │   ├── dir_action.rs
│   │   ├── feature/
│   │   │   ├── git.rs
│   │   │   ├── mod.rs
│   │   │   └── xattr.rs
│   │   ├── fields.rs
│   │   ├── file.rs
│   │   ├── filter.rs
│   │   └── mod.rs
│   ├── info/
│   │   ├── filetype.rs
│   │   ├── mod.rs
│   │   └── sources.rs
│   ├── logger.rs
│   ├── main.rs
│   ├── options/
│   │   ├── dir_action.rs
│   │   ├── error.rs
│   │   ├── file_name.rs
│   │   ├── filter.rs
│   │   ├── flags.rs
│   │   ├── help.rs
│   │   ├── mod.rs
│   │   ├── parser.rs
│   │   ├── theme.rs
│   │   ├── vars.rs
│   │   ├── version.rs
│   │   └── view.rs
│   ├── output/
│   │   ├── cell.rs
│   │   ├── details.rs
│   │   ├── escape.rs
│   │   ├── file_name.rs
│   │   ├── grid.rs
│   │   ├── grid_details.rs
│   │   ├── icons.rs
│   │   ├── lines.rs
│   │   ├── mod.rs
│   │   ├── render/
│   │   │   ├── blocks.rs
│   │   │   ├── filetype.rs
│   │   │   ├── git.rs
│   │   │   ├── groups.rs
│   │   │   ├── inode.rs
│   │   │   ├── links.rs
│   │   │   ├── mod.rs
│   │   │   ├── octal.rs
│   │   │   ├── permissions.rs
│   │   │   ├── size.rs
│   │   │   ├── times.rs
│   │   │   └── users.rs
│   │   ├── table.rs
│   │   ├── time.rs
│   │   └── tree.rs
│   └── theme/
│       ├── default_theme.rs
│       ├── lsc.rs
│       ├── mod.rs
│       └── ui_styles.rs
└── xtests/
    ├── README.md
    ├── attributes.toml
    ├── colour-term.toml
    ├── debug-logging.toml
    ├── details-view-dates.toml
    ├── details-view-filesizes.toml
    ├── details-view-passwd.toml
    ├── details-view-permissions.toml
    ├── details-view.toml
    ├── dotfiles.toml
    ├── errors.toml
    ├── features/
    │   ├── none.toml
    │   └── outputs/
    │       └── disabled_git.txt
    ├── git-ignore.toml
    ├── git.toml
    ├── grid-details-view.toml
    ├── grid-view.toml
    ├── help.toml
    ├── icons.toml
    ├── ignore-glob.toml
    ├── input-options.toml
    ├── lines-view.toml
    ├── outputs/
    │   ├── attributes_files_xattrs_tree.ansitxt
    │   ├── attributes_xattrs_long_tree.ansitxt
    │   ├── attributes_xattrs_tree.ansitxt
    │   ├── dates_long_currentyear_localefr.ansitxt
    │   ├── dates_long_localefr.ansitxt
    │   ├── dates_long_localejp.ansitxt
    │   ├── dates_long_time_accessed.ansitxt
    │   ├── dates_long_time_created.ansitxt
    │   ├── dates_long_time_modified.ansitxt
    │   ├── dates_long_timestyle_fulliso.ansitxt
    │   ├── dates_long_timestyle_iso.ansitxt
    │   ├── dates_long_timestyle_longiso.ansitxt
    │   ├── dev_long.ansitxt
    │   ├── dirs_grid.ansitxt
    │   ├── dirs_oneline.ansitxt
    │   ├── error_columns_invalid.ansitxt
    │   ├── error_columns_nines.ansitxt
    │   ├── error_grid_rows_invalid.ansitxt
    │   ├── error_grid_rows_nines.ansitxt
    │   ├── error_icon_spacing_invalid.ansitxt
    │   ├── error_icon_spacing_nines.ansitxt
    │   ├── error_invalid_option.ansitxt
    │   ├── error_level_invalid.ansitxt
    │   ├── error_level_nines.ansitxt
    │   ├── error_tree_all_all.ansitxt
    │   ├── exts_compressed_paths_themed.ansitxt
    │   ├── exts_compressed_paths_themed_reset.ansitxt
    │   ├── exts_grid_monochrome.ansitxt
    │   ├── exts_grid_sort_name_reverse.ansitxt
    │   ├── exts_oneline_icons.ansitxt
    │   ├── exts_oneline_sort_ext.ansitxt
    │   ├── exts_oneline_sort_extcase.ansitxt
    │   ├── exts_oneline_sort_name.ansitxt
    │   ├── exts_oneline_sort_name_reverse.ansitxt
    │   ├── exts_oneline_sort_namecase.ansitxt
    │   ├── exts_themed_reset.ansitxt
    │   ├── far_dates_long.ansitxt
    │   ├── files_grid_13col.ansitxt
    │   ├── files_grid_20col.ansitxt
    │   ├── files_grid_4col.ansitxt
    │   ├── files_grid_8col.ansitxt
    │   ├── files_grid_icons.ansitxt
    │   ├── files_grid_monochrome.ansitxt
    │   ├── files_long.ansitxt
    │   ├── files_long_binary.ansitxt
    │   ├── files_long_bytes.ansitxt
    │   ├── files_long_colourscale.ansitxt
    │   ├── files_long_colourscale_binary.ansitxt
    │   ├── files_long_colourscale_bytes.ansitxt
    │   ├── files_long_grid_1col.ansitxt
    │   ├── files_long_grid_2col.ansitxt
    │   ├── files_long_grid_3col.ansitxt
    │   ├── files_long_grid_4col.ansitxt
    │   ├── files_long_grid_exa_grid_rows_2_3files.ansitxt
    │   ├── files_long_grid_exa_grid_rows_5_15files.ansitxt
    │   ├── files_long_grid_exa_grid_rows_6_15files.ansitxt
    │   ├── files_long_grid_header_1file.ansitxt
    │   ├── files_long_grid_header_2files.ansitxt
    │   ├── files_long_grid_icons.ansitxt
    │   ├── files_long_header.ansitxt
    │   ├── files_long_header_binary.ansitxt
    │   ├── files_long_header_bytes.ansitxt
    │   ├── files_long_icons.ansitxt
    │   ├── files_long_monochrome.ansitxt
    │   ├── files_long_tree_icons.ansitxt
    │   ├── files_oneline_icons.ansitxt
    │   ├── files_paths_grid_3col.ansitxt
    │   ├── files_paths_grid_5col.ansitxt
    │   ├── files_paths_grid_7col.ansitxt
    │   ├── files_paths_long_grid_1col.ansitxt
    │   ├── files_paths_long_grid_2col.ansitxt
    │   ├── files_paths_long_grid_3col.ansitxt
    │   ├── files_tree_icons.ansitxt
    │   ├── git1+2_long.ansitxt
    │   ├── git1+2_long_directories.ansitxt
    │   ├── git1+2_long_nested.ansitxt
    │   ├── git1_long.ansitxt
    │   ├── git1_long_additions.ansitxt
    │   ├── git1_long_edits.ansitxt
    │   ├── git1_long_moves.ansitxt
    │   ├── git1_long_multiple.ansitxt
    │   ├── git1_long_recurse.ansitxt
    │   ├── git1_long_tree.ansitxt
    │   ├── git1_paths_long_grid.ansitxt
    │   ├── git2_ignoreds_grid_gitignore.ansitxt
    │   ├── git2_ignoreds_lines_gitignore.ansitxt
    │   ├── git2_ignoreds_long_gitignore.ansitxt
    │   ├── git2_ignoreds_long_grid_gitignore.ansitxt
    │   ├── git2_ignoreds_long_recurse_gitignore.ansitxt
    │   ├── git2_ignoreds_long_tree_gitignore.ansitxt
    │   ├── git2_ignoreds_tree_gitignore.ansitxt
    │   ├── git2_long.ansitxt
    │   ├── git2_long_ignoredcontent.ansitxt
    │   ├── git2_long_ignoreddir.ansitxt
    │   ├── git2_long_ignorednested.ansitxt
    │   ├── git2_long_multiple.ansitxt
    │   ├── git2_long_nested.ansitxt
    │   ├── git2_long_recurse.ansitxt
    │   ├── git2_long_recurse_gitignore.ansitxt
    │   ├── git2_long_tree.ansitxt
    │   ├── git2_long_tree_gitignore.ansitxt
    │   ├── git2_tree_gitignore.ansitxt
    │   ├── git3_long.ansitxt
    │   ├── git4_long.ansitxt
    │   ├── help.ansitxt
    │   ├── hiddens_grid.ansitxt
    │   ├── hiddens_grid_all.ansitxt
    │   ├── hiddens_grid_all_all.ansitxt
    │   ├── hiddens_long.ansitxt
    │   ├── hiddens_long_all.ansitxt
    │   ├── hiddens_long_all_all.ansitxt
    │   ├── links_grid.ansitxt
    │   ├── links_grid_monochrome.ansitxt
    │   ├── links_lines.ansitxt
    │   ├── links_long_classify.ansitxt
    │   ├── links_oneline_icons.ansitxt
    │   ├── links_oneline_icons_width0.ansitxt
    │   ├── links_oneline_icons_width2.ansitxt
    │   ├── links_oneline_icons_width3.ansitxt
    │   ├── links_oneline_sort_type.ansitxt
    │   ├── links_oneline_themed.ansitxt
    │   ├── links_paths_lines.ansitxt
    │   ├── links_tree.ansitxt
    │   ├── links_xattrs_tree.ansitxt
    │   ├── names_grid.ansitxt
    │   ├── names_grid_across.ansitxt
    │   ├── names_grid_recurse.ansitxt
    │   ├── names_lines.ansitxt
    │   ├── names_tree.ansitxt
    │   ├── passwd_long_group_header.ansitxt
    │   ├── permissions_long_group_header.ansitxt
    │   ├── permissions_long_group_header_sudo.ansitxt
    │   ├── permissions_long_themed.ansitxt
    │   ├── permissions_oneline_icons.ansitxt
    │   ├── proc_1_root.ansitxt
    │   ├── proc_1_root_xattrs.ansitxt
    │   ├── specials_long.ansitxt
    │   ├── specials_long_classify.ansitxt
    │   ├── specials_oneline_sort_type.ansitxt
    │   └── specials_oneline_themed.ansitxt
    ├── run.sh
    ├── sorting.toml
    ├── themes.toml
    └── tree-view.toml
Download .txt
SYMBOL INDEX (738 symbols across 52 files)

FILE: build.rs
  function main (line 22) | fn main() -> io::Result<()> {
  function strip_codes (line 51) | fn strip_codes(input: &str) -> String {
  function git_hash (line 58) | fn git_hash() -> String {
  function is_development_version (line 72) | fn is_development_version() -> bool {
  function is_debug_build (line 77) | fn is_debug_build() -> bool {
  function cargo_version (line 82) | fn cargo_version() -> String {
  function version_string (line 87) | fn version_string() -> String {
  function feature_enabled (line 99) | fn feature_enabled(name: &str) -> bool {
  function nonstandard_features_string (line 106) | fn nonstandard_features_string() -> String {
  function build_date (line 120) | fn build_date() -> String {

FILE: src/fs/dir.rs
  type Dir (line 19) | pub struct Dir {
    method read_dir (line 38) | pub fn read_dir(path: PathBuf) -> io::Result<Self> {
    method files (line 50) | pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, git: Option<&'ig ...
    method contains (line 62) | pub fn contains(&self, path: &Path) -> bool {
    method join (line 67) | pub fn join(&self, child: &Path) -> PathBuf {
  type Files (line 74) | pub struct Files<'dir, 'ig> {
  function parent (line 95) | fn parent(&self) -> PathBuf {
  function next_visible_file (line 106) | fn next_visible_file(&mut self) -> Option<Result<File<'dir>, (PathBuf, i...
  type DotsNext (line 139) | enum DotsNext {
  type Item (line 152) | type Item = Result<File<'dir>, (PathBuf, io::Error)>;
  method next (line 154) | fn next(&mut self) -> Option<Self::Item> {
  type DotFilter (line 180) | pub enum DotFilter {
    method shows_dotfiles (line 201) | fn shows_dotfiles(self) -> bool {
    method dots (line 210) | fn dots(self) -> DotsNext {
  method default (line 193) | fn default() -> Self {

FILE: src/fs/dir_action.rs
  type DirAction (line 23) | pub enum DirAction {
    method recurse_options (line 42) | pub fn recurse_options(self) -> Option<RecurseOptions> {
    method treat_dirs_as_files (line 50) | pub fn treat_dirs_as_files(self) -> bool {
  type RecurseOptions (line 62) | pub struct RecurseOptions {
    method is_too_deep (line 76) | pub fn is_too_deep(self, depth: usize) -> bool {

FILE: src/fs/feature/git.rs
  type GitCache (line 18) | pub struct GitCache {
    method has_anything_for (line 28) | pub fn has_anything_for(&self, index: &Path) -> bool {
    method get (line 32) | pub fn get(&self, index: &Path, prefix_lookup: bool) -> f::Git {
    method from_iter (line 42) | fn from_iter<I>(iter: I) -> Self
  type GitRepo (line 83) | pub struct GitRepo {
    method search (line 134) | fn search(&self, index: &Path, prefix_lookup: bool) -> f::Git {
    method has_workdir (line 152) | fn has_workdir(&self, path: &Path) -> bool {
    method has_path (line 157) | fn has_path(&self, path: &Path) -> bool {
    method discover (line 163) | fn discover(path: PathBuf) -> Result<Self, PathBuf> {
  type GitContents (line 104) | enum GitContents {
    method inner_repo (line 190) | fn inner_repo(self) -> git2::Repository {
  function repo_to_statuses (line 204) | fn repo_to_statuses(repo: &git2::Repository, workdir: &Path) -> Git {
  type Git (line 239) | struct Git {
    method status (line 248) | fn status(&self, index: &Path, prefix_lookup: bool) -> f::Git {
    method file_status (line 256) | fn file_status(&self, file: &Path) -> f::Git {
    method dir_status (line 277) | fn dir_status(&self, dir: &Path) -> f::Git {
  function reorient (line 300) | fn reorient(path: &Path) -> PathBuf {
  function reorient (line 313) | fn reorient(path: &Path) -> PathBuf {
  function working_tree_status (line 321) | fn working_tree_status(status: git2::Status) -> f::GitStatus {
  function index_status (line 336) | fn index_status(status: git2::Status) -> f::GitStatus {

FILE: src/fs/feature/mod.rs
  type GitCache (line 14) | pub struct GitCache;
    method from_iter (line 17) | fn from_iter<I>(_iter: I) -> Self
    method has_anything_for (line 25) | pub fn has_anything_for(&self, _index: &Path) -> bool {
    method get (line 29) | pub fn get(&self, _index: &Path, _prefix_lookup: bool) -> f::Git {

FILE: src/fs/feature/xattr.rs
  constant ENABLED (line 10) | pub const ENABLED: bool = cfg!(any(target_os = "macos", target_os = "lin...
  type FileAttributes (line 13) | pub trait FileAttributes {
    method attributes (line 14) | fn attributes(&self) -> io::Result<Vec<Attribute>>;
    method symlink_attributes (line 15) | fn symlink_attributes(&self) -> io::Result<Vec<Attribute>>;
    method attributes (line 20) | fn attributes(&self) -> io::Result<Vec<Attribute>> {
    method symlink_attributes (line 24) | fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {
    method attributes (line 31) | fn attributes(&self) -> io::Result<Vec<Attribute>> {
    method symlink_attributes (line 35) | fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {
  type FollowSymlinks (line 44) | pub enum FollowSymlinks {
  type Attribute (line 51) | pub struct Attribute {
  function list_attrs (line 58) | pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Ve...
  function listxattr (line 120) | fn listxattr(
  function getxattr (line 127) | fn getxattr(
  type Lister (line 137) | pub struct Lister {
    method new (line 142) | pub fn new(do_follow: FollowSymlinks) -> Self {
    method translate_attribute_name (line 151) | pub fn translate_attribute_name(&self, input: &[u8]) -> String {
    method listxattr_first (line 155) | pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
    method listxattr_second (line 166) | pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bu...
    method getxattr (line 177) | pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
    method new (line 233) | pub fn new(follow_symlinks: FollowSymlinks) -> Lister {
    method translate_attribute_name (line 237) | pub fn translate_attribute_name(&self, input: &[u8]) -> String {
    method listxattr_first (line 241) | pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
    method listxattr_second (line 256) | pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bu...
    method getxattr (line 271) | pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
  function listxattr (line 201) | fn listxattr(
  function llistxattr (line 207) | fn llistxattr(
  function getxattr (line 213) | fn getxattr(
  function lgetxattr (line 220) | fn lgetxattr(
  type Lister (line 228) | pub struct Lister {
    method new (line 142) | pub fn new(do_follow: FollowSymlinks) -> Self {
    method translate_attribute_name (line 151) | pub fn translate_attribute_name(&self, input: &[u8]) -> String {
    method listxattr_first (line 155) | pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
    method listxattr_second (line 166) | pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bu...
    method getxattr (line 177) | pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
    method new (line 233) | pub fn new(follow_symlinks: FollowSymlinks) -> Lister {
    method translate_attribute_name (line 237) | pub fn translate_attribute_name(&self, input: &[u8]) -> String {
    method listxattr_first (line 241) | pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
    method listxattr_second (line 256) | pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bu...
    method getxattr (line 271) | pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {

FILE: src/fs/fields.rs
  type blkcnt_t (line 20) | pub type blkcnt_t = u64;
  type gid_t (line 23) | pub type gid_t = u32;
  type ino_t (line 26) | pub type ino_t = u64;
  type nlink_t (line 29) | pub type nlink_t = u64;
  type time_t (line 32) | pub type time_t = i64;
  type uid_t (line 35) | pub type uid_t = u32;
  type Type (line 47) | pub enum Type {
    method is_regular_file (line 59) | pub fn is_regular_file(self) -> bool {
  type Permissions (line 67) | pub struct Permissions {
  type Attributes (line 87) | pub struct Attributes {
  type PermissionsPlus (line 100) | pub struct PermissionsPlus {
  type OctalPermissions (line 112) | pub struct OctalPermissions {
  type Links (line 123) | pub struct Links {
  type Inode (line 137) | pub struct Inode(pub ino_t);
  type Blocks (line 142) | pub enum Blocks {
  type User (line 155) | pub struct User(pub uid_t);
  type Group (line 159) | pub struct Group(pub gid_t);
  type Size (line 165) | pub enum Size {
  type DeviceIDs (line 196) | pub struct DeviceIDs {
  type Time (line 204) | pub struct Time {
  type GitStatus (line 214) | pub enum GitStatus {
  type Git (line 247) | pub struct Git {
  method default (line 255) | fn default() -> Self {

FILE: src/fs/file.rs
  type File (line 24) | pub struct File<'dir> {
  function from_args (line 72) | pub fn from_args<PD, FN>(path: PathBuf, parent_dir: PD, filename: FN) ->...
  function new_aa_current (line 87) | pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
  function new_aa_parent (line 99) | pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> io::Result...
  function filename (line 113) | pub fn filename(path: &Path) -> String {
  function ext (line 132) | fn ext(path: &Path) -> Option<String> {
  function is_directory (line 141) | pub fn is_directory(&self) -> bool {
  function points_to_directory (line 146) | pub fn points_to_directory(&self) -> bool {
  function to_dir (line 167) | pub fn to_dir(&self) -> io::Result<Dir> {
  function is_file (line 173) | pub fn is_file(&self) -> bool {
  function is_executable_file (line 181) | pub fn is_executable_file(&self) -> bool {
  function is_link (line 187) | pub fn is_link(&self) -> bool {
  function is_pipe (line 193) | pub fn is_pipe(&self) -> bool {
  function is_char_device (line 199) | pub fn is_char_device(&self) -> bool {
  function is_block_device (line 205) | pub fn is_block_device(&self) -> bool {
  function is_socket (line 211) | pub fn is_socket(&self) -> bool {
  function reorient_target_path (line 219) | fn reorient_target_path(&self, path: &Path) -> PathBuf {
  function link_target (line 244) | pub fn link_target(&self) -> FileTarget<'dir> {
  function links (line 282) | pub fn links(&self) -> f::Links {
  function inode (line 293) | pub fn inode(&self) -> f::Inode {
  function blocks (line 301) | pub fn blocks(&self) -> f::Blocks {
  function user (line 312) | pub fn user(&self) -> f::User {
  function group (line 318) | pub fn group(&self) -> f::Group {
  function size (line 331) | pub fn size(&self) -> f::Size {
  function size (line 353) | pub fn size(&self) -> f::Size {
  function modified_time (line 363) | pub fn modified_time(&self) -> Option<SystemTime> {
  function changed_time (line 369) | pub fn changed_time(&self) -> Option<SystemTime> {
  function changed_time (line 388) | pub fn changed_time(&self) -> Option<SystemTime> {
  function accessed_time (line 393) | pub fn accessed_time(&self) -> Option<SystemTime> {
  function created_time (line 398) | pub fn created_time(&self) -> Option<SystemTime> {
  function type_char (line 408) | pub fn type_char(&self) -> f::Type {
  function type_char (line 436) | pub fn type_char(&self) -> f::Type {
  function permissions (line 450) | pub fn permissions(&self) -> f::Permissions {
  function attributes (line 474) | pub fn attributes(&self) -> f::Attributes {
  function extension_is_one_of (line 492) | pub fn extension_is_one_of(&self, choices: &[&str]) -> bool {
  function name_is_one_of (line 501) | pub fn name_is_one_of(&self, choices: &[&str]) -> bool {
  function as_ref (line 508) | fn as_ref(&self) -> &File<'a> {
  type FileTarget (line 515) | pub enum FileTarget<'dir> {
  function is_broken (line 538) | pub fn is_broken(&self) -> bool {
  type Mode (line 551) | pub type Mode = u32;
  constant USER_READ (line 553) | pub const USER_READ: Mode     = libc::S_IRUSR as Mode;
  constant USER_WRITE (line 554) | pub const USER_WRITE: Mode    = libc::S_IWUSR as Mode;
  constant USER_EXECUTE (line 555) | pub const USER_EXECUTE: Mode  = libc::S_IXUSR as Mode;
  constant GROUP_READ (line 557) | pub const GROUP_READ: Mode    = libc::S_IRGRP as Mode;
  constant GROUP_WRITE (line 558) | pub const GROUP_WRITE: Mode   = libc::S_IWGRP as Mode;
  constant GROUP_EXECUTE (line 559) | pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode;
  constant OTHER_READ (line 561) | pub const OTHER_READ: Mode    = libc::S_IROTH as Mode;
  constant OTHER_WRITE (line 562) | pub const OTHER_WRITE: Mode   = libc::S_IWOTH as Mode;
  constant OTHER_EXECUTE (line 563) | pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode;
  constant STICKY (line 565) | pub const STICKY: Mode        = libc::S_ISVTX as Mode;
  constant SETGID (line 566) | pub const SETGID: Mode        = libc::S_ISGID as Mode;
  constant SETUID (line 567) | pub const SETUID: Mode        = libc::S_ISUID as Mode;
  function extension (line 577) | fn extension() {
  function dotfile (line 582) | fn dotfile() {
  function no_extension (line 587) | fn no_extension() {
  function file (line 599) | fn file() {
  function no_path (line 604) | fn no_path() {
  function here (line 609) | fn here() {
  function there (line 614) | fn there() {
  function everywhere (line 619) | fn everywhere() {
  function topmost (line 625) | fn topmost() {

FILE: src/fs/filter.rs
  type FileFilter (line 27) | pub struct FileFilter {
    method filter_child_files (line 68) | pub fn filter_child_files(&self, files: &mut Vec<File<'_>>) {
    method filter_argument_files (line 85) | pub fn filter_argument_files(&self, files: &mut Vec<File<'_>>) {
    method sort_files (line 92) | pub fn sort_files<'a, F>(&self, files: &mut [F])
  type SortField (line 117) | pub enum SortField {
    method compare_files (line 218) | pub fn compare_files(self, a: &File<'_>, b: &File<'_>) -> Ordering {
    method strip_dot (line 262) | fn strip_dot(n: &str) -> &str {
  type SortCase (line 198) | pub enum SortCase {
  type IgnorePatterns (line 275) | pub struct IgnorePatterns {
    method from_iter (line 281) | fn from_iter<I>(iter: I) -> Self
    method parse_from_iter (line 294) | pub fn parse_from_iter<'a, I: IntoIterator<Item = &'a str>>(iter: I) -...
    method empty (line 318) | pub fn empty() -> Self {
    method is_ignored (line 323) | fn is_ignored(&self, file: &str) -> bool {
  type GitIgnore (line 331) | pub enum GitIgnore {
  function empty_matches_nothing (line 347) | fn empty_matches_nothing() {
  function ignores_a_glob (line 354) | fn ignores_a_glob() {
  function ignores_an_exact_filename (line 362) | fn ignores_an_exact_filename() {
  function ignores_both (line 370) | fn ignores_both() {

FILE: src/info/filetype.rs
  type FileExtensions (line 15) | pub struct FileExtensions;
    method is_immediate (line 23) | fn is_immediate(&self, file: &File<'_>) -> bool {
    method is_image (line 36) | fn is_image(&self, file: &File<'_>) -> bool {
    method is_video (line 46) | fn is_video(&self, file: &File<'_>) -> bool {
    method is_music (line 53) | fn is_music(&self, file: &File<'_>) -> bool {
    method is_lossless (line 60) | fn is_lossless(&self, file: &File<'_>) -> bool {
    method is_crypto (line 66) | fn is_crypto(&self, file: &File<'_>) -> bool {
    method is_document (line 72) | fn is_document(&self, file: &File<'_>) -> bool {
    method is_compressed (line 80) | fn is_compressed(&self, file: &File<'_>) -> bool {
    method is_temp (line 88) | fn is_temp(&self, file: &File<'_>) -> bool {
    method is_compiled (line 94) | fn is_compiled(&self, file: &File<'_>) -> bool {
  method colour_file (line 108) | fn colour_file(&self, file: &File<'_>) -> Option<Style> {
  method icon_file (line 128) | fn icon_file(&self, file: &File<'_>) -> Option<char> {

FILE: src/info/sources.rs
  function get_source_files (line 16) | pub fn get_source_files(&self) -> Vec<PathBuf> {

FILE: src/logger.rs
  function configure (line 10) | pub fn configure<T: AsRef<OsStr>>(ev: Option<T>) {
  type Logger (line 36) | struct Logger;
    method enabled (line 41) | fn enabled(&self, _: &log::Metadata<'_>) -> bool {
    method log (line 45) | fn log(&self, record: &log::Record<'_>) {
    method flush (line 53) | fn flush(&self) {
  constant GLOBAL_LOGGER (line 38) | const GLOBAL_LOGGER: &Logger = &Logger;
  function level (line 58) | fn level(level: log::Level) -> ANSIString<'static> {

FILE: src/main.rs
  function main (line 49) | fn main() {
  type Exa (line 120) | pub struct Exa<'args> {
  type LiveVars (line 150) | struct LiveVars;
  method get (line 152) | fn get(&self, name: &'static str) -> Option<OsString> {
  function git_options (line 159) | fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
  function run (line 172) | pub fn run(mut self) -> io::Result<i32> {
  function print_dirs (line 213) | fn print_dirs(&mut self, dir_files: Vec<Dir>, mut first: bool, is_only_d...
  function print_files (line 271) | fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File<'_>>) -> io...
  constant SUCCESS (line 334) | pub const SUCCESS: i32 = 0;
  constant RUNTIME_ERROR (line 337) | pub const RUNTIME_ERROR: i32 = 1;
  constant OPTIONS_ERROR (line 340) | pub const OPTIONS_ERROR: i32 = 3;

FILE: src/options/dir_action.rs
  method deduce (line 15) | pub fn deduce(matches: &MatchedFlags<'_>, can_tree: bool) -> Result<Self...
  method deduce (line 57) | pub fn deduce(matches: &MatchedFlags<'_>, tree: bool) -> Result<Self, Op...

FILE: src/options/error.rs
  type OptionsError (line 11) | pub enum OptionsError {
    method from (line 58) | fn from(error: glob::PatternError) -> Self {
    method fmt (line 73) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    method suggestion (line 104) | pub fn suggestion(&self) -> Option<&'static str> {
  type NumberSource (line 48) | pub enum NumberSource {
    method fmt (line 64) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  type Choices (line 123) | pub struct Choices(pub &'static [&'static str]);
    method fmt (line 126) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

FILE: src/options/file_name.rs
  method deduce (line 9) | pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<S...
  method deduce (line 18) | fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
  method deduce (line 27) | pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<S...

FILE: src/options/filter.rs
  method deduce (line 13) | pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
  method deduce (line 32) | fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
  method default (line 143) | fn default() -> Self {
  method deduce (line 157) | pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
  method deduce (line 184) | pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
  method deduce (line 208) | pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
  function pat (line 299) | fn pat(string: &'static str) -> glob::Pattern {

FILE: src/options/flags.rs
  constant COLOURS (line 19) | const COLOURS: &[&str] = &["always", "auto", "never"];
  constant SORTS (line 34) | const SORTS: Values = &[ "name", "Name", "size", "extension",
  constant TIMES (line 54) | const TIMES: Values = &["modified", "changed", "accessed", "created"];
  constant TIME_STYLES (line 55) | const TIME_STYLES: Values = &["default", "long-iso", "full-iso", "iso"];

FILE: src/options/help.rs
  type HelpString (line 73) | pub struct HelpString;
    method deduce (line 84) | pub fn deduce(matches: &MatchedFlags<'_>) -> Option<Self> {
    method fmt (line 98) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
  function help (line 126) | fn help() {
  function help_with_file (line 133) | fn help_with_file() {
  function unhelpful (line 140) | fn unhelpful() {

FILE: src/options/mod.rs
  type Options (line 105) | pub struct Options {
    method parse (line 130) | pub fn parse<'args, I, V>(args: I, vars: &V) -> OptionsResult<'args>
    method should_scan_for_git (line 164) | pub fn should_scan_for_git(&self) -> bool {
    method deduce (line 178) | fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Sel...
  type OptionsResult (line 198) | pub enum OptionsResult<'args> {
  type Strictnesses (line 220) | pub enum Strictnesses {
  function parse_for_test (line 232) | pub fn parse_for_test<T, F>(inputs: &[&str], args: &'static [&'static Ar...

FILE: src/options/parser.rs
  type ShortArg (line 38) | pub type ShortArg = u8;
  type LongArg (line 44) | pub type LongArg = &'static str;
  type Values (line 51) | pub type Values = &'static [&'static str];
  type Flag (line 56) | pub enum Flag {
    method matches (line 62) | pub fn matches(&self, arg: &Arg) -> bool {
    method fmt (line 71) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
  type Strictness (line 81) | pub enum Strictness {
  type TakesValue (line 95) | pub enum TakesValue {
  type Arg (line 112) | pub struct Arg {
    method fmt (line 126) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
  type Args (line 140) | pub struct Args(pub &'static [&'static Arg]);
    method parse (line 146) | pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Re...
    method lookup_short (line 326) | fn lookup_short(&self, short: ShortArg) -> Result<&Arg, ParseError> {
    method lookup_long (line 333) | fn lookup_long<'b>(&self, long: &'b OsStr) -> Result<&Arg, ParseError> {
  type Matches (line 344) | pub struct Matches<'args> {
  type MatchedFlags (line 355) | pub struct MatchedFlags<'args> {
  function has (line 374) | pub fn has(&self, arg: &'static Arg) -> Result<bool, OptionsError> {
  function has_where (line 384) | pub fn has_where<P>(&self, predicate: P) -> Result<Option<&Flag>, Option...
  function has_where_any (line 403) | pub fn has_where_any<P>(&self, predicate: P) -> Option<&Flag>
  function get (line 417) | pub fn get(&self, arg: &'static Arg) -> Result<Option<&OsStr>, OptionsEr...
  function get_where (line 426) | pub fn get_where<P>(&self, predicate: P) -> Result<Option<&OsStr>, Optio...
  function count (line 449) | pub fn count(&self, arg: &Arg) -> usize {
  function is_strict (line 457) | pub fn is_strict(&self) -> bool {
  type ParseError (line 466) | pub enum ParseError {
    method fmt (line 485) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  function os_str_to_bytes (line 497) | fn os_str_to_bytes<'b>(s: &'b OsStr) ->  &'b [u8]{
  function bytes_to_os_str (line 504) | fn bytes_to_os_str<'b>(b:  &'b [u8]) ->  &'b OsStr{
  function os_str_to_bytes (line 511) | fn os_str_to_bytes<'b>(s: &'b OsStr) ->  &'b [u8]{
  function bytes_to_os_str (line 516) | fn bytes_to_os_str<'b>(b:  &'b [u8]) ->  &'b OsStr{
  function split_on_equals (line 524) | fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
  constant SUGGESTIONS (line 615) | const SUGGESTIONS: Values = &[ "example" ];
  function only_count (line 717) | fn only_count() {
  function rightmost_count (line 729) | fn rightmost_count() {
  function no_count (line 743) | fn no_count() {

FILE: src/options/theme.rs
  method deduce (line 7) | pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<S...
  method deduce (line 24) | fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self,...
  method deduce (line 52) | fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
  method deduce (line 64) | fn deduce<V: Vars>(vars: &V) -> Self {
  type MockVars (line 125) | struct MockVars {
    method empty (line 132) | fn empty() -> MockVars {
    method with_no_color (line 139) | fn with_no_color() -> MockVars {
  method get (line 150) | fn get(&self, name: &'static str) -> Option<OsString> {

FILE: src/options/vars.rs
  type Vars (line 53) | pub trait Vars {
    method get (line 54) | fn get(&self, name: &'static str) -> Option<OsString>;
    method get (line 61) | fn get(&self, _name: &'static str) -> Option<OsString> {

FILE: src/options/version.rs
  type VersionString (line 12) | pub struct VersionString;
    method deduce (line 22) | pub fn deduce(matches: &MatchedFlags<'_>) -> Option<Self> {
    method fmt (line 33) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
  function version (line 45) | fn version() {
  function version_with_file (line 52) | fn version_with_file() {

FILE: src/options/view.rs
  method deduce (line 12) | pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<S...
  method deduce (line 31) | pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<S...
  method strict_check_long_flags (line 79) | fn strict_check_long_flags(matches: &MatchedFlags<'_>) -> Result<(), Opt...
  function deduce (line 104) | fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
  function deduce_tree (line 115) | fn deduce_tree(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
  function deduce_long (line 125) | fn deduce_long<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<...
  method deduce (line 145) | fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {
  method deduce (line 167) | fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {
  method deduce (line 189) | fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self,...
  method deduce (line 200) | fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
  method deduce (line 229) | fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
  method deduce (line 244) | fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self,...
  method deduce (line 277) | fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
  method deduce (line 296) | fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {

FILE: src/output/cell.rs
  type TextCell (line 21) | pub struct TextCell {
    method paint (line 42) | pub fn paint(style: Style, text: String) -> Self {
    method paint_str (line 54) | pub fn paint_str(style: Style, text: &'static str) -> Self {
    method blank (line 69) | pub fn blank(style: Style) -> Self {
    method add_spaces (line 79) | pub fn add_spaces(&mut self, count: usize) {
    method push (line 87) | pub fn push(&mut self, string: ANSIString<'static>, extra_width: usize) {
    method append (line 93) | pub fn append(&mut self, other: Self) {
  type Target (line 31) | type Target = TextCellContents;
  method deref (line 33) | fn deref(&self) -> &Self::Target {
  type TextCellContents (line 134) | pub struct TextCellContents(Vec<ANSIString<'static>>);
    method from (line 137) | fn from(strings: Vec<ANSIString<'static>>) -> Self {
    method strings (line 158) | pub fn strings(&self) -> ANSIStrings<'_> {
    method width (line 164) | pub fn width(&self) -> DisplayWidth {
    method promote (line 172) | pub fn promote(self) -> TextCell {
  type Target (line 143) | type Target = [ANSIString<'static>];
  method deref (line 145) | fn deref(&self) -> &Self::Target {
  type DisplayWidth (line 195) | pub struct DisplayWidth(usize);
    method from (line 198) | fn from(input: &'a str) -> Self {
    method from (line 204) | fn from(width: usize) -> Self {
    type Output (line 232) | type Output = Self;
    method add (line 234) | fn add(self, rhs: usize) -> Self::Output {
  type Target (line 210) | type Target = usize;
  method deref (line 212) | fn deref(&self) -> &Self::Target {
  method deref_mut (line 218) | fn deref_mut(&mut self) -> &mut Self::Target {
  type Output (line 224) | type Output = Self;
  method add (line 226) | fn add(self, rhs: Self) -> Self::Output {
  method sum (line 240) | fn sum<I>(iter: I) -> Self
  function empty_string (line 253) | fn empty_string() {
  function test_string (line 259) | fn test_string() {
  function addition (line 265) | fn addition() {
  function addition_usize (line 272) | fn addition_usize() {

FILE: src/output/details.rs
  type Options (line 95) | pub struct Options {
  type Render (line 111) | pub struct Render<'a> {
  type Egg (line 133) | struct Egg<'a> {
  function as_ref (line 142) | fn as_ref(&self) -> &File<'a> {
  function render (line 149) | pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {
  function add_files_to_table (line 194) | fn add_files_to_table<'dir>(&self, pool: &mut Pool, table: &mut Option<T...
  function render_header (line 346) | pub fn render_header(&self, header: TableRow) -> Row {
  function render_error (line 354) | fn render_error(&self, error: &io::Error, tree: TreeParams, path: Option...
  function render_xattr (line 369) | fn render_xattr(&self, xattr: &Attribute, tree: TreeParams) -> Row {
  function render_file (line 374) | pub fn render_file(&self, cells: TableRow, name: TextCell, tree: TreePar...
  function iterate_with_table (line 378) | pub fn iterate_with_table(&'a self, table: Table<'a>, rows: Vec<Row>) ->...
  function iterate (line 388) | pub fn iterate(&'a self, rows: Vec<Row>) -> Iter {
  type Row (line 398) | pub struct Row {
  type TableIter (line 417) | pub struct TableIter<'a> {
  type Item (line 427) | type Item = TextCell;
  method next (line 429) | fn next(&mut self) -> Option<Self::Item> {
  type Iter (line 458) | pub struct Iter {
  type Item (line 465) | type Item = TextCell;
  method next (line 467) | fn next(&mut self) -> Option<Self::Item> {

FILE: src/output/escape.rs
  function escape (line 4) | pub fn escape(string: String, bits: &mut Vec<ANSIString<'_>>, good: Styl...

FILE: src/output/file_name.rs
  type Options (line 15) | pub struct Options {
    method for_file (line 28) | pub fn for_file<'a, 'dir, C>(self, file: &'a File<'dir>, colours: &'a ...
  type LinkStyle (line 43) | enum LinkStyle {
  type Classify (line 58) | pub enum Classify {
  method default (line 69) | fn default() -> Self {
  type ShowIcons (line 77) | pub enum ShowIcons {
  type FileName (line 90) | pub struct FileName<'a, 'dir, C> {
  function with_link_paths (line 111) | pub fn with_link_paths(mut self) -> Self {
  function paint (line 125) | pub fn paint(&self) -> TextCellContents {
  function add_parent_bits (line 225) | fn add_parent_bits(&self, bits: &mut Vec<ANSIString<'_>>, parent: &Path) {
  function classify_char (line 245) | fn classify_char(&self, file: &File<'_>) -> Option<&'static str> {
  function classify_char (line 267) | fn classify_char(&self, file: &File<'_>) -> Option<&'static str> {
  function coloured_file_name (line 289) | fn coloured_file_name<'unused>(&self) -> Vec<ANSIString<'unused>> {
  function style (line 307) | pub fn style(&self) -> Style {
  type Colours (line 337) | pub trait Colours: FiletypeColours {
    method symlink_path (line 341) | fn symlink_path(&self) -> Style;
    method normal_arrow (line 344) | fn normal_arrow(&self) -> Style;
    method broken_symlink (line 349) | fn broken_symlink(&self) -> Style;
    method broken_filename (line 352) | fn broken_filename(&self) -> Style;
    method control_char (line 355) | fn control_char(&self) -> Style;
    method broken_control_char (line 359) | fn broken_control_char(&self) -> Style;
    method executable_file (line 362) | fn executable_file(&self) -> Style;
    method colour_file (line 364) | fn colour_file(&self, file: &File<'_>) -> Style;
  function spaces (line 369) | fn spaces(width: u32) -> String {

FILE: src/output/grid.rs
  type Options (line 12) | pub struct Options {
    method direction (line 17) | pub fn direction(self) -> tg::Direction {
  type Render (line 24) | pub struct Render<'a> {
  function render (line 34) | pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {

FILE: src/output/grid_details.rs
  type Options (line 22) | pub struct Options {
    method to_details_options (line 29) | pub fn to_details_options(&self) -> &DetailsOptions {
  type RowThreshold (line 43) | pub enum RowThreshold {
  type Render (line 54) | pub struct Render<'a> {
  function details_for_column (line 101) | fn details_for_column(&self) -> DetailsRender<'a> {
  function give_up (line 119) | pub fn give_up(self) -> DetailsRender<'a> {
  function render (line 136) | pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {
  function find_fitting_grid (line 145) | pub fn find_fitting_grid(&mut self) -> Option<(grid::Grid, grid::Width)> {
  function make_table (line 198) | fn make_table(&mut self, options: &'a TableOptions, drender: &DetailsRen...
  function make_grid (line 217) | fn make_grid(&mut self, column_count: usize, options: &'a TableOptions, ...
  function divide_rounding_up (line 293) | fn divide_rounding_up(a: usize, b: usize) -> usize {
  function file_has_xattrs (line 304) | fn file_has_xattrs(file: &File<'_>) -> bool {

FILE: src/output/icons.rs
  type FileIcon (line 9) | pub trait FileIcon {
    method icon_file (line 10) | fn icon_file(&self, file: &File<'_>) -> Option<char>;
  type Icons (line 15) | pub enum Icons {
    method value (line 22) | pub fn value(self) -> char {
  function iconify_style (line 40) | pub fn iconify_style(style: Style) -> Style {
  function icon_for_file (line 96) | pub fn icon_for_file(file: &File<'_>) -> char {

FILE: src/output/lines.rs
  type Render (line 13) | pub struct Render<'a> {
  function render (line 21) | pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {
  function render_file (line 31) | fn render_file<'f>(&self, file: &'f File<'a>) -> TextCellContents {

FILE: src/output/mod.rs
  type View (line 21) | pub struct View {
  type Mode (line 31) | pub enum Mode {
  type TerminalWidth (line 41) | pub enum TerminalWidth {
    method actual_terminal_width (line 51) | pub fn actual_terminal_width(self) -> Option<usize> {

FILE: src/output/render/blocks.rs
  function render (line 8) | pub fn render<C: Colours>(&self, colours: &C) -> TextCell {
  type Colours (line 17) | pub trait Colours {
    method block_count (line 18) | fn block_count(&self) -> Style;
    method no_blocks (line 19) | fn no_blocks(&self) -> Style;
    method block_count (line 36) | fn block_count(&self) -> Style { Red.blink() }
    method no_blocks (line 37) | fn no_blocks(&self)   -> Style { Green.italic() }
  type TestColours (line 33) | struct TestColours;
  function blocklessness (line 42) | fn blocklessness() {
  function blockfulity (line 51) | fn blockfulity() {

FILE: src/output/render/filetype.rs
  function render (line 7) | pub fn render<C: Colours>(self, colours: &C) -> ANSIString<'static> {
  type Colours (line 22) | pub trait Colours {
    method normal (line 23) | fn normal(&self) -> Style;
    method directory (line 24) | fn directory(&self) -> Style;
    method pipe (line 25) | fn pipe(&self) -> Style;
    method symlink (line 26) | fn symlink(&self) -> Style;
    method block_device (line 27) | fn block_device(&self) -> Style;
    method char_device (line 28) | fn char_device(&self) -> Style;
    method socket (line 29) | fn socket(&self) -> Style;
    method special (line 30) | fn special(&self) -> Style;

FILE: src/output/render/git.rs
  function render (line 8) | pub fn render(self, colours: &dyn Colours) -> TextCell {
  function render (line 21) | fn render(self, colours: &dyn Colours) -> ANSIString<'static> {
  type Colours (line 36) | pub trait Colours {
    method not_modified (line 37) | fn not_modified(&self) -> Style;
    method new (line 39) | fn new(&self) -> Style;
    method modified (line 40) | fn modified(&self) -> Style;
    method deleted (line 41) | fn deleted(&self) -> Style;
    method renamed (line 42) | fn renamed(&self) -> Style;
    method type_change (line 43) | fn type_change(&self) -> Style;
    method ignored (line 44) | fn ignored(&self) -> Style;
    method conflicted (line 45) | fn conflicted(&self) -> Style;
    method not_modified (line 62) | fn not_modified(&self) -> Style { Fixed(90).normal() }
    method new (line 63) | fn new(&self)          -> Style { Fixed(91).normal() }
    method modified (line 64) | fn modified(&self)     -> Style { Fixed(92).normal() }
    method deleted (line 65) | fn deleted(&self)      -> Style { Fixed(93).normal() }
    method renamed (line 66) | fn renamed(&self)      -> Style { Fixed(94).normal() }
    method type_change (line 67) | fn type_change(&self)  -> Style { Fixed(95).normal() }
    method ignored (line 68) | fn ignored(&self)      -> Style { Fixed(96).normal() }
    method conflicted (line 69) | fn conflicted(&self)   -> Style { Fixed(97).normal() }
  type TestColours (line 59) | struct TestColours;
  function git_blank (line 74) | fn git_blank() {
  function git_new_changed (line 93) | fn git_new_changed() {

FILE: src/output/render/groups.rs
  function render (line 10) | pub fn render<C: Colours, U: Users+Groups>(self, colours: &C, users: &U,...
  type Colours (line 40) | pub trait Colours {
    method yours (line 41) | fn yours(&self) -> Style;
    method not_yours (line 42) | fn not_yours(&self) -> Style;
    method yours (line 64) | fn yours(&self)     -> Style { Fixed(80).normal() }
    method not_yours (line 65) | fn not_yours(&self) -> Style { Fixed(81).normal() }
  type TestColours (line 61) | struct TestColours;
  function named (line 70) | fn named() {
  function unnamed (line 84) | fn unnamed() {
  function primary (line 94) | fn primary() {
  function secondary (line 105) | fn secondary() {
  function overflow (line 118) | fn overflow() {

FILE: src/output/render/inode.rs
  function render (line 8) | pub fn render(self, style: Style) -> TextCell {
  function blocklessness (line 23) | fn blocklessness() {

FILE: src/output/render/links.rs
  function render (line 9) | pub fn render<C: Colours>(&self, colours: &C, numeric: &NumericLocale) -...
  type Colours (line 18) | pub trait Colours {
    method normal (line 19) | fn normal(&self) -> Style;
    method multi_link_file (line 20) | fn multi_link_file(&self) -> Style;
    method normal (line 38) | fn normal(&self)           -> Style { Blue.normal() }
    method multi_link_file (line 39) | fn multi_link_file(&self)  -> Style { Blue.on(Red) }
  type TestColours (line 35) | struct TestColours;
  function regular_file (line 44) | fn regular_file() {
  function regular_directory (line 59) | fn regular_directory() {
  function popular_file (line 74) | fn popular_file() {

FILE: src/output/render/octal.rs
  function bits_to_octal (line 8) | fn bits_to_octal(r: bool, w: bool, x: bool) -> u8 {
  function render (line 12) | pub fn render(&self, style: Style) -> TextCell {
  function normal_folder (line 33) | fn normal_folder() {
  function normal_file (line 47) | fn normal_file() {
  function secret_file (line 61) | fn secret_file() {
  function sticky1 (line 75) | fn sticky1() {
  function sticky2 (line 90) | fn sticky2() {
  function sticky3 (line 104) | fn sticky3() {

FILE: src/output/render/permissions.rs
  function render (line 10) | pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {
  function render (line 28) | pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {
  function render (line 41) | pub fn render<C: Colours>(&self, colours: &C, is_regular_file: bool) -> ...
  function user_execute_bit (line 61) | fn user_execute_bit<C: Colours>(&self, colours: &C, is_regular_file: boo...
  function group_execute_bit (line 72) | fn group_execute_bit<C: Colours>(&self, colours: &C) -> ANSIString<'stat...
  function other_execute_bit (line 81) | fn other_execute_bit<C: Colours>(&self, colours: &C) -> ANSIString<'stat...
  function render (line 92) | pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> Vec<ANS...
  function render_type (line 106) | pub fn render_type<C: Colours+FiletypeColours>(&self, colours: &C) -> AN...
  type Colours (line 119) | pub trait Colours {
    method dash (line 120) | fn dash(&self) -> Style;
    method user_read (line 122) | fn user_read(&self) -> Style;
    method user_write (line 123) | fn user_write(&self) -> Style;
    method user_execute_file (line 124) | fn user_execute_file(&self) -> Style;
    method user_execute_other (line 125) | fn user_execute_other(&self) -> Style;
    method group_read (line 127) | fn group_read(&self) -> Style;
    method group_write (line 128) | fn group_write(&self) -> Style;
    method group_execute (line 129) | fn group_execute(&self) -> Style;
    method other_read (line 131) | fn other_read(&self) -> Style;
    method other_write (line 132) | fn other_write(&self) -> Style;
    method other_execute (line 133) | fn other_execute(&self) -> Style;
    method special_user_file (line 135) | fn special_user_file(&self) -> Style;
    method special_other (line 136) | fn special_other(&self) -> Style;
    method attribute (line 138) | fn attribute(&self) -> Style;
    method dash (line 156) | fn dash(&self)                -> Style { Fixed(11).normal() }
    method user_read (line 157) | fn user_read(&self)           -> Style { Fixed(101).normal() }
    method user_write (line 158) | fn user_write(&self)          -> Style { Fixed(102).normal() }
    method user_execute_file (line 159) | fn user_execute_file(&self)   -> Style { Fixed(103).normal() }
    method user_execute_other (line 160) | fn user_execute_other(&self)  -> Style { Fixed(113).normal() }
    method group_read (line 161) | fn group_read(&self)          -> Style { Fixed(104).normal() }
    method group_write (line 162) | fn group_write(&self)         -> Style { Fixed(105).normal() }
    method group_execute (line 163) | fn group_execute(&self)       -> Style { Fixed(106).normal() }
    method other_read (line 164) | fn other_read(&self)          -> Style { Fixed(107).normal() }
    method other_write (line 165) | fn other_write(&self)         -> Style { Fixed(108).normal() }
    method other_execute (line 166) | fn other_execute(&self)       -> Style { Fixed(109).normal() }
    method special_user_file (line 167) | fn special_user_file(&self)   -> Style { Fixed(110).normal() }
    method special_other (line 168) | fn special_other(&self)       -> Style { Fixed(111).normal() }
    method attribute (line 169) | fn attribute(&self)           -> Style { Fixed(112).normal() }
  type TestColours (line 153) | struct TestColours;
  function negate (line 174) | fn negate() {
  function affirm (line 192) | fn affirm() {
  function specials (line 210) | fn specials() {
  function extra_specials (line 228) | fn extra_specials() {

FILE: src/output/render/size.rs
  function render (line 11) | pub fn render<C: Colours>(self, colours: &C, size_format: SizeFormat, nu...
  function render (line 63) | fn render<C: Colours>(self, colours: &C) -> TextCell {
  type Colours (line 79) | pub trait Colours {
    method size (line 80) | fn size(&self, prefix: Option<Prefix>) -> Style;
    method unit (line 81) | fn unit(&self, prefix: Option<Prefix>) -> Style;
    method no_size (line 82) | fn no_size(&self) -> Style;
    method major (line 84) | fn major(&self) -> Style;
    method comma (line 85) | fn comma(&self) -> Style;
    method minor (line 86) | fn minor(&self) -> Style;
    method size (line 106) | fn size(&self, _prefix: Option<Prefix>) -> Style { Fixed(66).normal() }
    method unit (line 107) | fn unit(&self, _prefix: Option<Prefix>) -> Style { Fixed(77).bold() }
    method no_size (line 108) | fn no_size(&self)          -> Style { Black.italic() }
    method major (line 110) | fn major(&self) -> Style { Blue.on(Red) }
    method comma (line 111) | fn comma(&self) -> Style { Green.italic() }
    method minor (line 112) | fn minor(&self) -> Style { Cyan.on(Yellow) }
  type TestColours (line 103) | struct TestColours;
  function directory (line 117) | fn directory() {
  function file_decimal (line 125) | fn file_decimal() {
  function file_binary (line 140) | fn file_binary() {
  function file_bytes (line 155) | fn file_bytes() {
  function device_ids (line 169) | fn device_ids() {

FILE: src/output/render/times.rs
  type Render (line 10) | pub trait Render {
    method render (line 11) | fn render(self, style: Style, tz: &Option<TimeZone>, format: TimeForma...
    method render (line 15) | fn render(self, style: Style, tz: &Option<TimeZone>, format: TimeForma...

FILE: src/output/render/users.rs
  function render (line 10) | pub fn render<C: Colours, U: Users>(self, colours: &C, users: &U, format...
  type Colours (line 24) | pub trait Colours {
    method you (line 25) | fn you(&self) -> Style;
    method someone_else (line 26) | fn someone_else(&self) -> Style;
    method you (line 47) | fn you(&self)          -> Style { Red.bold() }
    method someone_else (line 48) | fn someone_else(&self) -> Style { Blue.underline() }
  type TestColours (line 44) | struct TestColours;
  function named (line 53) | fn named() {
  function unnamed (line 66) | fn unnamed() {
  function different_named (line 76) | fn different_named() {
  function different_unnamed (line 86) | fn different_unnamed() {
  function overflow (line 93) | fn overflow() {

FILE: src/output/table.rs
  type Options (line 25) | pub struct Options {
  type Columns (line 35) | pub struct Columns {
    method collect (line 55) | pub fn collect(&self, actually_enable_git: bool) -> Vec<Column> {
  type Column (line 123) | pub enum Column {
    method alignment (line 154) | pub fn alignment(self) -> Alignment {
    method alignment (line 166) | pub fn alignment(&self) -> Alignment {
    method header (line 176) | pub fn header(self) -> &'static str {
  type Alignment (line 145) | pub enum Alignment {
  type SizeFormat (line 205) | pub enum SizeFormat {
  type UserFormat (line 221) | pub enum UserFormat {
  method default (line 229) | fn default() -> Self {
  type TimeType (line 238) | pub enum TimeType {
    method header (line 256) | pub fn header(self) -> &'static str {
  type TimeTypes (line 274) | pub struct TimeTypes {
  method default (line 285) | fn default() -> Self {
  type Environment (line 300) | pub struct Environment {
    method lock_users (line 316) | pub fn lock_users(&self) -> MutexGuard<'_, UsersCache> {
    method load_all (line 320) | fn load_all() -> Self {
  function determine_time_zone (line 342) | fn determine_time_zone() -> TZResult<TimeZone> {
  function determine_time_zone (line 363) | fn determine_time_zone() -> TZResult<TimeZone> {
  type Table (line 392) | pub struct Table<'a> {
  type Row (line 404) | pub struct Row {
  function new (line 409) | pub fn new(options: &'a Options, git: Option<&'a GitCache>, theme: &'a T...
  function widths (line 426) | pub fn widths(&self) -> &TableWidths {
  function header_row (line 430) | pub fn header_row(&self) -> Row {
  function row_for_file (line 438) | pub fn row_for_file(&self, file: &File<'_>, xattrs: bool) -> Row {
  function add_widths (line 446) | pub fn add_widths(&mut self, row: &Row) {
  function permissions_plus (line 450) | fn permissions_plus(&self, file: &File<'_>, xattrs: bool) -> f::Permissi...
  function octal_permissions (line 462) | fn octal_permissions(&self, file: &File<'_>) -> f::OctalPermissions {
  function display (line 468) | fn display(&self, file: &File<'_>, column: Column, xattrs: bool) -> Text...
  function git_status (line 519) | fn git_status(&self, file: &File<'_>) -> f::Git {
  function render (line 527) | pub fn render(&self, row: Row) -> TextCell {
  type TableWidths (line 556) | pub struct TableWidths(Vec<usize>);
    method zero (line 567) | pub fn zero(count: usize) -> Self {
    method add_widths (line 571) | pub fn add_widths(&mut self, row: &Row) {
    method total (line 577) | pub fn total(&self) -> usize {
  type Target (line 559) | type Target = [usize];
  method deref (line 561) | fn deref(&self) -> &Self::Target {

FILE: src/output/time.rs
  type TimeFormat (line 29) | pub enum TimeFormat {
    method format_local (line 55) | pub fn format_local(self, time: SystemTime) -> String {
    method format_zoned (line 64) | pub fn format_zoned(self, time: SystemTime, zone: &TimeZone) -> String {
  function default_local (line 76) | fn default_local(time: SystemTime) -> String {
  function default_zoned (line 83) | fn default_zoned(time: SystemTime, zone: &TimeZone) -> String {
  function get_dateformat (line 89) | fn get_dateformat(date: &LocalDateTime) -> &'static DateFormat<'static> {
  function long_local (line 101) | fn long_local(time: SystemTime) -> String {
  function long_zoned (line 109) | fn long_zoned(time: SystemTime, zone: &TimeZone) -> String {
  function full_local (line 117) | fn full_local(time: SystemTime) -> String {
  function full_zoned (line 125) | fn full_zoned(time: SystemTime, zone: &TimeZone) -> String {
  function iso_local (line 138) | fn iso_local(time: SystemTime) -> String {
  function iso_zoned (line 153) | fn iso_zoned(time: SystemTime, zone: &TimeZone) -> String {
  function systemtime_epoch (line 168) | fn systemtime_epoch(time: SystemTime) -> i64 {
  function systemtime_nanos (line 181) | fn systemtime_nanos(time: SystemTime) -> u32 {
  function is_recent (line 194) | fn is_recent(date: &LocalDateTime) -> bool {

FILE: src/output/tree.rs
  type TreePart (line 43) | pub enum TreePart {
    method ascii_art (line 62) | pub fn ascii_art(self) -> &'static str {
  type TreeTrunk (line 75) | pub struct TreeTrunk {
    method new_row (line 109) | pub fn new_row(&mut self, params: TreeParams) -> &[TreePart] {
  type TreeParams (line 87) | pub struct TreeParams {
    method new (line 144) | pub fn new(depth: TreeDepth, last: bool) -> Self {
    method is_at_root (line 148) | pub fn is_at_root(&self) -> bool {
  type TreeDepth (line 98) | pub struct TreeDepth(pub usize);
    method root (line 154) | pub fn root() -> Self {
    method deeper (line 158) | pub fn deeper(self) -> Self {
    method iterate_over (line 164) | pub fn iterate_over<I, T>(self, inner: I) -> Iter<I>
  type Iter (line 172) | pub struct Iter<I> {
  type Item (line 180) | type Item = (TreeParams, T);
  method next (line 182) | fn next(&mut self) -> Option<Self::Item> {
  function params (line 196) | fn params(depth: usize, last: bool) -> TreeParams {
  function empty_at_first (line 201) | fn empty_at_first() {
  function one_child (line 207) | fn one_child() {
  function two_children (line 214) | fn two_children() {
  function two_times_two_children (line 222) | fn two_times_two_children() {
  function two_times_two_nested_children (line 234) | fn two_times_two_nested_children() {
  function test_iteration (line 254) | fn test_iteration() {
  function test_empty (line 274) | fn test_empty() {

FILE: src/theme/default_theme.rs
  method default_theme (line 9) | pub fn default_theme(scale: ColourScale) -> Self {
  method colourful (line 86) | pub fn colourful(scale: ColourScale) -> Self {
  method colourful_fixed (line 93) | fn colourful_fixed() -> Self {
  method colourful_gradient (line 112) | fn colourful_gradient() -> Self {

FILE: src/theme/lsc.rs
  type LSColors (line 25) | pub struct LSColors<'var>(pub &'var str);
  function each_pair (line 28) | pub fn each_pair<C>(&mut self, mut callback: C)
  function parse_into_high_colour (line 44) | fn parse_into_high_colour<'a, I>(iter: &mut Peekable<I>) -> Option<Colour>
  type Pair (line 88) | pub struct Pair<'var> {
  function to_style (line 94) | pub fn to_style(&self) -> Style {

FILE: src/theme/mod.rs
  type Options (line 18) | pub struct Options {
    method to_theme (line 68) | pub fn to_theme(&self, isatty: bool) -> Theme {
  type UseColours (line 35) | pub enum UseColours {
  type ColourScale (line 48) | pub enum ColourScale {
  type Definitions (line 54) | pub struct Definitions {
    method parse_color_vars (line 101) | fn parse_color_vars(&self, colours: &mut UiStyles) -> (ExtensionMappin...
  type Theme (line 60) | pub struct Theme {
    method block_count (line 205) | fn block_count(&self)  -> Style { self.ui.blocks }
    method no_blocks (line 206) | fn no_blocks(&self)    -> Style { self.ui.punctuation }
    method normal (line 210) | fn normal(&self)       -> Style { self.ui.filekinds.normal }
    method directory (line 211) | fn directory(&self)    -> Style { self.ui.filekinds.directory }
    method pipe (line 212) | fn pipe(&self)         -> Style { self.ui.filekinds.pipe }
    method symlink (line 213) | fn symlink(&self)      -> Style { self.ui.filekinds.symlink }
    method block_device (line 214) | fn block_device(&self) -> Style { self.ui.filekinds.block_device }
    method char_device (line 215) | fn char_device(&self)  -> Style { self.ui.filekinds.char_device }
    method socket (line 216) | fn socket(&self)       -> Style { self.ui.filekinds.socket }
    method special (line 217) | fn special(&self)      -> Style { self.ui.filekinds.special }
    method not_modified (line 221) | fn not_modified(&self)  -> Style { self.ui.punctuation }
    method new (line 223) | fn new(&self)           -> Style { self.ui.git.new }
    method modified (line 224) | fn modified(&self)      -> Style { self.ui.git.modified }
    method deleted (line 225) | fn deleted(&self)       -> Style { self.ui.git.deleted }
    method renamed (line 226) | fn renamed(&self)       -> Style { self.ui.git.renamed }
    method type_change (line 227) | fn type_change(&self)   -> Style { self.ui.git.typechange }
    method ignored (line 228) | fn ignored(&self)       -> Style { self.ui.git.ignored }
    method conflicted (line 229) | fn conflicted(&self)    -> Style { self.ui.git.conflicted }
    method yours (line 234) | fn yours(&self)      -> Style { self.ui.users.group_yours }
    method not_yours (line 235) | fn not_yours(&self)  -> Style { self.ui.users.group_not_yours }
    method normal (line 239) | fn normal(&self)           -> Style { self.ui.links.normal }
    method multi_link_file (line 240) | fn multi_link_file(&self)  -> Style { self.ui.links.multi_link_file }
    method dash (line 244) | fn dash(&self)               -> Style { self.ui.punctuation }
    method user_read (line 245) | fn user_read(&self)          -> Style { self.ui.perms.user_read }
    method user_write (line 246) | fn user_write(&self)         -> Style { self.ui.perms.user_write }
    method user_execute_file (line 247) | fn user_execute_file(&self)  -> Style { self.ui.perms.user_execute_file }
    method user_execute_other (line 248) | fn user_execute_other(&self) -> Style { self.ui.perms.user_execute_oth...
    method group_read (line 249) | fn group_read(&self)         -> Style { self.ui.perms.group_read }
    method group_write (line 250) | fn group_write(&self)        -> Style { self.ui.perms.group_write }
    method group_execute (line 251) | fn group_execute(&self)      -> Style { self.ui.perms.group_execute }
    method other_read (line 252) | fn other_read(&self)         -> Style { self.ui.perms.other_read }
    method other_write (line 253) | fn other_write(&self)        -> Style { self.ui.perms.other_write }
    method other_execute (line 254) | fn other_execute(&self)      -> Style { self.ui.perms.other_execute }
    method special_user_file (line 255) | fn special_user_file(&self)  -> Style { self.ui.perms.special_user_file }
    method special_other (line 256) | fn special_other(&self)      -> Style { self.ui.perms.special_other }
    method attribute (line 257) | fn attribute(&self)          -> Style { self.ui.perms.attribute }
    method size (line 261) | fn size(&self, prefix: Option<number_prefix::Prefix>) -> Style {
    method unit (line 273) | fn unit(&self, prefix: Option<number_prefix::Prefix>) -> Style {
    method no_size (line 285) | fn no_size(&self) -> Style { self.ui.punctuation }
    method major (line 286) | fn major(&self)   -> Style { self.ui.size.major }
    method comma (line 287) | fn comma(&self)   -> Style { self.ui.punctuation }
    method minor (line 288) | fn minor(&self)   -> Style { self.ui.size.minor }
    method you (line 293) | fn you(&self)           -> Style { self.ui.users.user_you }
    method someone_else (line 294) | fn someone_else(&self)  -> Style { self.ui.users.user_someone_else }
  type FileColours (line 148) | pub trait FileColours: std::marker::Sync {
    method colour_file (line 149) | fn colour_file(&self, file: &File<'_>) -> Option<Style>;
    method colour_file (line 155) | fn colour_file(&self, _file: &File<'_>) -> Option<Style> {
    method colour_file (line 168) | fn colour_file(&self, file: &File<'_>) -> Option<Style> {
    method colour_file (line 184) | fn colour_file(&self, file: &File<'_>) -> Option<Style> {
  type NoFileColours (line 153) | struct NoFileColours;
  type ExtensionMappings (line 176) | struct ExtensionMappings {
    method is_non_empty (line 192) | fn is_non_empty(&self) -> bool {
    method add (line 196) | fn add(&mut self, pattern: glob::Pattern, style: Style) {
  method normal_arrow (line 298) | fn normal_arrow(&self)        -> Style { self.ui.punctuation }
  method broken_symlink (line 299) | fn broken_symlink(&self)      -> Style { self.ui.broken_symlink }
  method broken_filename (line 300) | fn broken_filename(&self)     -> Style { apply_overlay(self.ui.broken_sy...
  method broken_control_char (line 301) | fn broken_control_char(&self) -> Style { apply_overlay(self.ui.control_c...
  method control_char (line 302) | fn control_char(&self)        -> Style { self.ui.control_char }
  method symlink_path (line 303) | fn symlink_path(&self)        -> Style { self.ui.symlink_path }
  method executable_file (line 304) | fn executable_file(&self)     -> Style { self.ui.filekinds.executable }
  method colour_file (line 306) | fn colour_file(&self, file: &File<'_>) -> Style {
  function apply_overlay (line 324) | fn apply_overlay(mut base: Style, overlay: Style) -> Style {

FILE: src/theme/ui_styles.rs
  type UiStyles (line 7) | pub struct UiStyles {
    method plain (line 108) | pub fn plain() -> Self {
    method set_ls (line 119) | pub fn set_ls(&mut self, pair: &Pair<'_>) -> bool {
    method set_exa (line 142) | pub fn set_exa(&mut self, pair: &Pair<'_>) -> bool {
    method set_number_style (line 202) | pub fn set_number_style(&mut self, style: Style) {
    method set_unit_style (line 210) | pub fn set_unit_style(&mut self, style: Style) {
  type FileKinds (line 31) | pub struct FileKinds {
  type Permissions (line 44) | pub struct Permissions {
  type Size (line 65) | pub struct Size {
  type Users (line 83) | pub struct Users {
  type Links (line 91) | pub struct Links {
  type Git (line 97) | pub struct Git {
Condensed preview — 250 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (875K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 14,
    "preview": "github: ogham\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 226,
    "preview": "---\nname: exa is unmaintained\nabout: Please use the active fork eza instead. <https://github.com/eza-community/eza>\n---\n"
  },
  {
    "path": ".github/workflows/unit-tests.yml",
    "chars": 908,
    "preview": "name: Unit tests\n\non:\n  push:\n    branches: [ master ]\n    paths:\n      - '.github/workflows/*'\n      - 'src/**'\n      -"
  },
  {
    "path": ".gitignore",
    "chars": 252,
    "preview": "# Rust stuff\ntarget\n\n# Vagrant stuff\n.vagrant\n*.log\n\n# Compiled artifacts\n# (see devtools/*-package-for-*.sh)\n/exa-linux"
  },
  {
    "path": ".rustfmt.toml",
    "chars": 30,
    "preview": "disable_all_formatting = true\n"
  },
  {
    "path": "Cargo.toml",
    "chars": 1956,
    "preview": "[package]\nname = \"exa\"\ndescription = \"A modern replacement for ls\"\nauthors = [\"Benjamin Sago <ogham@bsago.me>\"]\ncategori"
  },
  {
    "path": "Justfile",
    "chars": 2644,
    "preview": "all: build test\nall-release: build-release test-release\n\n\n#----------#\n# building #\n#----------#\n\n# compile the exa bina"
  },
  {
    "path": "LICENCE",
    "chars": 1080,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Benjamin Sago\n\nPermission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "README.md",
    "chars": 11430,
    "preview": "# exa is unmaintained, use the [fork eza](https://github.com/eza-community/eza) instead.\n\n(This repository isn’t archive"
  },
  {
    "path": "Vagrantfile",
    "chars": 6036,
    "preview": "Vagrant.configure(2) do |config|\n\n  # We use Ubuntu instead of Debian because the image comes with two-way\n  # shared fo"
  },
  {
    "path": "build.rs",
    "chars": 3734,
    "preview": "/// The version string isn’t the simplest: we want to show the version,\n/// current Git hash, and compilation date when "
  },
  {
    "path": "completions/bash/exa",
    "chars": 1763,
    "preview": "_exa()\n{\n    cur=${COMP_WORDS[COMP_CWORD]}\n    prev=${COMP_WORDS[COMP_CWORD-1]}\n\n    case \"$prev\" in\n        -'?'|--help"
  },
  {
    "path": "completions/fish/exa.fish",
    "chars": 4901,
    "preview": "# Meta-stuff\ncomplete -c exa -s 'v' -l 'version' -d \"Show version of exa\"\ncomplete -c exa -s '?' -l 'help'    -d \"Show l"
  },
  {
    "path": "completions/zsh/_exa",
    "chars": 3344,
    "preview": "#compdef exa\n\n# Save this file as _exa in /usr/local/share/zsh/site-functions or in any\n# other folder in $fpath.  E.g. "
  },
  {
    "path": "devtools/README.md",
    "chars": 250,
    "preview": "## exa › development tools\n\nThese scripts deal with things like packaging release-worthy versions of exa.\n\nThey are **no"
  },
  {
    "path": "devtools/dev-bash.sh",
    "chars": 2933,
    "preview": "# This file gets executed when a user starts a `bash` shell, usually because\n# they’ve just started a new Vagrant sessio"
  },
  {
    "path": "devtools/dev-create-test-filesystem.sh",
    "chars": 13784,
    "preview": "#!/bin/bash\n# This script creates a bunch of awkward test case files. It gets\n# automatically run as part of Vagrant pro"
  },
  {
    "path": "devtools/dev-fixtures.sh",
    "chars": 1584,
    "preview": "#!/bin/bash\n# This file contains the text fixtures — the known, constant data — that are\n# used when setting up the envi"
  },
  {
    "path": "devtools/dev-help.sh",
    "chars": 997,
    "preview": "# This file prints out some help text that says which commands are available\n# in the VM. It gets executed during Vagran"
  },
  {
    "path": "devtools/dev-package-for-linux.sh",
    "chars": 3107,
    "preview": "set -e\n\n# This script builds a publishable release-worthy version of exa.\n# It gets the version number, builds exa using"
  },
  {
    "path": "devtools/dev-run-debug.sh",
    "chars": 206,
    "preview": "#!/bin/bash\nif [[ -f ~/target/debug/exa ]]; then\n  ~/target/debug/exa \"$@\"\nelse\n  echo -e \"Debug exa binary does not exi"
  },
  {
    "path": "devtools/dev-run-release.sh",
    "chars": 232,
    "preview": "#!/bin/bash\nif [[ -f ~/target/release/exa ]]; then\n  ~/target/release/exa \"$@\"\nelse\n  echo -e \"Release exa binary does n"
  },
  {
    "path": "devtools/dev-set-up-environment.sh",
    "chars": 1381,
    "preview": "#!/bin/bash\n\nif [[ ! -d \"/vagrant\" ]]; then\n    echo \"This script should be run in the Vagrant environment\"\n    exit 1\nf"
  },
  {
    "path": "devtools/dev-versions.sh",
    "chars": 281,
    "preview": "# Displays the installed versions of Rust and Cargo.\n# This gets run from ‘dev-bash.sh’, which gets run from ‘~/.bash_pr"
  },
  {
    "path": "devtools/local-package-for-macos.sh",
    "chars": 3629,
    "preview": "set -e\n\n# This script builds a publishable release-worthy version of exa.\n# It gets the version number, builds exa using"
  },
  {
    "path": "man/exa.1.md",
    "chars": 7772,
    "preview": "% exa(1) v0.9.0\n\n<!-- This is the exa(1) man page, written in Markdown. -->\n<!-- To generate the roff version, run `just"
  },
  {
    "path": "man/exa_colors.5.md",
    "chars": 5573,
    "preview": "% exa_colors(5) v0.9.0\n\n<!-- This is the exa_colors(5) man page, written in Markdown. -->\n<!-- To generate the roff vers"
  },
  {
    "path": "rust-toolchain.toml",
    "chars": 31,
    "preview": "[toolchain]\nchannel = \"1.66.1\"\n"
  },
  {
    "path": "snap/.gitignore",
    "chars": 11,
    "preview": ".snapcraft\n"
  },
  {
    "path": "snap/snapcraft.yaml",
    "chars": 652,
    "preview": "name: exa\nversion: 'latest'\nsummary: Replacement for 'ls' written in Rust\ndescription: |\n  It uses colours for informati"
  },
  {
    "path": "src/fs/dir.rs",
    "chars": 6758,
    "preview": "use crate::fs::feature::git::GitCache;\nuse crate::fs::fields::GitStatus;\nuse std::io;\nuse std::fs;\nuse std::path::{Path,"
  },
  {
    "path": "src/fs/dir_action.rs",
    "chars": 2939,
    "preview": "//! What to do when encountering a directory?\n\n/// The action to take when trying to list a file that turns out to be a\n"
  },
  {
    "path": "src/fs/feature/git.rs",
    "chars": 12649,
    "preview": "//! Getting the Git status of files and directories.\n\nuse std::ffi::OsStr;\n#[cfg(target_family = \"unix\")]\nuse std::os::u"
  },
  {
    "path": "src/fs/feature/mod.rs",
    "chars": 637,
    "preview": "pub mod xattr;\n\n#[cfg(feature = \"git\")]\npub mod git;\n\n#[cfg(not(feature = \"git\"))]\npub mod git {\n    use std::iter::From"
  },
  {
    "path": "src/fs/feature/xattr.rs",
    "chars": 7711,
    "preview": "//! Extended attribute support for Darwin and Linux systems.\n\n#![allow(trivial_casts)]  // for ARM\n\nuse std::cmp::Orderi"
  },
  {
    "path": "src/fs/fields.rs",
    "chars": 7457,
    "preview": "//! Wrapper types for the values returned from `File`s.\n//!\n//! The methods of `File` that return information about the "
  },
  {
    "path": "src/fs/file.rs",
    "chars": 20572,
    "preview": "//! Files, and methods and fields to access their metadata.\n\nuse std::io;\n#[cfg(unix)]\nuse std::os::unix::fs::{FileTypeE"
  },
  {
    "path": "src/fs/filter.rs",
    "chars": 13436,
    "preview": "//! Filtering and sorting the list of files before displaying them.\n\nuse std::cmp::Ordering;\nuse std::iter::FromIterator"
  },
  {
    "path": "src/fs/mod.rs",
    "chars": 167,
    "preview": "mod dir;\npub use self::dir::{Dir, DotFilter};\n\nmod file;\npub use self::file::{File, FileTarget};\n\npub mod dir_action;\npu"
  },
  {
    "path": "src/info/filetype.rs",
    "chars": 5238,
    "preview": "//! Tests for various types of file (video, image, compressed, etc).\n//!\n//! Currently this is dependent on the file’s n"
  },
  {
    "path": "src/info/mod.rs",
    "chars": 298,
    "preview": "//! The “info” module contains routines that aren’t about probing the\n//! filesystem nor displaying output to the user, "
  },
  {
    "path": "src/info/sources.rs",
    "chars": 2130,
    "preview": "use std::path::PathBuf;\n\nuse crate::fs::File;\n\n\nimpl<'a> File<'a> {\n\n    /// For this file, return a vector of alternate"
  },
  {
    "path": "src/logger.rs",
    "chars": 1678,
    "preview": "//! Debug error logging.\n\nuse std::ffi::OsStr;\n\nuse ansi_term::{Colour, ANSIString};\n\n\n/// Sets the internal logger, cha"
  },
  {
    "path": "src/main.rs",
    "chars": 11731,
    "preview": "#![warn(deprecated_in_future)]\n#![warn(future_incompatible)]\n#![warn(nonstandard_style)]\n#![warn(rust_2018_compatibility"
  },
  {
    "path": "src/options/dir_action.rs",
    "chars": 6477,
    "preview": "//! Parsing the options for `DirAction`.\n\nuse crate::options::parser::MatchedFlags;\nuse crate::options::{flags, OptionsE"
  },
  {
    "path": "src/options/error.rs",
    "chars": 4729,
    "preview": "use std::ffi::OsString;\nuse std::fmt;\nuse std::num::ParseIntError;\n\nuse crate::options::flags;\nuse crate::options::parse"
  },
  {
    "path": "src/options/file_name.rs",
    "chars": 1466,
    "preview": "use crate::options::{flags, OptionsError, NumberSource};\nuse crate::options::parser::MatchedFlags;\nuse crate::options::v"
  },
  {
    "path": "src/options/filter.rs",
    "chars": 13624,
    "preview": "//! Parsing the options for `FileFilter`.\n\nuse crate::fs::DotFilter;\nuse crate::fs::filter::{FileFilter, SortField, Sort"
  },
  {
    "path": "src/options/flags.rs",
    "chars": 6196,
    "preview": "use crate::options::parser::{Arg, Args, TakesValue, Values};\n\n\n// exa options\npub static VERSION: Arg = Arg { short: Som"
  },
  {
    "path": "src/options/help.rs",
    "chars": 5365,
    "preview": "use std::fmt;\n\nuse crate::fs::feature::xattr;\nuse crate::options::flags;\nuse crate::options::parser::MatchedFlags;\n\n\nsta"
  },
  {
    "path": "src/options/mod.rs",
    "chars": 9510,
    "preview": "//! Parsing command-line strings into exa options.\n//!\n//! This module imports exa’s configuration types, such as `View`"
  },
  {
    "path": "src/options/parser.rs",
    "chars": 30658,
    "preview": "//! A general parser for command-line options.\n//!\n//! exa uses its own hand-rolled parser for command-line options. It "
  },
  {
    "path": "src/options/theme.rs",
    "chars": 8647,
    "preview": "use crate::options::{flags, vars, Vars, OptionsError};\nuse crate::options::parser::MatchedFlags;\nuse crate::theme::{Opti"
  },
  {
    "path": "src/options/vars.rs",
    "chars": 2354,
    "preview": "use std::ffi::OsString;\n\n\n// General variables\n\n/// Environment variable used to colour files, both by their filesystem "
  },
  {
    "path": "src/options/version.rs",
    "chars": 1556,
    "preview": "//! Printing the version string.\n//!\n//! The code that works out which string to print is done in `build.rs`.\n\nuse std::"
  },
  {
    "path": "src/options/view.rs",
    "chars": 27731,
    "preview": "use crate::fs::feature::xattr;\nuse crate::options::{flags, OptionsError, NumberSource, Vars};\nuse crate::options::parser"
  },
  {
    "path": "src/output/cell.rs",
    "chars": 8433,
    "preview": "//! The `TextCell` type for the details and lines views.\n\nuse std::iter::Sum;\nuse std::ops::{Add, Deref, DerefMut};\n\nuse"
  },
  {
    "path": "src/output/details.rs",
    "chars": 18000,
    "preview": "//! The **Details** output view displays each file as a row in a table.\n//!\n//! It’s used in the following situations:\n/"
  },
  {
    "path": "src/output/escape.rs",
    "chars": 845,
    "preview": "use ansi_term::{ANSIString, Style};\n\n\npub fn escape(string: String, bits: &mut Vec<ANSIString<'_>>, good: Style, bad: St"
  },
  {
    "path": "src/output/file_name.rs",
    "chars": 12588,
    "preview": "use std::fmt::Debug;\nuse std::path::Path;\n\nuse ansi_term::{ANSIString, Style};\n\nuse crate::fs::{File, FileTarget};\nuse c"
  },
  {
    "path": "src/output/grid.rs",
    "chars": 1893,
    "preview": "use std::io::{self, Write};\n\nuse term_grid as tg;\n\nuse crate::fs::File;\nuse crate::fs::filter::FileFilter;\nuse crate::ou"
  },
  {
    "path": "src/output/grid_details.rs",
    "chars": 10572,
    "preview": "//! The grid-details view lists several details views side-by-side.\n\nuse std::io::{self, Write};\n\nuse ansi_term::ANSIStr"
  },
  {
    "path": "src/output/icons.rs",
    "chars": 16169,
    "preview": "use ansi_term::Style;\n\nuse crate::fs::File;\nuse crate::info::filetype::FileExtensions;\nuse lazy_static::lazy_static;\nuse"
  },
  {
    "path": "src/output/lines.rs",
    "chars": 968,
    "preview": "use std::io::{self, Write};\n\nuse ansi_term::ANSIStrings;\n\nuse crate::fs::File;\nuse crate::fs::filter::FileFilter;\nuse cr"
  },
  {
    "path": "src/output/mod.rs",
    "chars": 1410,
    "preview": "pub use self::cell::{TextCell, TextCellContents, DisplayWidth};\npub use self::escape::escape;\n\npub mod details;\npub mod "
  },
  {
    "path": "src/output/render/blocks.rs",
    "chars": 1229,
    "preview": "use ansi_term::Style;\n\nuse crate::fs::fields as f;\nuse crate::output::cell::TextCell;\n\n\nimpl f::Blocks {\n    pub fn rend"
  },
  {
    "path": "src/output/render/filetype.rs",
    "chars": 999,
    "preview": "use ansi_term::{ANSIString, Style};\n\nuse crate::fs::fields as f;\n\n\nimpl f::Type {\n    pub fn render<C: Colours>(self, co"
  },
  {
    "path": "src/output/render/git.rs",
    "chars": 3060,
    "preview": "use ansi_term::{ANSIString, Style};\n\nuse crate::output::cell::{TextCell, DisplayWidth};\nuse crate::fs::fields as f;\n\n\nim"
  },
  {
    "path": "src/output/render/groups.rs",
    "chars": 3719,
    "preview": "use ansi_term::Style;\nuse users::{Users, Groups};\n\nuse crate::fs::fields as f;\nuse crate::output::cell::TextCell;\nuse cr"
  },
  {
    "path": "src/output/render/inode.rs",
    "chars": 564,
    "preview": "use ansi_term::Style;\n\nuse crate::fs::fields as f;\nuse crate::output::cell::TextCell;\n\n\nimpl f::Inode {\n    pub fn rende"
  },
  {
    "path": "src/output/render/links.rs",
    "chars": 2065,
    "preview": "use ansi_term::Style;\nuse locale::Numeric as NumericLocale;\n\nuse crate::fs::fields as f;\nuse crate::output::cell::TextCe"
  },
  {
    "path": "src/output/render/mod.rs",
    "chars": 694,
    "preview": "mod blocks;\npub use self::blocks::Colours as BlocksColours;\n\nmod filetype;\npub use self::filetype::Colours as FiletypeCo"
  },
  {
    "path": "src/output/render/octal.rs",
    "chars": 4186,
    "preview": "use ansi_term::Style;\n\nuse crate::fs::fields as f;\nuse crate::output::cell::TextCell;\n\n\nimpl f::OctalPermissions {\n    f"
  },
  {
    "path": "src/output/render/permissions.rs",
    "chars": 9161,
    "preview": "use ansi_term::{ANSIString, Style};\n\nuse crate::fs::fields as f;\nuse crate::output::cell::{TextCell, DisplayWidth};\nuse "
  },
  {
    "path": "src/output/render/size.rs",
    "chars": 5590,
    "preview": "use ansi_term::Style;\nuse locale::Numeric as NumericLocale;\nuse number_prefix::Prefix;\n\nuse crate::fs::fields as f;\nuse "
  },
  {
    "path": "src/output/render/times.rs",
    "chars": 731,
    "preview": "use std::time::SystemTime;\n\nuse datetime::TimeZone;\nuse ansi_term::Style;\n\nuse crate::output::cell::TextCell;\nuse crate:"
  },
  {
    "path": "src/output/render/users.rs",
    "chars": 3074,
    "preview": "use ansi_term::Style;\nuse users::Users;\n\nuse crate::fs::fields as f;\nuse crate::output::cell::TextCell;\nuse crate::outpu"
  },
  {
    "path": "src/output/table.rs",
    "chars": 15135,
    "preview": "use std::cmp::max;\nuse std::env;\nuse std::ops::Deref;\n#[cfg(unix)]\nuse std::sync::{Mutex, MutexGuard};\n\nuse datetime::Ti"
  },
  {
    "path": "src/output/time.rs",
    "chars": 8378,
    "preview": "//! Timestamp formatting.\n\nuse std::time::{SystemTime, UNIX_EPOCH};\n\nuse datetime::{LocalDateTime, TimeZone, DatePiece, "
  },
  {
    "path": "src/output/tree.rs",
    "chars": 8996,
    "preview": "//! Tree structures, such as `├──` or `└──`, used in a tree view.\n//!\n//! ## Constructing Tree Views\n//!\n//! When using "
  },
  {
    "path": "src/theme/default_theme.rs",
    "chars": 4058,
    "preview": "use ansi_term::Style;\nuse ansi_term::Colour::*;\n\nuse crate::theme::ColourScale;\nuse crate::theme::ui_styles::*;\n\n\nimpl U"
  },
  {
    "path": "src/theme/lsc.rs",
    "chars": 8848,
    "preview": "use std::iter::Peekable;\nuse std::ops::FnMut;\n\nuse ansi_term::{Colour, Style};\nuse ansi_term::Colour::*;\n\n\n// Parsing th"
  },
  {
    "path": "src/theme/mod.rs",
    "chars": 25216,
    "preview": "use ansi_term::Style;\n\nuse crate::fs::File;\nuse crate::output::file_name::Colours as FileNameColours;\nuse crate::output:"
  },
  {
    "path": "src/theme/ui_styles.rs",
    "chars": 7755,
    "preview": "use ansi_term::Style;\n\nuse crate::theme::lsc::Pair;\n\n\n#[derive(Debug, Default, PartialEq)]\npub struct UiStyles {\n    pub"
  },
  {
    "path": "xtests/README.md",
    "chars": 2364,
    "preview": "# exa › xtests\n\nThese are the **extended tests**. They are integration tests: they run the `exa` binary with select conf"
  },
  {
    "path": "xtests/attributes.toml",
    "chars": 1255,
    "preview": "[[cmd]]\nname = \"‘exa -@lT’ produces a tree view with metadata and attribute entries\"\nshell = \"exa -@lT /testcases/attrib"
  },
  {
    "path": "xtests/colour-term.toml",
    "chars": 1801,
    "preview": "# details view (check the argument works)\n\n[[cmd]]\nname = \"‘exa -l --colour=always’ always uses colours for metadata\"\nsh"
  },
  {
    "path": "xtests/debug-logging.toml",
    "chars": 471,
    "preview": "[[cmd]]\nname = \"‘EXA_DEBUG=1 exa’ produces debug output\"\nshell = \"exa --long /testcases\"\nenvironment = { EXA_DEBUG = \"1\""
  },
  {
    "path": "xtests/details-view-dates.toml",
    "chars": 3012,
    "preview": "# various date fields\n\n[[cmd]]\nname = \"‘exa -lh’ produces a table using the modified time field\"\nshell = \"exa -lh /testc"
  },
  {
    "path": "xtests/details-view-filesizes.toml",
    "chars": 2649,
    "preview": "[[cmd]]\nname = \"‘exa -lb’ produces a details table with binary file sizes\"\nshell = \"exa -lb /testcases/files\"\nstdout = {"
  },
  {
    "path": "xtests/details-view-passwd.toml",
    "chars": 246,
    "preview": "[[cmd]]\nname = \"‘exa -lgh’ produces a tree view with attribute entries\"\nshell = \"exa -lgh /testcases/passwd\"\nstdout = { "
  },
  {
    "path": "xtests/details-view-permissions.toml",
    "chars": 748,
    "preview": "[[cmd]]\nname = \"‘exa -lghR’ (not as the user) produces a tree view with attribute entries\"\nshell = \"exa -lghR /testcases"
  },
  {
    "path": "xtests/details-view.toml",
    "chars": 1292,
    "preview": "[[cmd]]\nname = \"‘exa -l’ produces a details table\"\nshell = \"exa -l /testcases/files\"\nstdout = { file = \"outputs/files_lo"
  },
  {
    "path": "xtests/dotfiles.toml",
    "chars": 1631,
    "preview": "# hidden files in grid view\n\n[[cmd]]\nname = \"‘exa’ does not show hidden files (in grid view)\"\nshell = \"exa /testcases/hi"
  },
  {
    "path": "xtests/errors.toml",
    "chars": 3175,
    "preview": "# Command-line errors\n\n[[cmd]]\nname = \"‘exa --aoeu’ displays an error\"\nshell = \"exa --aoeu\"\nstdout = { empty = true }\nst"
  },
  {
    "path": "xtests/features/none.toml",
    "chars": 736,
    "preview": "# These tests are meant to be run against an exa binary compiled with\n# `--no-default-features`. They will fail otherwis"
  },
  {
    "path": "xtests/features/outputs/disabled_git.txt",
    "chars": 106,
    "preview": "exa: Options --git and --git-ignore can't be used because `git` feature was disabled in this build of exa\n"
  },
  {
    "path": "xtests/git-ignore.toml",
    "chars": 2746,
    "preview": "# Git-ignoring\n\n[[cmd]]\nname = \"‘exa --git-ignore’ skips Git-ignored files\"\nshell = \"exa --git-ignore /testcases/git2/ig"
  },
  {
    "path": "xtests/git.toml",
    "chars": 6335,
    "preview": "# The first Git repo: additions and modifications\n\n[[cmd]]\nname = \"‘exa --git -l’ shows a Git status column\"\nshell = \"ex"
  },
  {
    "path": "xtests/grid-details-view.toml",
    "chars": 4338,
    "preview": "# listing directory tests\n\n[[cmd]]\nname = \"‘COLUMNS=40 exa -lG’ produces a grid with details of 1 column\"\nshell = \"exa -"
  },
  {
    "path": "xtests/grid-view.toml",
    "chars": 3540,
    "preview": "# file name tests\n\n[[cmd]]\nname = \"‘exa’ produces a grid of file names\"\nshell = \"exa /testcases/file-names\"\nenvironment "
  },
  {
    "path": "xtests/help.toml",
    "chars": 180,
    "preview": "[[cmd]]\nname = \"‘exa --help’ produces the correct help text\"\nshell = \"exa --help\"\nstdout = { file = \"outputs/help.ansitx"
  },
  {
    "path": "xtests/icons.toml",
    "chars": 3597,
    "preview": "# view icons tests\n\n[[cmd]]\nname = \"‘exa -1 --icons’ shows icons next to file names in lines mode\"\nshell = \"exa -1 --ico"
  },
  {
    "path": "xtests/ignore-glob.toml",
    "chars": 443,
    "preview": "[[cmd]]\nname = \"‘exa -1 -I’ ignores based on a glob\"\nshell = \"exa -1 -I '*.OGG' /testcases/file-names-exts/music.*\"\nstdo"
  },
  {
    "path": "xtests/input-options.toml",
    "chars": 2164,
    "preview": "[[cmd]]\nname = \"exa can handle invalid UTF-8 in command-line arguments\"\nshell = \"exa /testcases/file-names/*\"\nstdout = {"
  },
  {
    "path": "xtests/lines-view.toml",
    "chars": 912,
    "preview": "# file name tests\n\n[[cmd]]\nname = \"‘exa -1’ displays file names, one on each line\"\nshell = \"exa -1 /testcases/file-names"
  },
  {
    "path": "xtests/outputs/attributes_files_xattrs_tree.ansitxt",
    "chars": 2713,
    "preview": "\u001b[36m/testcases/attributes/\u001b[1;34mdirs\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mno-xattrs_empty\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mno-xa"
  },
  {
    "path": "xtests/outputs/attributes_xattrs_long_tree.ansitxt",
    "chars": 7697,
    "preview": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:"
  },
  {
    "path": "xtests/outputs/attributes_xattrs_tree.ansitxt",
    "chars": 2897,
    "preview": "\u001b[36m/testcases/\u001b[1;34mattributes\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mdirs\u001b[0m\n\u001b[38;5;244m│  ├──\u001b[0m \u001b[1;34mno-xattrs_empty\u001b[0"
  },
  {
    "path": "xtests/outputs/dates_long_currentyear_localefr.ansitxt",
    "chars": 5342,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 janv. 12:34\u001b"
  },
  {
    "path": "xtests/outputs/dates_long_localefr.ansitxt",
    "chars": 394,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m15 juin   200"
  },
  {
    "path": "xtests/outputs/dates_long_localejp.ansitxt",
    "chars": 388,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m15  6月  2006\u001b"
  },
  {
    "path": "xtests/outputs/dates_long_time_accessed.ansitxt",
    "chars": 489,
    "preview": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Accessed\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[3"
  },
  {
    "path": "xtests/outputs/dates_long_time_created.ansitxt",
    "chars": 485,
    "preview": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Created\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33"
  },
  {
    "path": "xtests/outputs/dates_long_time_modified.ansitxt",
    "chars": 489,
    "preview": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[3"
  },
  {
    "path": "xtests/outputs/dates_long_timestyle_fulliso.ansitxt",
    "chars": 457,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m2006-06-15 23"
  },
  {
    "path": "xtests/outputs/dates_long_timestyle_iso.ansitxt",
    "chars": 382,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m2006-06-15\u001b[0"
  },
  {
    "path": "xtests/outputs/dates_long_timestyle_longiso.ansitxt",
    "chars": 400,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m2006-06-15 23"
  },
  {
    "path": "xtests/outputs/dev_long.ansitxt",
    "chars": 932,
    "preview": "\u001b[1;33mcr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m-----\u001b[0m \u001b[1;32m1\u001b[0m\u001b[38;5;244m,\u001b[32m1\u001b[0m root \u001b[1;33mmem\u001b[0m\n\u001b[1;33m"
  },
  {
    "path": "xtests/outputs/dirs_grid.ansitxt",
    "chars": 42,
    "preview": "\u001b[1;34m.\u001b[0m  \u001b[1;34m..\u001b[0m  \u001b[1;34m/\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/dirs_oneline.ansitxt",
    "chars": 40,
    "preview": "\u001b[1;34m.\u001b[0m\n\u001b[1;34m..\u001b[0m\n\u001b[1;34m/\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/error_columns_invalid.ansitxt",
    "chars": 94,
    "preview": "exa: Value \"abcdef\" not valid for environment variable COLUMNS: invalid digit found in string\n"
  },
  {
    "path": "xtests/outputs/error_columns_nines.ansitxt",
    "chars": 120,
    "preview": "exa: Value \"99999999999999999999999\" not valid for environment variable COLUMNS: number too large to fit in target type\n"
  },
  {
    "path": "xtests/outputs/error_grid_rows_invalid.ansitxt",
    "chars": 100,
    "preview": "exa: Value \"abcdef\" not valid for environment variable EXA_GRID_ROWS: invalid digit found in string\n"
  },
  {
    "path": "xtests/outputs/error_grid_rows_nines.ansitxt",
    "chars": 126,
    "preview": "exa: Value \"99999999999999999999999\" not valid for environment variable EXA_GRID_ROWS: number too large to fit in target"
  },
  {
    "path": "xtests/outputs/error_icon_spacing_invalid.ansitxt",
    "chars": 103,
    "preview": "exa: Value \"abcdef\" not valid for environment variable EXA_ICON_SPACING: invalid digit found in string\n"
  },
  {
    "path": "xtests/outputs/error_icon_spacing_nines.ansitxt",
    "chars": 129,
    "preview": "exa: Value \"99999999999999999999999\" not valid for environment variable EXA_ICON_SPACING: number too large to fit in tar"
  },
  {
    "path": "xtests/outputs/error_invalid_option.ansitxt",
    "chars": 29,
    "preview": "exa: Unknown argument --aoeu\n"
  },
  {
    "path": "xtests/outputs/error_level_invalid.ansitxt",
    "chars": 85,
    "preview": "exa: Value \"abcdef\" not valid for option --level (-L): invalid digit found in string\n"
  },
  {
    "path": "xtests/outputs/error_level_nines.ansitxt",
    "chars": 111,
    "preview": "exa: Value \"99999999999999999999999\" not valid for option --level (-L): number too large to fit in target type\n"
  },
  {
    "path": "xtests/outputs/error_tree_all_all.ansitxt",
    "chars": 48,
    "preview": "exa: Option --tree is useless given --all --all\n"
  },
  {
    "path": "xtests/outputs/exts_compressed_paths_themed.ansitxt",
    "chars": 292,
    "preview": "\u001b[36m/testcases/file-names-exts/\u001b[1;37mcompressed.deb\u001b[0m\n\u001b[36m/testcases/file-names-exts/\u001b[1;37mcompressed.tar.gz\u001b[0m\n\u001b"
  },
  {
    "path": "xtests/outputs/exts_compressed_paths_themed_reset.ansitxt",
    "chars": 282,
    "preview": "\u001b[36m/testcases/file-names-exts/\u001b[1;37mcompressed.deb\u001b[0m\n\u001b[36m/testcases/file-names-exts/\u001b[1;37mcompressed.tar.gz\u001b[0m\n\u001b"
  },
  {
    "path": "xtests/outputs/exts_grid_monochrome.ansitxt",
    "chars": 438,
    "preview": "#SAVEFILE#       compressed.deb     crypto.asc        image.svg      VIDEO.AVI\nbackup~          compressed.tar.gz  crypt"
  },
  {
    "path": "xtests/outputs/exts_grid_sort_name_reverse.ansitxt",
    "chars": 743,
    "preview": "\u001b[38;5;135mvideo.wmv\u001b[0m     \u001b[38;5;93mlossless.flac\u001b[0m  \u001b[38;5;109mcrypto.signature\u001b[0m   \u001b[31mcompressed.tar.gz\u001b[0m  "
  },
  {
    "path": "xtests/outputs/exts_oneline_icons.ansitxt",
    "chars": 716,
    "preview": "\u001b[38;5;244m #SAVEFILE#\u001b[0m\n\u001b[38;5;244m backup~\u001b[0m\n\u001b[38;5;137m compiled.class\u001b[0m\n compiled.coffee\n\u001b[38;5;137m comp"
  },
  {
    "path": "xtests/outputs/exts_oneline_sort_ext.ansitxt",
    "chars": 662,
    "preview": "\u001b[38;5;244m#SAVEFILE#\u001b[0m\n\u001b[38;5;244mbackup~\u001b[0m\n\u001b[1;4;33mMakefile\u001b[0m\n\u001b[38;5;109mcrypto.asc\u001b[0m\n\u001b[38;5;135mVIDEO.AVI\u001b[0"
  },
  {
    "path": "xtests/outputs/exts_oneline_sort_extcase.ansitxt",
    "chars": 662,
    "preview": "\u001b[38;5;244m#SAVEFILE#\u001b[0m\n\u001b[1;4;33mMakefile\u001b[0m\n\u001b[38;5;244mbackup~\u001b[0m\n\u001b[38;5;109mcrypto.asc\u001b[0m\n\u001b[38;5;135mVIDEO.AVI\u001b[0"
  },
  {
    "path": "xtests/outputs/exts_oneline_sort_name.ansitxt",
    "chars": 662,
    "preview": "\u001b[38;5;244m#SAVEFILE#\u001b[0m\n\u001b[38;5;244mbackup~\u001b[0m\n\u001b[38;5;137mcompiled.class\u001b[0m\ncompiled.coffee\n\u001b[38;5;137mcompiled.js\u001b[0"
  },
  {
    "path": "xtests/outputs/exts_oneline_sort_name_reverse.ansitxt",
    "chars": 647,
    "preview": "\u001b[38;5;135mvideo.wmv\u001b[0m\n\u001b[38;5;135mVIDEO.AVI\u001b[0m\n\u001b[38;5;92mMUSIC.OGG\u001b[0m\n\u001b[38;5;92mmusic.mp3\u001b[0m\n\u001b[1;4;33mMakefile\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/exts_oneline_sort_namecase.ansitxt",
    "chars": 662,
    "preview": "\u001b[38;5;244m#SAVEFILE#\u001b[0m\n\u001b[31mCOMPRESSED.ZIP\u001b[0m\n\u001b[38;5;105mDOCUMENT.XLSX\u001b[0m\n\u001b[38;5;133mIMAGE.PNG\u001b[0m\n\u001b[38;5;92mMUSIC."
  },
  {
    "path": "xtests/outputs/exts_themed_reset.ansitxt",
    "chars": 329,
    "preview": "#SAVEFILE#\nbackup~\ncompiled.class\ncompiled.coffee\ncompiled.js\ncompiled.o\ncompressed.deb\ncompressed.tar.gz\ncompressed.tar"
  },
  {
    "path": "xtests/outputs/far_dates_long.ansitxt",
    "chars": 301,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m \u001b[1;33mvagrant\u001b[0m \u001b[34m 1 J"
  },
  {
    "path": "xtests/outputs/files_grid_13col.ansitxt",
    "chars": 356,
    "preview": "1_bytes  2_bytes  3_bytes  4_bytes  5_bytes  6_bytes  7_bytes  8_bytes  9_bytes  10_bytes  11_bytes  12_bytes  13_bytes\n"
  },
  {
    "path": "xtests/outputs/files_grid_20col.ansitxt",
    "chars": 338,
    "preview": "1_bytes  1_MiB    2_KiB  3_bytes  3_MiB    4_KiB  5_bytes  5_MiB    6_KiB  7_bytes  7_MiB    8_KiB  9_bytes  9_MiB     1"
  },
  {
    "path": "xtests/outputs/files_grid_4col.ansitxt",
    "chars": 350,
    "preview": "1_bytes  4_KiB    7_MiB     11_bytes\n1_KiB    4_MiB    8_bytes   11_KiB\n1_MiB    5_bytes  8_KiB     11_MiB\n2_bytes  5_Ki"
  },
  {
    "path": "xtests/outputs/files_grid_8col.ansitxt",
    "chars": 356,
    "preview": "1_bytes  2_MiB    4_KiB    6_bytes  7_MiB    9_KiB     11_bytes  12_MiB\n1_KiB    3_bytes  4_MiB    6_KiB    8_bytes  9_M"
  },
  {
    "path": "xtests/outputs/files_grid_icons.ansitxt",
    "chars": 440,
    "preview": " 1_bytes   3_bytes   5_bytes   7_bytes   9_bytes    11_bytes   13_bytes\n 1_KiB     3_KiB     5_KiB     7_KiB "
  },
  {
    "path": "xtests/outputs/files_grid_monochrome.ansitxt",
    "chars": 432,
    "preview": "ansi: [\\u{1b}[34mblue\\u{1b}[0m]  form-feed: [\\u{c}]      new-line-dir: [\\n]\nascii: hello                     invalid-utf"
  },
  {
    "path": "xtests/outputs/files_long.ansitxt",
    "chars": 5264,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0"
  },
  {
    "path": "xtests/outputs/files_long_binary.ansitxt",
    "chars": 5303,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b["
  },
  {
    "path": "xtests/outputs/files_long_bytes.ansitxt",
    "chars": 5264,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12"
  },
  {
    "path": "xtests/outputs/files_long_colourscale.ansitxt",
    "chars": 5316,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[38;5;118m1\u001b[0m cassowary \u001b[34m 1 Jan 12:3"
  },
  {
    "path": "xtests/outputs/files_long_colourscale_binary.ansitxt",
    "chars": 5355,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m     \u001b[38;5;118m1\u001b[0m cassowary \u001b[34m 1 Jan 12:"
  },
  {
    "path": "xtests/outputs/files_long_colourscale_bytes.ansitxt",
    "chars": 5420,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m          \u001b[38;5;118m1\u001b[0m cassowary \u001b[34m 1 Ja"
  },
  {
    "path": "xtests/outputs/files_long_grid_1col.ansitxt",
    "chars": 5264,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0"
  },
  {
    "path": "xtests/outputs/files_long_grid_2col.ansitxt",
    "chars": 5351,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0"
  },
  {
    "path": "xtests/outputs/files_long_grid_3col.ansitxt",
    "chars": 5376,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0"
  },
  {
    "path": "xtests/outputs/files_long_grid_4col.ansitxt",
    "chars": 5393,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0"
  },
  {
    "path": "xtests/outputs/files_long_grid_exa_grid_rows_2_3files.ansitxt",
    "chars": 482,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0"
  },
  {
    "path": "xtests/outputs/files_long_grid_exa_grid_rows_5_15files.ansitxt",
    "chars": 2454,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m"
  },
  {
    "path": "xtests/outputs/files_long_grid_exa_grid_rows_6_15files.ansitxt",
    "chars": 2422,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0"
  },
  {
    "path": "xtests/outputs/files_long_grid_header_1file.ansitxt",
    "chars": 245,
    "preview": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[3"
  },
  {
    "path": "xtests/outputs/files_long_grid_header_2files.ansitxt",
    "chars": 524,
    "preview": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m                         \u001b[4mPermis"
  },
  {
    "path": "xtests/outputs/files_long_grid_icons.ansitxt",
    "chars": 5342,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0"
  },
  {
    "path": "xtests/outputs/files_long_header.ansitxt",
    "chars": 5428,
    "preview": "\u001b[4mPermissions\u001b[0m \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[3"
  },
  {
    "path": "xtests/outputs/files_long_header_binary.ansitxt",
    "chars": 5468,
    "preview": "\u001b[4mPermissions\u001b[0m  \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b["
  },
  {
    "path": "xtests/outputs/files_long_header_bytes.ansitxt",
    "chars": 5434,
    "preview": "\u001b[4mPermissions\u001b[0m       \u001b[4mSize\u001b[0m \u001b[4mUser\u001b[0m      \u001b[4mDate Modified\u001b[0m \u001b[4mName\u001b[0m\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;24"
  },
  {
    "path": "xtests/outputs/files_long_icons.ansitxt",
    "chars": 5342,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m    \u001b[1;32m1\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0"
  },
  {
    "path": "xtests/outputs/files_long_monochrome.ansitxt",
    "chars": 1793,
    "preview": ".rw-r--r--    1 cassowary  1 Jan 12:34 1_bytes\n.rw-r--r-- 1.0k cassowary  1 Jan 12:34 1_KiB\n.rw-r--r-- 1.0M cassowary  1"
  },
  {
    "path": "xtests/outputs/files_long_tree_icons.ansitxt",
    "chars": 6263,
    "preview": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m    \u001b[38;5;244m-\u001b[0m \u001b[1;33mvagrant\u001b[0m   \u001b["
  },
  {
    "path": "xtests/outputs/files_oneline_icons.ansitxt",
    "chars": 350,
    "preview": " 1_bytes\n 1_KiB\n 1_MiB\n 2_bytes\n 2_KiB\n 2_MiB\n 3_bytes\n 3_KiB\n 3_MiB\n 4_bytes\n 4_KiB\n 4_MiB\n 5_bytes\n 5_Ki"
  },
  {
    "path": "xtests/outputs/files_paths_grid_3col.ansitxt",
    "chars": 1346,
    "preview": "\u001b[36m/testcases/files/\u001b[0m1_bytes  \u001b[36m/testcases/files/\u001b[0m5_KiB    \u001b[36m/testcases/files/\u001b[0m9_MiB\n\u001b[36m/testcases/fi"
  },
  {
    "path": "xtests/outputs/files_paths_grid_5col.ansitxt",
    "chars": 1364,
    "preview": "\u001b[36m/testcases/files/\u001b[0m1_bytes  \u001b[36m/testcases/files/\u001b[0m3_MiB    \u001b[36m/testcases/files/\u001b[0m6_KiB    \u001b[36m/testcases"
  },
  {
    "path": "xtests/outputs/files_paths_grid_7col.ansitxt",
    "chars": 1376,
    "preview": "\u001b[36m/testcases/files/\u001b[0m1_bytes  \u001b[36m/testcases/files/\u001b[0m3_bytes  \u001b[36m/testcases/files/\u001b[0m5_bytes  \u001b[36m/testcases"
  },
  {
    "path": "xtests/outputs/files_paths_long_grid_1col.ansitxt",
    "chars": 6278,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0"
  },
  {
    "path": "xtests/outputs/files_paths_long_grid_2col.ansitxt",
    "chars": 6373,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m   \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0"
  },
  {
    "path": "xtests/outputs/files_paths_long_grid_3col.ansitxt",
    "chars": 6378,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[33mr\u001b[38;5;244m--\u001b[0m  \u001b[1;32m10\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b[0m"
  },
  {
    "path": "xtests/outputs/files_tree_icons.ansitxt",
    "chars": 1131,
    "preview": "\u001b[34m \u001b[36m/testcases/\u001b[1;34mfiles\u001b[0m\n\u001b[38;5;244m├──\u001b[0m  1_bytes\n\u001b[38;5;244m├──\u001b[0m  1_KiB\n\u001b[38;5;244m├──\u001b[0m  1_M"
  },
  {
    "path": "xtests/outputs/git1+2_long.ansitxt",
    "chars": 1020,
    "preview": "/testcases/git:\n\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary "
  },
  {
    "path": "xtests/outputs/git1+2_long_directories.ansitxt",
    "chars": 1529,
    "preview": "/testcases/git/additions:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m c"
  },
  {
    "path": "xtests/outputs/git1+2_long_nested.ansitxt",
    "chars": 1710,
    "preview": "/testcases/git2/deeply/nested/directory:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m "
  },
  {
    "path": "xtests/outputs/git1_long.ansitxt",
    "chars": 480,
    "preview": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:3"
  },
  {
    "path": "xtests/outputs/git1_long_additions.ansitxt",
    "chars": 461,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34"
  },
  {
    "path": "xtests/outputs/git1_long_edits.ansitxt",
    "chars": 454,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34"
  },
  {
    "path": "xtests/outputs/git1_long_moves.ansitxt",
    "chars": 186,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m21\u001b[0m cassowary \u001b[34m 1 Jan 12:34"
  },
  {
    "path": "xtests/outputs/git1_long_multiple.ansitxt",
    "chars": 964,
    "preview": "/testcases/git/additions:\n.\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m c"
  },
  {
    "path": "xtests/outputs/git1_long_recurse.ansitxt",
    "chars": 1624,
    "preview": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:3"
  },
  {
    "path": "xtests/outputs/git1_long_tree.ansitxt",
    "chars": 1935,
    "preview": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m  \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:"
  },
  {
    "path": "xtests/outputs/git1_paths_long_grid.ansitxt",
    "chars": 1495,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m20\u001b[0m cassowary \u001b[34m 1 Jan 12:34"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_grid_gitignore.ansitxt",
    "chars": 63,
    "preview": "\u001b[38;5;92mmusic.m4a\u001b[0m  \u001b[1;34mnested\u001b[0m  \u001b[1;34mnested2\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_lines_gitignore.ansitxt",
    "chars": 61,
    "preview": "\u001b[38;5;92mmusic.m4a\u001b[0m\n\u001b[1;34mnested\u001b[0m\n\u001b[1;34mnested2\u001b[0m\n"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_long_gitignore.ansitxt",
    "chars": 437,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_long_grid_gitignore.ansitxt",
    "chars": 443,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_long_recurse_gitignore.ansitxt",
    "chars": 662,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_long_tree_gitignore.ansitxt",
    "chars": 839,
    "preview": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:3"
  },
  {
    "path": "xtests/outputs/git2_ignoreds_tree_gitignore.ansitxt",
    "chars": 213,
    "preview": "\u001b[36m/testcases/git2/\u001b[1;34mignoreds\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[38;5;92mmusic.m4a\u001b[0m\n\u001b[38;5;244m├──\u001b[0m \u001b[1;34mnested\u001b[0m"
  },
  {
    "path": "xtests/outputs/git2_long.ansitxt",
    "chars": 506,
    "preview": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:3"
  },
  {
    "path": "xtests/outputs/git2_long_ignoredcontent.ansitxt",
    "chars": 193,
    "preview": "\u001b[1;34md\u001b[33mr\u001b[31mw\u001b[32mx\u001b[0m\u001b[33mr\u001b[31mw\u001b[32mx\u001b[33mr\u001b[38;5;244m-\u001b[32mx\u001b[0m \u001b[38;5;244m-\u001b[0m cassowary \u001b[34m 1 Jan 12:3"
  },
  {
    "path": "xtests/outputs/git2_long_ignoreddir.ansitxt",
    "chars": 171,
    "preview": ".\u001b[1;33mr\u001b[31mw\u001b[0m\u001b[38;5;244m-\u001b[33mr\u001b[31mw\u001b[38;5;244m-\u001b[33mr\u001b[38;5;244m--\u001b[0m \u001b[1;32m0\u001b[0m cassowary \u001b[34m 1 Jan 12:34\u001b"
  }
]

// ... and 50 more files (download for full content)

About this extraction

This page contains the full source code of the ogham/exa GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 250 files (720.4 KB), approximately 285.4k tokens, and a symbol index with 738 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!