Neovim Fuzzy File Finder with Rust Backend: Complete fff.nvim Architecture Guide
fff.nvim is a high-performance fuzzy file picker for Neovim that delegates indexing, matching, and grep operations to a native Rust engine while providing a rich Lua UI for configuration and interaction.
fff.nvim (by dmtrKovalenko/fff.nvim) reimagines file discovery in Neovim by combining a lightweight Lua frontend with a compiled Rust core. This architecture delivers sub-millisecond fuzzy matching on massive codebases while maintaining the configurability expected by modern Neovim users. The plugin splits responsibilities across clear architectural layers, from floating-window rendering in Lua to parallel filesystem scanning in Rust.
Architecture Overview
fff.nvim employs a hybrid architecture that separates presentation from computation. The design ensures that heavy operations—such as indexing thousands of files or computing fuzzy match scores—never block the Neovim UI.
The Layered Stack
The plugin consists of four distinct layers:
- Neovim UI (Lua) – Handles floating windows, keymaps, preview rendering, and user interaction via
lua/fff/picker_ui.lua. - Configuration (Lua) – Merges user settings with defaults in
lua/fff/conf.lua, exposing options throughvim.g.fff. - Bridge (Lua + mlua) –
plugin/fff.lualoads the compiledfff_nvimmodule using the mlua Rust bindings. - Core Engine (Rust) – The
crates/fff-nvim/src/lib.rscrate performs indexing, fuzzy matching, live grep, and Git integration on background threads. - Persistence (LMDB) – Rust manages frecency data and query history using LMDB databases via
FrecencyTrackerandQueryTracker.
Installation and Binary Management
Unlike pure Lua plugins, fff.nvim requires a compiled native binary. The plugin automates this process during initialization.
When Neovim sources plugin/fff.lua, it executes:
require('fff.download').download_or_build_binary()
require('fff').setup(user_config)
This ensures the Rust library is available before the UI attempts to load it. For lazy.nvim users, the recommended configuration handles the build step explicitly:
{
'dmtrKovalenko/fff.nvim',
build = function()
require('fff.download').download_or_build_binary()
end,
opts = {
debug = { enabled = true, show_scores = true },
keymaps = { close = '<Esc>', select = '<CR>' },
},
keys = {
{ 'ff', function() require('fff').find_files() end, desc = 'FFF Find files' },
{ 'fg', function() require('fff').live_grep() end, desc = 'FFF Live grep' },
},
}
The Rust Core: Performance and Features
The Rust backend, exposed as the fff_nvim Lua module, handles all computationally expensive operations. It is compiled as a shared library using mlua's #[mlua::lua_module] attribute in crates/fff-nvim/src/lib.rs.
Exported Functions
The Rust side exports a table of functions that the Lua frontend calls via require('fff_nvim'):
| Function | Purpose |
|---|---|
init_db |
Opens LMDB databases for frecency and query history persistence. |
init_file_picker |
Instantiates the global FILE_PICKER with the project's base path. |
fuzzy_search_files |
Runs the fuzzy matcher on indexed files, applying combo-boost, frecency, and Git status scores. |
live_grep |
Performs fast content search using the fff-grep crate, respecting size limits and search mode. |
track_access / track_query_completion |
Updates frecency and query history asynchronously on background threads. |
health_check |
Returns version, Git detection, DB health, and picker status for :FFFHealth. |
Core Data Structures
Three global statics manage state across Neovim sessions:
FilePicker– Maintains the in-memory file index, background scan thread, and Git status cache. Defined in the externalfffcrate, it providesfuzzy_search(),grep(), andrefresh_git_status()methods.FrecencyTracker– LMDB-backed scoring based on file open frequency and recency.QueryTracker– Stores successful search patterns for history navigation.
These structures live in once_cell::sync::Lazy globals (FILE_PICKER, FRECENCY, QUERY_TRACKER) and are protected by RwLock to allow concurrent reads during searches while ensuring thread-safe writes.
Lua Frontend and Configuration
The Lua layer focuses on user experience and configuration management, deferring all heavy lifting to the Rust core.
Configuration Handling
lua/fff/conf.lua merges user options with sensible defaults, handles deprecated settings, and creates highlight groups. It provides a get() accessor used throughout the codebase to retrieve settings like max_results or layout dimensions.
The configuration is stored globally in vim.g.fff after fff.setup() is called, making it accessible to both Lua and Rust components.
UI Implementation
lua/fff/picker_ui.lua constructs the floating window interface using vim.api.nvim_open_win(). It draws the result list, preview pane, and scrollbar, then wires keymaps defined in the user configuration. When a user types a query, the UI calls fff_nvim.fuzzy_search_files() and renders the returned results without blocking the editor.
Public API Entry Points
lua/fff/main.lua defines the user-facing functions find_files() and live_grep(). Each function lazily loads the UI module and forwards requests to the Rust engine:
local picker_ok, picker_ui = pcall(require, 'fff.picker_ui')
if picker_ok then picker_ui.open(opts) else vim.notify(...) end
Available Commands and API
fff.nvim exposes several Ex commands for direct interaction:
:FFFScan– Forces a rescan of the current directory index.:FFFRefreshGit– Updates Git status for all indexed files.:FFFClearCache [all|frecency|files]– Removes cached data from LMDB.:FFFHealth– Displays diagnostics including Rust version, DB status, and scan progress.:FFFDebug– Toggles inline score display (also bound toF2by default).:FFFOpenLog– Opens the log file at~/.local/state/nvim/log/fff.log.
For programmatic access, call the Lua API directly:
require('fff').setup({
base_path = vim.fn.getcwd(),
max_results = 200,
layout = { height = 0.9, width = 0.7, preview_position = 'right' },
})
require('fff').find_files({ title = 'My project files' })
require('fff').live_grep({ query = 'TODO' })
Advanced: Accessing the Rust API Directly
Advanced users and plugin developers can bypass the UI layer and call the Rust functions directly via the fff_nvim module:
local fff = require('fff_nvim')
-- Initialize databases and picker
fff.init_db(
vim.fn.stdpath('cache')..'/fff_nvim',
vim.fn.stdpath('data')..'/fff_queries',
false
)
fff.init_file_picker(vim.fn.getcwd())
-- Perform raw fuzzy search
local result = fff.fuzzy_search_files('main', 4, nil, 100, nil, nil, 20)
print(vim.inspect(result))
This low-level access is useful for debugging scoring algorithms or integrating fff.nvim's engine into custom picker implementations.
Summary
- fff.nvim combines a Lua frontend (
lua/fff/picker_ui.lua) with a Rust backend (crates/fff-nvim/src/lib.rs) to deliver high-performance fuzzy finding. - The Rust core handles indexing, fuzzy matching, grep operations, and frecency tracking on background threads, storing data in LMDB.
plugin/fff.luaautomates binary downloading, whilelua/fff/conf.luamanages configuration merging.- Public functions like
find_files()andlive_grep()inlua/fff/main.luaprovide the primary interface. - The plugin supports direct Rust API access via
require('fff_nvim')for advanced use cases.
Frequently Asked Questions
How does fff.nvim compare to Telescope for Neovim?
fff.nvim prioritizes raw performance by delegating all search operations to a compiled Rust engine rather than processing results in Lua. According to the source code, the Rust core scans filesystems in parallel and maintains in-memory indices, delivering sub-millisecond query responses even on large repositories. Telescope, while more extensible, executes its matching logic within Neovim's Lua interpreter, which can lag on massive file lists.
What is frecency tracking in fff.nvim?
Frecency is a ranking algorithm that combines frequency (how often you open a file) and recency (when you last opened it). The FrecencyTracker struct in the Rust backend persists this data to an LMDB database via track_access() calls. When you search, fuzzy_search_files() boosts scores for files you access frequently or recently, surfacing relevant results faster.
How does the Rust backend communicate with Neovim?
The communication uses the mlua crate, which allows exporting Rust functions as a native Lua module. In crates/fff-nvim/src/lib.rs, the #[mlua::lua_module] attribute exposes a table of functions (like fuzzy_search_files and live_grep) that Lua code calls via require('fff_nvim'). The Lua frontend in lua/fff/picker_ui.lua invokes these functions when the user types queries, passing strings and receiving structured results without serializing through JSON or RPC.
Can I use fff.nvim without the floating window UI?
Yes. While lua/fff/picker_ui.lua provides the default floating window interface, you can call the Rust backend directly through require('fff_nvim') as shown in the advanced usage examples. This allows integration with alternative UI plugins or custom command-line workflows while still leveraging the Rust engine's indexing and fuzzy matching capabilities.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →