# Understanding fff.nvim Architecture: A Deep Dive into the Rust-Lua File Picker

> Explore fff.nvim architecture, blending Rust performance with Lua flexibility for lightning-fast file picking in Neovim. Discover its unique FFI communication.

- Repository: [Dmitriy Kovalenko/fff.nvim](https://github.com/dmtrKovalenko/fff.nvim)
- Tags: deep-dive
- Published: 2026-04-04

---

**fff.nvim combines a lightweight Lua frontend for UI and configuration with a high-performance Rust backend for indexing and fuzzy searching, communicating through a minimal FFI layer to deliver sub-second file picking in Neovim.**

Understanding the architecture of `fff.nvim` reveals how this Neovim plugin achieves high-performance file picking by separating concerns between a responsive Lua interface and a compiled Rust engine. The repository `dmtrKovalenko/fff.nvim` structures its codebase into distinct layers that handle everything from lazy initialization to floating window management, all while maintaining a clean separation between presentation and search logic.

## Plugin Entry Point and Lazy Initialization

The architecture begins at [`plugin/fff.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/plugin/fff.lua), which serves as the entry point for Vim script loading. This file prevents double-loading by checking `vim.g.fff_loaded` and defers heavy initialization until after the `UIEnter` event.

The initialization logic detects whether Neovim has already started:

```lua
if vim.v.vim_did_enter == 1 then
  init()
else
  vim.api.nvim_create_autocmd('UIEnter', {
    callback = init,
  })
end

```

This design ensures the Rust backend only starts when the user interface is ready. The `init()` function eventually calls `require('fff.core').ensure_initialized()`, which triggers the full startup sequence and registers user commands like `:FFFFind` and `:FFFScan`.

## Core State Management

The [`lua/fff/core.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/core.lua) module maintains global state and orchestrates the relationship between Lua and Rust. It stores critical flags in a `state` table, including `initialized`, `file_picker_initialized`, and the Rust fuzzy object reference (`fuzzy`).

The `ensure_initialized()` function performs three critical operations:

1. Loads user configuration and merges it with defaults
2. Starts the Rust database via `fuzzy.init_db`
3. Initializes the file-picker backend through `fuzzy.init_file_picker`

Additionally, `setup_global_autocmds()` registers event listeners for directory changes and buffer access patterns, enabling real-time frecency tracking and automatic index updates when `DirChanged` events fire.

## Configuration System

Configuration handling lives in [`lua/fff/conf.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/conf.lua), which implements a singleton pattern for settings management. The module defines a complete `FffConfig` schema covering base paths, UI layout dimensions, preview settings, keymaps, and Git integration options.

The system supports backward compatibility through `handle_deprecated_config()`, which automatically migrates legacy top-level options—such as moving `width` to `layout.width`—while emitting deprecation warnings. User settings are accessed throughout the codebase via `require('fff.conf').get()`, ensuring consistent configuration resolution without global variable pollution.

## The Rust Bridge and FFI Layer

The [`lua/fff/fuzzy.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/fuzzy.lua) file acts as the sole bridge to the compiled Rust library. It loads the dynamic library through `require('fff.rust')` and re-exports every native function for use by higher-level Lua modules.

Key Rust functions exposed to Lua include:

- `fuzzy.fuzzy_search_files` – Performs fuzzy matching with frecency scoring
- `fuzzy.live_grep` – Executes content search with regex and fuzzy modes
- `fuzzy.scan_files` – Asynchronous filesystem indexing
- `fuzzy.init_db` – Database initialization and persistence

This FFI layer keeps the Lua codebase lightweight and testable while delegating all CPU-intensive operations—such as fuzzy scoring algorithms and Git status tracking—to the optimized Rust backend.

## File Search Implementation

The [`lua/fff/file_picker/init.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/file_picker/init.lua) module provides the high-level API for file searching. It wraps the Rust `fuzzy_search_files` function while handling pagination, combo-boost overrides, and thread count configuration.

When `search_files_paginated()` receives a query, it forwards the request to Rust with parameters for maximum results and scoring preferences. The Rust engine returns results already sorted by relevance (combining fuzzy match scores and frecency data), eliminating the need for Lua-side sorting. Additional functions like `get_search_metadata()` and `track_access()` expose indexing statistics and update access patterns for future scoring calculations.

## Live Grep Implementation

Content searching follows a similar pattern in [`lua/fff/grep/init.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/grep/init.lua). The `search()` function accepts a query string, file offset for pagination, page size, and grep mode (plain, regex, or fuzzy), then delegates to `fuzzy.live_grep`.

The Rust backend returns a structured `SearchResult` containing:

- `items` – Array of matched lines with file paths and line numbers
- `total_matched` – Total hit count for pagination calculations
- `next_file_offset` – Cursor for fetching subsequent pages
- `regex_fallback_error` – Optional error when regex compilation fails

The UI layer can cycle between search modes using `:FFFDebug cycle_grep_modes`, with the Lua module handling mode state while Rust executes the actual text matching across indexed files.

## UI Rendering and Layout Management

All visual components are managed by [`lua/fff/picker_ui.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/picker_ui.lua), which remains entirely decoupled from search algorithms. This module calculates window geometry through `compute_layout(config)`, converting relative dimensions (like `width = 0.9`) into absolute terminal coordinates.

The `build_window_configs()` function constructs option tables for `nvim_open_win`, applying border styles (single, double, rounded) and determining preview pane positioning. When `M.open()` executes, it creates floating windows for the input field, result list, and optional file preview, then attaches buffer-local autocmds for input change detection.

Rendering utilities in [`lua/fff/list_renderer.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/list_renderer.lua) and [`lua/fff/file_renderer.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/file_renderer.lua) transform raw Rust result items into formatted buffer lines, handling icon injection, Git status highlighting, and score display when debug mode is enabled.

## Public API Surface

The [`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/main.lua) module exposes the user-facing API consumed by plugin commands and external scripts. It maintains the active picker state while providing functions for common operations.

Key exported functions include:

- `setup(config)` – Stores configuration in `vim.g.fff`
- `find_files(opts)` – Opens the file picker UI
- `live_grep(opts)` – Opens the content search UI
- `search(query, max_results)` – Programmatic search returning a Lua table
- `change_indexing_directory(new_path)` – Switches the root directory for subsequent searches

These functions act as thin wrappers that delegate to `core`, `file_picker`, `grep`, or `picker_ui` submodules, maintaining clean separation of concerns while providing a unified interface for both interactive and scripted usage.

## Summary

- **fff.nvim** uses a dual-language architecture with Lua handling UI and configuration while Rust manages indexing and search algorithms
- The entry point at [`plugin/fff.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/plugin/fff.lua) implements lazy loading to prevent startup time impact
- [`lua/fff/core.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/core.lua) maintains global state and manages the Rust object lifecycle
- Configuration is centralized in [`lua/fff/conf.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/conf.lua) with automatic migration for deprecated options
- The FFI bridge in [`lua/fff/fuzzy.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/fuzzy.lua) provides zero-overhead access to Rust functions
- Search operations in [`lua/fff/file_picker/init.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/file_picker/init.lua) and [`lua/fff/grep/init.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/grep/init.lua) receive pre-sorted results from Rust, eliminating Lua-side processing
- UI rendering in [`lua/fff/picker_ui.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/picker_ui.lua) handles floating window layout independently of search logic

## Frequently Asked Questions

### How does fff.nvim achieve fast search speeds?

The plugin achieves sub-second search performance by implementing the fuzzy matching algorithm, frecency scoring, and filesystem indexing in Rust rather than Lua. The Rust backend compiles to native machine code and can leverage multi-threading for directory scanning, while the Lua frontend focuses solely on rendering results that are pre-sorted by the `fuzzy.fuzzy_search_files` function.

### Can I use fff.nvim programmatically without opening the UI?

Yes, the [`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/main.lua) module exposes `search(query, max_results)`, which returns a Lua table of matches without opening floating windows. This allows other plugins or scripts to leverage the Rust fuzzy engine directly: `local results = require('fff').search('config', 15)` returns the top 15 matches as a structured table.

### How is the configuration validated and migrated?

The configuration system in [`lua/fff/conf.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/conf.lua) maintains a complete schema with default values and includes a `handle_deprecated_config()` function that automatically migrates legacy options. For example, top-level `width` settings are automatically moved to `layout.width` with a deprecation warning, ensuring backward compatibility while guiding users toward the current API.

### What happens when I change directories while fff.nvim is running?

When the `DirChanged` autocmd fires, [`lua/fff/core.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/core.lua) detects the event through `setup_global_autocmds()` and automatically calls `picker.change_indexing_directory`. This updates the Rust backend's indexing root without requiring a manual refresh, allowing seamless project switching while maintaining frecency scores and Git status awareness across directories.