CLI tool
natecraddock/zf avatar
natecraddock/zf

zf: a fuzzy finder that treats filenames as the important part of a path

a commandline fuzzy finder and zig module designed for filtering filepaths

607 stars26 forksZigMIT

At a glance

What is it?
zf is a fuzzy finder in Zig that ranks matches on the filename before the path, adds strict path matching when the query looks like a path, and ships both Zig and C libraries for the ranking algorithm. It suits someone filtering file lists, and its author asks contributors to keep the project small.
Who is it for?
zf is worth installing if your fuzzy finding is mostly about files, because the ranking prioritises the filename over the path and the strict path matching rules out the near-misses that make other finders noisy on a real codebase, and because the ranking algorithm is available as a Zig and C library for anyone writing their own picker.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 19 days ago.
What is it written in?
Mainly Zig, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Filenames are usually unique, paths are not

The design premise of zf is stated in one line: filenames are usually unique. That observation leads directly to the ranking rule, and it explains why zf behaves differently from the fuzzy finders people reach for by default.

A fuzzy finder that scores the whole string will happily put ./GNUmakefile above ./source/blender/makesdna/DNA_genfile.h when the query is make, because the shorter path scores better on a naive length preference. The example in the project documentation comes from Blender's source code and was the original inspiration for the design.

zf matches on the filename first and only falls back to the path if the filename does not match. In that Blender example, Fzf and fzy both rank source/blender/makesdna/DNA_genfile.h first with GNUmakefile ten items down the list, while zf puts the DNA_genfile.h first and GNUmakefile below it.

So the difference in behaviour comes from a change of what is being scored, not from a cleverer scoring formula. That is worth understanding before you compare zf against another finder, because most of the perceived precision is this one reordering.

The goal stated in the project description is to be more accurate than other fuzzy finders when filtering filepaths, while still working as a general-purpose fuzzy finder for arbitrary strings. The tool is a Zig program with an MIT licence, and the topics list the usual set for a Unix fuzzy search utility.

Strict path matching turns a path query into a structural filter

The second feature is the one the documentation describes as a do what I mean feature, more easily used than explained.

Strict path matching is enabled automatically when the query looks like a path, which the documentation defines as containing at least one path separator. The rule is that the path segments of the query term must not span across path segments in the candidate string. Path segments are the portions of a path delimited by path separators, so foo/bar has the segments foo and bar.

The example given is precise. With these paths:

text
./app/models/foo/bar/baz.rb
./app/models/foo/bar-baz.rb
./app/models/foo-bar-baz.rb
./app/monsters/dungeon/foo/bar/baz.rb

the query a/m/f/b/baz returns only ./app/models/foo/bar/baz.rb. Other fuzzy finders also return ./app/monsters/dungeon/foo/bar/baz.rb, and strict path matching prevents that because there is a slash between onsters/dungeon and nothing in the query matches the dungeon segment.

The characters foo have to appear inside a single path segment. That is a stronger constraint than fuzzy matching normally imposes, and it is what makes the result correct rather than merely plausible.

To end strict path matching, you add a space to start a new query term. That detail connects the feature to the third one.

So the tool has three matching behaviours that interact: filename priority, strict path matching, and space separated terms. A user who understands how they compose can predict the output, which is the property that makes a finder usable rather than merely impressive.

Whitespace separated terms, and the smartcase convention

Not every filename is unique, and that is the case the second feature addresses. The project documentation names the example directly: codebases with many files of the same or similar name, such as an __init__.py in Python, or .c and .h file pairs in C.

The solution is to treat the query string as a sequence of terms, and each term narrows the results further. Searching for init in a Python project gives the root __init__.py plus the ones under ui, data and config. At that point you can either move the selection down with the Down key or c-n, or you can add a second term. Adding c gives:

text
> init c
./config/__init__.py

The documentation's summary is that treating the query string as a sequence of terms makes filtering more efficient.

The other convention in the feature list is smartcase, which is case insensitive unless the query contains uppercase letters. That is the same convention ripgrep and fzf use, and it is worth adopting in your own tool if you write one, because it lets a user type a capital letter to force a case sensitive search without needing a separate mode switch.

The remaining features are the interface ones rather than the matching ones: multiselect to output multiple selected lines, and a preview window. The output side matters more than it sounds, because a finder that can only print one result forces you to run it again for the second thing you wanted, which is the difference between a tool you keep using and one you reach for occasionally.

Installing it: five package managers, one build command

The distribution story is broad for a small utility, and it covers the platforms people actually run finders on.

Arch Linux has an AUR package. Void Linux installs from its official repository:

bash
sudo xbps-install zf

macOS uses Homebrew:

bash
brew install zf

Nix has two routes, the classic environment install and the newer profile install:

bash
nix-env --install zf
bash
nix profile install nixpkgs#zf

Each release also has binaries attached for macOS and Linux, which is the route for anyone not on a package manager.

Building from source needs Zig, and the project is explicit about the constraint: for compatibility with system package managers, zf targets the latest stable release of Zig. The build is:

bash
git clone https://github.com/natecraddock/zf
cd zf
zig build -Doptimize=ReleaseSafe --summary all

The executable lands in ./zig-out/bin/zf, and for a debug build you omit -Doptimize=ReleaseSafe. The --summary all flag asks the build system to print a summary of what it did, which is a Zig build system feature worth knowing about because it tells you what was compiled and what was cached.

The repository layout confirms the build setup: build.zig and build.zig.zon at the root, src/ for the source, complete/ for shell completions, doc/ for the two documents the project links, a flake.nix and flake.lock for the Nix route, and a CHANGELOG.md. The project also carries a packaging status badge from repology, which tracks how many distributions carry a package.

The ranking algorithm is a library, and the C binding is the interesting part

Alongside the command line tool, zf is available as an allocation-free library for fuzzy filtering, with the details in a separate library document. The feature list names it as Zig and C libraries for the zf ranking algorithm, which is a stronger claim than simply exposing the same code through a language binding.

Allocation-free is the part that matters for a library. A finder that allocates on every keystroke is fine as an interactive program and painful as a component inside something else, because the caller usually wants to reuse buffers across a render loop. An allocation-free ranking routine can be called from a tight loop without pushing pressure on the allocator.

The C library is the more unusual of the two. A Zig project exporting a C-compatible API makes the ranking routine usable from anything with a C compiler, which covers a lot of ground that a Zig-only library does not. It also means the algorithm can be embedded in an editor or a launcher written in a language with no Zig toolchain, which is where the integrations come in.

Two editor integrations are listed. zf.vim integrates zf with vim for fuzzy file finding, and is described as similar to fzf.vim. telescope-zf-native.nvim is a neovim telescope extension that overrides the default Lua sorter with zf. Both are the pattern to look for: the ranking algorithm in the tool, the interface in something else.

There is also a browser playground linked from the project documentation, which lets you try the matcher against sample data without installing anything, and a linked blog post about the search for a better finder that explains the data collection behind the filename-priority idea.

Where zf is the wrong tool

The first limit is the obvious one: zf is the matching half of a fuzzy finder, not the whole application. It filters a list and prints the selection. It does not give you an interactive prompt with a scrolling preview pane, key handling, history or multi-select interaction, in the way fzf does. The feature list mentions multiselect and a preview window, so some of that is present, but the project's own framing is that it is a fuzzy finder for filepaths first and a general-purpose one second, and the interactive surface is not the selling point.

The second is the interaction model implied by the space separated terms. You narrow by typing terms, and you end a strict path match by adding a space. That works well when you know roughly what you are looking for and want the ranking to do the work. It is a different interaction from a live-updating fuzzy list where you watch the results move as you type, and people who prefer the latter will not find zf a substitute.

The third is the toolchain. Targeting the latest stable Zig release is reasonable and it keeps the package managers happy, but it means a project pinned to an older Zig cannot build it without work, and it puts zf in the same boat as any other Zig-dependent tool. There is a prebuilt binary for macOS and Linux on each release, which covers most cases, but there is no Windows binary mentioned.

The fourth is the release line versus the branch. The recent tags are 0.10.0 from 2024-10-02, 0.10.1 from 2024-10-29 and 0.10.2 from 2024-11-12, while the last push to the repository was on 2026-10-04. The package managers are likely tracking something in that older series, so the version you get from a distribution and the version on the branch are probably not the same code.

The fifth is the maintenance stance, which is stated plainly: the author is open to contributions of all kinds, but wants to keep zf small and easy to maintain. That is a healthy constraint for a utility, and it also means a feature request is more likely to be declined than accepted. A team that wants a heavily customisable finder with plugins, layouts and a scriptable configuration language has picked the wrong tool.

Editorial conclusion

zf is worth installing if your fuzzy finding is mostly about files, because the ranking prioritises the filename over the path and the strict path matching rules out the near-misses that make other finders noisy on a real codebase, and because the ranking algorithm is available as a Zig and C library for anyone writing their own picker. It is the wrong tool if you want a general fuzzy finder with a full terminal UI, since zf is the matching half of that idea rather than the whole application. Before you adopt it, check the three things the project tells you: that it targets the latest stable Zig release, which matters if your toolchain is older; that the latest tagged release is 0.10.2 from 2024-11-12 while the last push was on 2026-10-04, so the packaged versions lag the branch; and that the author wants the project kept small and easy to maintain, which means a feature request is more likely to be declined than accepted.

Frequently asked questions

How do I install zf?

There is an AUR package for Arch Linux, a Void Linux package installed with sudo xbps-install zf, a Homebrew formula for macOS installed with brew install zf, and two Nix routes using nix-env --install zf or nix profile install nixpkgs#zf. Each release also ships binaries for macOS and Linux, and building from source needs Zig with zig build -Doptimize=ReleaseSafe.

What does strict path matching do in zf?

It is enabled when the query contains a path separator, and it requires the path segments of the query term to appear within a single path segment of the candidate. So the query foo/ matches foo/bar/ but not fo/obar/, and the query a/m/f/b/baz matches only ./app/models/foo/bar/baz.rb rather than a path where the same letters are spread across different directories.

Can zf be used as a library?

Yes. zf is available as an allocation-free library for fuzzy filtering, and the feature list names Zig and C libraries for the ranking algorithm. There are editor integrations built on it, including zf.vim for vim and telescope-zf-native.nvim, a neovim telescope extension that overrides the default Lua sorter.

Official sources

  1. Issues
  2. License: MIT
  3. natecraddock/zf on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/natecraddock-zf.svg)](https://hysenlabs.com/projects/natecraddock-zf)

Was this useful?