📈 GH Trending Explorer 🎲 Surprise me

← all repos

noahdunnagan / fsearch

git clone https://github.com/noahdunnagan/fsearch.git v0.2.1 · 10d ago

Whole-disk file search for macOS: fuzzy names, typo tolerance, indexed content grep. ~1 ms over 8M files.

+5
stars, 24h · #5,815
+~47
stars, 7 days · #4,252
+2.2k
stars, 30 days · #373
2.2k
★ total · +2% this week
138
forks · 5 open issues

Star history (as recorded here)

2026-10-20 · 2,153 ★ 2026-10-23 · 2,173 ★
LanguageRust
LicenseMIT
Created2026-10-08 (14d ago)
Last push10d ago
Watchers6
OwnerUser

Links

README excerpt
# FSearch
Whole-disk file search for macOS: names in about 0.1 ms, typo-tolerant,
plus indexed search inside files. A CLI with a small daemon, a Rust crate,
or an MCP server for coding agents.
 [code] 
Or skip the Rust toolchain with a release build for Apple Silicon:
 [code] 
## Speed
M4 Mac mini, a home holding the Linux or the Chromium source tree, vs the
previous version (b88f4e9). Medians; Linux / Chromium.
| | before | now |
|---|---:|---:|
| right file first, name search | 83% / 86% | 93% / 97% |
| right file first, two typos | 11% / 15% | 74% / 93% |
| find a file by name | 0.045 / 0.10 ms | 0.054 / 0.12 ms |
| an edited file greppable | 2.1 s | 1.3 ms |
| a rename or delete visible | 45 / 47 ms | 18 / 15 ms |
| first run, a name in a folder | 7.8 / 7.8 s | 0.18 / 1.2 s |
| first run, every match in the tree | 10.9 / 12.2 s | 2.4 / 10.8 s |
| files grep can search (rg's own set) | 96% / 92% | 99.9% / 99.8% |
| grep, first page | 0.29 / 0.48 ms | 0.33 / 0.65 ms |
| index on disk | 503 / 893 MB | 309 / 600 MB |
| peak memory while indexing | 1.13 / 1.37 GB | 0.77 / 0.94 GB |
| daemon memory | 19 / 100 MB | 21 / 35 MB |
The index is a third smaller while holding a third more text: one 3-bit
filter per file instead of two, compressed posting lists and paths, and
bit-packed name arrays read in place. Grep's first page reads a few more
files for it, and the deeper typo search costs a little on names.
## Queries
 [code] json
{"q": "fsearch main", "limit": 20, "offset": 20}
{"op": "grep", "pattern": "apply_dir", "in": "~/Developer", "context": 2}
{"q": "main.rs", "boost": true, "near": "~/Developer/app/src/lib.rs", "git": true}
{"op": "open", "path": "~/Developer/app/src/main.rs"}
{"op": "recent", "in": "~/Developer/app"}
 [code] rust
let engine = fsearch::Engine::start(fsearch::Options { dir: fsearch::default_dir(&home), home: home.clone(), skip: None })?;
let hits = engine.search(&fsearch::Query::parse("fsearch main", &home)?)?;
```
An app and the CLI share one index: the first process owns it, the rest
follow.
## How it works
- Crawls the disk once with `getattrlistbulk`, then follows FSEvents:
 changes show up in about 15 ms, and a restart replays only what changed.
 On a first run, a search in a folder indexes that folder first.
- Names live in one mmap'd file, folder by folder, so `in:` is a range. A
 bitmap of each name's letters rules out most names before scoring.
- Content search is a trigram index plus a per-file filter, so a search
 only reads files likely to match. Identical files are indexed once, and
 deleted files' entries are compacted away. Matches are read fresh from
 disk; files edited since the last index sync are read directly.
- The daemon runs with malloc's space-efficient mode, so it gives memory
 back when idle.