❯
waiting for input…
① Input — Lua UI Layer
1
LUA
▶
Keypress → Buffer Attach
picker_ui.lua · setup_keymaps()
User types in the floating input buffer. Neovim's
nvim_buf_attach(on_lines) fires on
every buffer change and schedules the search on the
main thread via vim.schedule().
data shape
-- on_lines callback args buf_id = 42 changedtick = 7 first_line = 0 last_line = 1
why vim.schedule?
Buffer-attach callbacks fire in a restricted
context. Scheduling defers the search until Neovim's
event loop is idle, preventing re-entrancy.
▼
2
LUA
▶
Extract Query String
picker_ui.lua · on_input_change()
Reads the input buffer, strips the prompt prefix
(e.g.
❯ ), trims whitespace, and stores
the clean query. Then calls
update_results_sync().
transform
"❯ scr/main" ↓ strip prompt + trim "scr/main" → M.state.query
▼
3
LUA
▶
Dispatch Mode & Build Context
picker_ui.lua · update_results_sync()
Determines which search path to take based on
M.state.mode. Calculates page size from
window height and builds call arguments.
branch logic
if M.state.mode == 'grep' then grep.search(query, offset, page_size, ...) else file_picker.search_files_paginated(query, ...) end
inputs computed here
query: string
page_size: number
current_file: path
min_combo_override
② FFI Bridge
▼
file picker path ↓
4
LUA
▶
FFI Call via fuzzy.so
rust/init.lua · fuzzy.fuzzy_search_files()
The compiled Rust shared library
(
libfff_nvim.so) is loaded via LuaJIT's
FFI. The Lua side calls the exported C-ABI function
directly — no subprocess or RPC overhead.
C ABI signature
fuzzy_search_files( query -- string max_threads -- usize current_file -- string | nil combo_boost_mult -- i32 min_combo_count -- u32 | nil page_index -- usize | nil page_size -- usize | nil )
why FFI not RPC?
Direct FFI eliminates serialization and socket
latency. Results come back as a Lua table in
microseconds, keeping search imperceptibly fast.
③ Rust Core — Parse & Route
▼
5
RUST
▶
FFI Entry — Acquire Locks & Parse Query
lib.rs · fuzzy_search_files()
Global state is protected by
RwLock.
This function acquires read locks on both
FILE_PICKER and
QUERY_TRACKER, then parses the raw
query string into a structured
FFFQuery.
locking
let picker = FILE_PICKER.read(); // shared read let tracker = QUERY_TRACKER.read(); // shared read
query parsing output
struct FFFQuery { fuzzy_parts: Vec<String>, // "scr/main" → ["scr", "main"] constraints: Vec<Constraint>, // *.rs, path:src/ location: Option<(u32, u32)>, // :42:7 }
▼
④ Rust Core — Fuzzy Match
6
RUST
▶
Query Tracker Lookup (combo boost)
file_picker.rs · fuzzy_search()
Before scoring, checks if the user has previously
typed this exact query and selected a file. If so,
that file gets a massive score multiplier (combo
boost).
combo boost lookup
tracker.get_last_query_entry( query, project_path ) → Option<QueryEntry> // if count ≥ min_combo_count: score *= combo_boost_multiplier // default 100×
example
Type
main → always picks
src/main.rs. After a few picks, the
tracker locks that file to the top for that query
string.
▼
7
RUST
▶
Frizbee Fuzzy Match (parallel)
score.rs · fuzzy_match_and_score_files()
Uses the neo_frizbee library to
match the query against every indexed file path in
parallel via Rayon threadpool. Each fuzzy part is
matched independently and averaged.
parallel match call
neo_frizbee::match_list_parallel_resolved( paths, // *const u8 pointers into arena query_part, // one fuzzy part max_typos, // query.len() / 4, clamped 2..6 ) → Vec<Match> // (index, score 0–65535) // multi-part: average across all parts base_score = parts.iter().sum() / parts.len()
memory layout
File paths live in a
ChunkedPathStore arena. Frizbee
receives raw pointers — zero copy. Overflow files
(added after initial scan) have their own arena.
⑤ Rust Core — Score Composition
▼
8
RUST
▶
Score Composition (7 factors)
score.rs
Each candidate file's final score is the sum of
multiple weighted components. The design lets
frecency nudge ranking without overwhelming match
quality.
score breakdown
base_score
frizbee match (0–65535)
filename_bonus
match on name vs. full path
frecency_boost
recent/frequent access weight
git_status_boost
modified files ranked up
distance_penalty
deeper paths rank lower
current_file_penalty
deprioritize open file
combo_match_boost
100× if query-history hit
exact_match flag
perfect match sentinel
then sort + paginate
items.sort_by(|a, b| b.score.cmp(&a.score)) result = items[offset .. offset + limit]
▼
consulted during scoring ↓
DB
▶
Frecency DB
frecency.rs · LMDB
decay formula
// 10-day half-life λ = 0.0693 score = Σ exp(−λ × days_ago) // recency multipliers <2 min → 16× <15 min → 8× <1 hr → 4×
LMDB gives lock-free concurrent reads — no
contention with background writes.
DB
▶
Query Tracker
query_tracker.rs · LMDB
stored per query
struct QueryEntry { file_path: String, count: u32, last_timestamp: u64, }
Key = hash(query + project_path). Enables
per-project query muscle memory.
⑥ Rust → Lua — Serialize Results
▼
9
RUST
▶
Convert SearchResult → Lua Table
lua_types.rs · SearchResultLua
The top-N results are serialized into a Lua table.
ChunkedString paths are resolved to full strings.
Score breakdowns are included for debug/display.
Lua table shape
{
items = {
{
relative_path = "src/main.rs",
name = "main.rs",
git_status = "M",
is_binary = false,
total_frecency_score = 650,
}, ...
},
scores = {
{ total=9500, base_score=5000, frecency_boost=1500, ... }
},
total_matched = 47,
total_files = 1234,
}
⑦ Lua — Update State & Render
▼
10
LUA
▶
Update State & Reset Cursor
picker_ui.lua · update_results_sync()
Stores results in
M.state, resets
cursor to item 1, checks for zero-result cross-mode
suggestions, then schedules a debounced render.
state mutations
M.state.items = results.items M.state.filtered_items = results.items M.state.pagination.total_matched = n M.state.cursor = 1 -- best result M.state.location = loc -- :line:col M.render_debounced()
zero-result fallback
If file mode returns 0 results, silently runs a grep
suggestion and shows it as a hint below the empty
list.
▼
11
LUA
▶
Render List to Buffer
picker_ui.lua · render_list()
Iterates results in display order (reversed if
prompt_position="bottom"), writes lines
into list_buf, and applies extmarks for
icons, git indicators, and highlight groups.
per-item render
for i, item in ipairs(display_items) do -- icon + name + path + git badge renderer.render_file_item(buf, row, item) -- highlight matched chars set_extmark(buf, ns, row, col, hl) end render_scrollbar(win, total, offset)
combo separator
If the top result is a combo-boosted item, a visual
separator is drawn between it and the regular
results.
▼
12
LUA
▶
Update Preview Pane
file_picker/preview.lua · update_preview()
Debounced 100 ms. Reads the highlighted file,
renders it with syntax highlighting into the preview
buffer, and jumps to
line:col if the
query contained a location suffix.
debounce
-- avoid expensive reads on every keypress vim.defer_fn(function() read_and_highlight_file(item.path) end, 100)
⑧ Threading & Storage Overview
Threads
Storage
Perf tricks
| Component | Model | Notes |
|---|---|---|
| Lua search dispatch | Neovim main thread | Non-blocking, scheduled |
| Fuzzy match | Rayon threadpool | max_threads from config |
| Grep search | Rayon threadpool | early-exit after page_limit |
| Frecency/QT reads | Shared read locks | Concurrent, no writer wait |
| File system watcher | Dedicated bg thread | Async inotify/FSEvents |
| Initial scanner | bg threadpool | Brief write lock at merge |
| Store | Engine | Key → Value |
|---|---|---|
| Frecency DB | LMDB | path_hash → Vec<timestamp> |
| Query Tracker | LMDB | hash(query+project) → QueryEntry |
| File index | In-memory Vec | ChunkedPathStore arena |
| Bigram index | In-memory | bigram → file_id bitset |
| Git cache | In-memory HashMap | path → git::Status |
| Technique | Benefit |
|---|---|
| ChunkedPathStore arena | Zero-copy path pointers to frizbee |
| Bigram inverted index | Skip non-matching files before grep |
| LMDB memory-mapped | Lock-free concurrent frecency reads |
| mmap file I/O (grep) | Zero-copy file reading |
| Frecency-ordered grep | Best files searched first, early exit |
| StableVec snapshots | Post-scan index access without copy |
| 100 ms preview debounce | Avoid file reads on every keypress |