A modern ls alternative written in Zig.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-28 11:05:53 +08:00
.github/workflows refactor: rename executable from ls to zl 2026-02-13 14:33:29 +08:00
docs feat: Added more symlink config and made behavior more consistent 2026-08-28 11:05:53 +08:00
pics docs: update screenshot image 2026-07-20 16:08:19 +08:00
src feat: Added more symlink config and made behavior more consistent 2026-08-28 11:05:53 +08:00
.gitignore build: switch clap to zig package dependency in build.zig.zon 2026-04-11 13:17:01 +08:00
build.zig fix(build): lazy-load clap and exclude CLI code from package consumers 2026-07-01 17:27:10 +08:00
build.zig.zon chore: bump project version to 0.1.13 2026-08-27 16:00:22 +08:00
LICENSE Initial commit 2026-01-21 16:38:19 +08:00
README.md docs: document configurable icons and colors via .zon files 2026-08-25 09:21:39 +08:00

zlist

A modern, colorful alternative to ls built with Zig.

Build Status Zig Version License

Note: This is my first CLI tool in Zig! 🚀

I built this project to learn Zig, get comfortable with manual memory management, and explore the standard library. It might not be the fastest or smallest ls clone (yet), but it's usable today and still getting better.

Table of Contents

Features

Already pretty capable for a learning project:

  • Compact grid layout that stays easy to scan.
  • Color and Nerd Font icons for common file types and languages.
  • Readable long view with permissions, owner, size, and timestamps.
  • Optional recursive directory size in long view and size sorting.
  • Multiple sort modes including name, length, directories first, mtime, and size.
  • Recursive listing with optional depth limits.
  • Useful filters for files, directories, extensions, names, size, and modified time.
  • Quick summary report for file and folder counts.
  • Git status indicators in long view.
  • Configurable icons and colors via a .zon file (-C).

Preview

Preview1 Preview2 Preview3

(Make sure you have a Nerd Font installed in your terminal to see the icons!)

Installation

macOS

Install with Homebrew:

brew tap here-leslie-lau/tap
# Maybe need trust
brew install zlist

Precompiled Binaries

Download the latest binary for your system from the Releases page.

Note

: Windows is currently not supported due to differences in file system APIs. Support may be added in future versions.

From Source

Requirements: zig (master/0.17.0-dev recommended).

# 1. Clone the repo
git clone git@github.com:here-Leslie-Lau/zlist.git
cd zlist

# 2. Build in release mode [ReleaseFast, ReleaseSafe, ReleaseSmall]
zig build -Doptimize=ReleaseFast

# 3. Run it. (Optional: add to PATH, it's up to you.)
./zig-out/bin/zl

Usage

Just run:

zl [OPTIONS] [PATH]
$ zl --help
    -h, --help
            Usage: zl [OPTIONS] [PATH]...

    -l, --long
            Show the long view.

    -H, --header
            Show header in the long view.

        --no-permissions
            Hide permissions from the long view.

        --no-user
            Hide user from the long view.

        --no-group
            Hide group from the long view.

        --no-size
            Hide size from the long view.

        --no-time
            Hide time from the long view.

        --no-icon
            Hide icon from the long view.

    -a, --a
            Include hidden entries.

        --du
            Show recursive directory size in long view and size sort. This is the sum of file sizes, not the same as `du` disk usage.

        --dir-grouping <DIRGROUPING>
            Group directories before or after files. Default: none. OPTIONS: none, before, after.

    -s, --sort <SORTTYPE>
            Sort results. Default: name. OPTIONS: name, length, mtime, size.

        --reverse
            Reverse sort.

        --size <str>...
            Filter files by size range (e.g. --size gt:10K --size lte:2M).

        --changed-within <str>
            Only show entries changed within a time range (e.g. --changed-within 7d).

    -r, --recursive
            Recurse into subdirectories. Same as -L 0.

    -L, --level <INT>
            Limit recursion depth. 0 means no limit.

        --root-display <ROOTDISPLAY>
            Changes how root dir is displayed in recursive view. Default: dot. OPTIONS: dot, name, none.

    -p, --pure
            Show names only, without colors or icons.

        --color <COLORUSE>
            When to use terminal colors. Default: auto. OPTIONS: auto, always, never.

    -R, --report
            Show a short summary of files and folders.

    -d, --dir
            Only show directories. If used with -D, both are ignored.

    -D, --no_dir
            Only show files. If used with -d, both are ignored.

    -g, --git
            Show git status in long view.

    -e, --ext <str>...
            Filter by extension (e.g. --ext zig,md,ts).

    -m, --match <str>...
            Filter names by substring (e.g. --match main,readme).

    -C, --config <str>
            Load config from a .zon file.

    <str>...

For common commands examples, see zlist examples.

Icons and colors are configurable too:

zl -C zlist.zon

See config for a sample file.

Use as a Zig Module

zlist can also be used from another Zig project when you want the listing data without the CLI output.

Add it with Zig's package manager:

zig fetch --save git+https://github.com/here-Leslie-Lau/zlist

Then expose the module in your build.zig:

const zlist_dep = b.dependency("zlist", .{
    .target = target,
    .optimize = optimize,
});

exe.root_module.addImport("zlist", zlist_dep.module("zlist"));

Use it from code:

const std = @import("std");
const zlist = @import("zlist");

fn printNames(allocator: std.mem.Allocator, io: std.Io, dir: std.Io.Dir) !void {
    var files = try zlist.Files.init(allocator, io, dir, .{ .path = "." });
    defer files.deinit();

    for (files.entries()) |entry| {
        std.debug.print("{s}\n", .{entry.name});
    }
}

For options, ownership rules, and more examples, see Using zlist as a module.

Benchmark

hyperfine on macOS (Apple M4). Build: zig build -Doptimize=ReleaseFast.

Trying to keep the comparison fair — colored grid-style listing on all three:

zl <dir>
eza --icons always --color always --grid <dir>
lsd --color always <dir>

Each <dir> was a throwaway folder with n small text files (file_0.txt …).

Tool n=50 n=500 n=5000 n=50000
zl 1.2 ms ± 0.4 ms [U 0.6 / S 0.4] 1.7 ms ± 0.6 ms [U 0.9 / S 0.6] 5.9 ms ± 0.6 ms [U 3.3 / S 2.0] 49.9 ms ± 1.4 ms [U 32.2 / S 16.8]
eza 3.7 ms ± 0.2 ms [U 2.3 / S 0.9] 5.6 ms ± 0.8 ms [U 3.4 / S 1.9] 21.2 ms ± 0.6 ms [U 11.6 / S 8.9] 176 ms ± 4.4 ms [U 107 / S 66.9]
lsd 3.6 ms ± 0.5 ms [U 1.6 / S 1.6] 13.5 ms ± 2.9 ms [U 2.9 / S 9.9] 112 ms ± 1.9 ms [U 13.5 / S 97.2] 1.293 s ± 0.057 s [U 0.12 / S 1.16]

zl wins every column here. Gap vs lsd blows up on big dirs; vs eza it's a steadier ~3×. Still plenty of CPU left on the table at 50k (zl user time ~32ms) if we want to push further.

Older README numbers had eza stuck near ~3ms even at 50k — that was a bad run on my side (wrong flags / sloppy setup). Ignore those; this table replaces them.

Numbers move with cache, load, and FS. YMMV.

Roadmap

  • Basic file listing & recursion
  • Color output & Nerd Font icons
  • Detailed file stats
  • Sorting by name (default), length, and modification time
  • Recursive directory traversal (-r)
  • Depth control for recursion (-L)
  • Clean output mode (-p)
  • Filter by files or directories (-d, -D)
  • Extension filter (-e, --ext)
  • Name match filter (-m, --match)
  • Smart dynamic grid layout
  • Summary report (-R)
  • Git status integration (-g)
  • Recursive directory size (--du)
  • Lib API for embedding in other Zig projects
  • Custom color/icon configurations (-C)
  • Multi-threading for faster stat calls

Contributing

Got an idea? Found a bug? Open an issue or send a PR. This is a fun side project, and contributions are always welcome.

  1. Fork it
  2. Create your feature branch (git checkout -b feature/cool-thing)
  3. Commit your changes
  4. Push to the branch
  5. Open a Pull Request

Crafted with ❤️ in Zig.