twig is a single-binary Rust CLI that lists one directory level (max_depth = 1) with optional long-format metadata, sorting, Git integration, symlink target rendering, hyperlink support, path caching for shell tooling, and NTFS-aware recursive stats fast paths.
- Eligible plain and long listings use dedicated
std::fs::read_dirfast paths. When piping or when there are >1000 entries in the listing,--color=autoand--hyperlink=autodo not apply colour or hyperlinks.- Benchmarking with hyperlinks and colours disabled on all
twigis 2–3× faster than/bin/ls(withtwig -lasame speed as/bin/ls -la) and 8–12× faster thaneza(withtwig -la1.5× faster thaneza -la). - With hyperlinks and colour forced on both,
twigis 4× faster thanezawith hyperlinks and colour (withtwig -la2.2× faster thaneza -la).
- Benchmarking with hyperlinks and colours disabled on all
- On NTFS-like mounts, recursive stats (
-S,-c,--sort dircount|filecount) attempt an MFT-based fast path first, then automatically fall back to regular filesystem scanning when unavailable. --cache-rawwrites full directory/file path lists to/tmp/fzf-history-$USER/universal-last-{dirs,files}-<fish_pid>, allowing quick access to listed files with a fuzzy picker.-c,--counts– recursive directory/file count columns. Supports--sort dircountand--sort filecount.eza--git-reposand--gitare combined into a smart--gitflag that shows either or both columns when relevant- In
-X/--absolute,twigsplits the prefix and basename into separate hyperlinks, with the prefix styled in white. -x,--show-targets– explicit flag to display symlink targets (usable outside long mode).- Symlink targets are rendered/styled separately and can be hyperlinked independently. The hyperlink is also split; the prefix is coloured white and styled separately from the
LS_COLORSscheme. - Column order follows flag order from
argv, including compact short bundles.-lis expanded into orderedp,s,o,tat parse time, so later flags append after it. --headermoves to the bottom when-r(reverse) is used.-L– one file per line view.twigforce‑enables list mode when piped, when--headeris used, or when any detail columns are active.-a -Sshows.(not..) and gives.full recursive true size.-sintwigshows logical size of files and allocated blocks for directories and-Sshows allocated block size of files and true recursive sizes of directories.-H,--no-dedupe-hardlinks– toggle for-Shardlink deduplication.- Recursive totals include hidden descendants even when hidden entries are not displayed.
--git-fetchfetches the current repository when inside one, plus immediate child repository roots.
.
├── .cargo/
│ └── config.toml # local cargo build flags (target-cpu=native)
├── Cargo.toml # crate metadata and dependencies
├── Cargo.lock # locked dependency graph
├── src/
│ ├── main.rs # process entry point and module wiring
│ ├── app.rs # listing orchestration, sorting, entry construction and cache coordination
│ ├── cli.rs # Clap options, flag-order semantics and resolved display context
│ ├── fs_ops.rs # metadata, recursive size/count collection and NTFS/MFT paths
│ ├── git.rs # Git status, repository markers, remote state and fetch operations
│ ├── model.rs # shared entry and filesystem data structures
│ └── render.rs # fast paths, detailed/grid output, styling and hyperlinks
└── target/ # build artifacts (ignored in git)
src/main.rs- Starts the process and delegates to the application module.
src/app.rs- Collects one or multiple listing paths, sorts entries, builds display records, coordinates Git metadata, and emits output.
src/cli.rs- Defines Clap flags and preserves flag-order behavior for column order and implicit sorting.
src/fs_ops.rs- Reads metadata, computes recursive sizes/counts, and selects the NTFS MFT path or filesystem walker.
src/git.rs- Computes file status, repository markers, remote state, and
--git-fetchbehavior.
- Computes file status, repository markers, remote state, and
src/model.rs- Contains shared entry and mount data structures passed between modules.
src/render.rs- Owns the large-directory fast paths, list/grid rendering, LS_COLORS, hyperlinks, symlink targets, permissions, sizes, and raw path caches.
.cargo/config.toml- Enables
-C target-cpu=nativefor local optimized builds
- Enables
cargo build --releaseBinary:
./target/release/twig
./target/release/twig [OPTIONS] [PATH]Default path is ..
From twig --help:
-a, --alllist all files, including hidden- with
-S,.is shown but..is omitted - with implicit/default sort, injected dot entries are pinned to top; with explicit
--sort, they are sorted normally
- with
-A, --almost-alllist hidden but exclude.and..-l, --longshorthand for-Lptos --show-targets-L, --listforce one-entry-per-line list mode-d, --dirs-onlyshow only directories in the listed folder-n, --no-traverselist a directory entry itself (do not list its contents)-p, --permissionsshow permission bits-s, --sizeshow logical file size and allocated dir size-c, --countsshow recursive dir/file counts for directory entries; auto-sorts by total files + dirs ascending-o, --ownershow file owner-g, --groupshow group-t, --modifiedshow mtime-F, --classifyappend classifier (/,@,*, etc.)--sort <name|type|time|size|dircount|filecount>sort key (defaulttype;datealiasestime)-r, --reversereverse listing order-U, --hyperlink[=<always|auto|never>]render names as OSC8 hyperlinks-x, --show-targetsshow symlink target paths-X, --absoluteshow absolute paths in output-G, --gitsmart Git columns:- file staged/unstaged status when listing path is in a Git repo
- repo-root status markers when listed entries include Git repo roots
-1, --dereferenceuse symlink target size/time fields for-s/-S/-t-S, --true-sizeshow allocated file size + recursive allocated dir size; auto-sorts by size ascending-H, --no-dedupe-hardlinksdisable hardlink dedupe for-S-f, --git-fetchfetch the current and immediately nested Git repositories--headershow list headers (moved to bottom with-r)--color <always|auto|never>control ANSI color rendering--cache-rawwrite listed full paths for dirs/files to/tmp/fzf-history-$USER/...
For each invocation, twig runs roughly this pipeline:
- Parse CLI flags and build rendering context.
- Optionally precompute recursive stats when needed:
- recursive sizes for
-S - recursive counts for
-cand--sort dircount|filecount - root recursive total for injected
.in-a -S - on NTFS-like mounts, attempts MFT scan first and falls back automatically
- recursive sizes for
- Scan one directory level for displayed entries (
std::fs::read_dir). - Build per-entry metadata struct:
- file type
- symlink target/broken state
- size string + numeric sort size
- owner/group/time strings
- Resolve
--colorand--hyperlinkmodes:always: always enablednever: always disabledauto: enabled only on TTY and only when shown entry count is<= 1000
- Sort entries by selected key, apply reverse if requested.
- Optionally populate Git columns:
- file status pair when listing path is inside a Git repo
- repo-root cleanliness marker when listed entries include Git repo roots
- Optionally write raw path cache files (
--cache-raw). - Render all rows into one buffered
String. - Single
stdout.lock().write_all(...)write.
When the listing path is on ntfs, ntfs3, or fuseblk:
twigtries an MFT-based recursive scan via the underlying block device.- If that is unavailable (for example permission/device access),
twigfalls back to normal filesystem recursion.
Environment controls:
TWIG_NTFS_THREADS=<n>: overrides NTFS recursive scanner worker count.TWIG_NTFS_DEBUG=1: prints whether MFT fast path was enabled or unavailable.
twig intentionally separates logical size and on-disk size:
-
-s/--size- Files: logical byte size (
metadata.len()) - Dirs: allocated blocks for that directory entry (
st_blocks * 512) - With
-don symlink entries:- symlink -> file: logical target file size
- symlink -> dir: allocated blocks of target directory
- Files: logical byte size (
-
-S/--true-size- Files: allocated blocks (
st_blocks * 512) - Dirs: recursive allocated total (directory + descendants)
- In
-a -S, injected.shows the listing directory's full recursive total - With
-don symlink entries:- symlink -> file: allocated blocks of target file
- symlink -> dir: recursive allocated total of target directory
- Files: allocated blocks (
By default, -S deduplicates hardlinks by (dev, ino) while aggregating descendants.
- Default: dedupe on
-H/--no-dedupe-hardlinks: dedupe off
Column format is:
- left: staged/index state
- right: unstaged/worktree state
Status symbols:
-unmodified / no change in that sideMmodifiedAadded to indexNnew untracked (worktree)DdeletedRrenamedTtype-changeIignoredUunmerged/conflicted
Common combinations:
MMstaged + unstaged modificationsM-staged modified only-Munstaged modified onlyA-staged new fileAMstaged new file plus unstaged editsD-staged deletionR-staged renameUUconflict
Color mapping:
N/A: greenM/R/T: yellowD/U: red-/I: dimmed
Shown only for directory entries that are Git roots:
|clean repo (green)+dirty repo (red)~unknown status (yellow)
- Classifier for symlink entries uses
@. - Long mode prints
name@ -> target. - Target rendering:
- Prefix path segment before basename is forced white (
255,255,255) - Basename uses
LS_COLORS - If
-Fand target is a directory, target suffix/is shown
- Prefix path segment before basename is forced white (
- Broken symlink rows are highlighted with red background and white foreground.
- Hyperlink mode can hyperlink link name and target path independently.
- Entry names are styled from
LS_COLORS. - Directory type marker
din permissions now uses the same directory style as name rendering. - Symlink targets also resolve color via
LS_COLORSwhere metadata is available.
For --color=auto and --hyperlink=auto (including plain -U):
- output is disabled when stdout is not a TTY
- output is disabled when shown entry count is greater than
1000(files or dirs) alwaysbypasses these guards
When enabled, twig writes full absolute paths of displayed entries to:
-
Paths are lexically normalized (for example,
/home/user/./fileis written as/home/user/file) -
Dirs:
/tmp/fzf-history-$USER/universal-last-dirs-<fish_pid>
-
Files:
/tmp/fzf-history-$USER/universal-last-files-<fish_pid>
PID suffix resolution order:
fish_pidenvironment variable- parsed parent PID from
/proc/self/stat - current process PID fallback
- Compact mode (default): names separated by two spaces, single row
- Detailed mode: one row per entry when any of:
-L,-p,-s,-c,-o,-g,-t,-l,--git,-S,--header
Detailed row columns are assembled left-to-right as enabled:
- permissions
- size
- owner
- group
- modified time
- git status pair (
--git) - repo status marker (
--git) - styled name (and symlink target arrow when
-x/--show-targetsis active)
# Fast type-sorted top-level list
./target/release/twig
# Long view equivalent to -Lptos --show-targets
./target/release/twig -l ~/Dev
# Include hidden files and classify entries
./target/release/twig -aF ~
# List-only mode without metadata columns
./target/release/twig -L ~/Dev
# Show symlink targets in compact mode
./target/release/twig -x /home/lewis/.local/bin/twig
# True size with hardlink dedupe (default)
./target/release/twig -S ~/Downloads
# True size without hardlink dedupe
./target/release/twig -S -H ~/Downloads
# Git-aware long listing
./target/release/twig -l --git .
# Show Git root status for child directories (same --git flag)
./target/release/twig -l --git ~/src
# Reverse by date
./target/release/twig --sort time -r
# Emit shell cache files for last shown dirs/files
./target/release/twig --cache-raw ~/DevCore crates:
clapfor CLI parsingjwalkfor fast traversalrayonfor parallel recursive statsntfsfor MFT-based NTFS recursive scanninglscolors+nu-ansi-termfor stylingchronofor timestamp formattingusersfor uid/gid resolutionjemallocatorfor allocator performance
- Current listing depth is one directory level.
- Git status is computed via NUL-delimited
git status --porcelain=v1 -zrecords. - Repo-root marker is shown only for directories that are Git toplevel roots.
- Size units are decimal text with
K/M/Gsuffixes, one decimal above bytes. --cache-rawis disabled automatically when stdout is not a TTY.- NTFS MFT fast path generally requires raw block-device access (often root privileges).
cargo fmt
cargo build --release
cargo testUnit tests cover CLI aliases and mode separation, Git status translation, filesystem block rounding/hidden-name handling, and rendering path/size formatting. Larger output behavior is still checked with fixture directories and manual command checks.