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 through vim.g.fff.
  • Bridge (Lua + mlua)plugin/fff.lua loads the compiled fff_nvim module using the mlua Rust bindings.
  • Core Engine (Rust) – The crates/fff-nvim/src/lib.rs crate 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 FrecencyTracker and QueryTracker.

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 external fff crate, it provides fuzzy_search(), grep(), and refresh_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 to F2 by 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.lua automates binary downloading, while lua/fff/conf.lua manages configuration merging.
  • Public functions like find_files() and live_grep() in lua/fff/main.lua provide 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →