How to Contribute to fff.nvim: The Complete Guide for Neovim Plugin Developers

Contributing to fff.nvim requires setting up a dual Rust and Lua development environment, building the core library with make build, and following the standardized workflow of formatting, linting, and testing via make commands before submitting a pull request.

fff.nvim is a high-performance fuzzy finder for Neovim that combines a Rust-based indexing engine with a lightweight Lua user interface. Whether you want to extend the fuzzy search algorithms, add new Lua API functions, or improve the floating window UI, this guide covers the exact file paths, build commands, and code patterns used in the repository.

Repository Architecture and Key Files

fff.nvim follows a hybrid architecture where performance-critical operations reside in Rust and the Neovim integration lives in Lua. Understanding this separation is essential before contributing.

Core Components

  • Rust Core (crates/): Handles file indexing, frecency tracking, query parsing, and search algorithms. The FFI bridge resides in crates/fff-nvim/src/lib.rs, which exposes functions like init_file_picker and fuzzy_search_files to Lua via mlua.
  • Lua Frontend (lua/fff/): Provides the public API through require('fff'). Key files include lua/fff/main.lua (containing find_files and live_grep), lua/fff/conf.lua (configuration defaults), and lua/fff/picker_ui.lua (UI rendering and window management).
  • Plugin Entry (plugin/fff.lua): The Vimscript shim that loads the Lua module when Neovim starts.
  • Documentation (doc/fff.nvim.txt and README.md): Help files and usage examples that must be updated with any API changes.

Critical File Reference

When contributing to fff.nvim, you will most likely edit these specific files:

Development Environment Setup

Before writing code, you must install the Rust toolchain and Lua formatting tools to match the CI environment.

Prerequisites Installation


# Install Rust (required for the core library)

curl https://sh.rustup.rs -sSf | sh -s -- -y

# Install Lua linting and formatting tools

sudo apt-get install luarocks
luarocks install luacheck

# Install stylua for Lua formatting

curl -L https://github.com/folke/stylua/releases/download/v0.20.0/stylua-linux-x86_64 -o ~/.local/bin/stylua
chmod +x ~/.local/bin/stylua

Building the Project

Clone your fork and build the Rust core with the required zlob feature:

git clone https://github.com/<your-username>/fff.nvim.git
cd fff.nvim
make build

This generates target/release/libfff_c.* and prepares the binary for the Lua side. If you are modifying the Node.js or Bun packages, also run make prepare-node or make prepare-bun to copy the compiled library into packages/*/bin.

The Contribution Workflow

Follow this exact sequence to ensure your changes pass CI and maintain code quality.

  1. Install test dependencies: The repository includes a test-setup target that clones plenary.nvim (required for Lua tests).

    make test-setup
  2. Run the full test suite: Verify your environment is working before making changes.

    make test
  3. Make your changes: Edit the appropriate files from the key file reference section above. For Rust changes, ensure you follow the existing patterns in crates/fff-nvim/src/lib.rs for exposing functions to Lua.

  4. Format and lint: The repository enforces strict formatting standards.

    make format  # Runs stylua and cargo fmt
    
    make lint    # Runs luacheck and cargo clippy
    
  5. Update documentation: Modify doc/fff.nvim.txt and README.md to reflect any API changes or new configuration options.

  6. Version bump (if needed): If you added public API features, synchronize the version across packages:

    make set-npm-version PKG=packages/fff-bun VERSION=0.6.0
  7. Submit your PR: Push your branch and open a pull request. The CI pipelines (rust.yml, lua.yml) will automatically run the same make commands you ran locally.

Practical Contribution Examples

Adding a New Configuration Option to the Rust Grep Engine

To add a new before_context option for grep results, you must modify both the Rust struct and the Lua wrapper.

Rust side (crates/fff-grep/src/searcher/core.rs):

pub struct GrepSearchOptions {
    pub max_file_size: usize,
    pub max_matches_per_file: usize,
    pub smart_case: bool,
    pub file_offset: usize,
    pub page_limit: usize,
    pub mode: GrepMode,
    pub time_budget_ms: u64,
    // New field:
    pub before_context: usize,
}

Lua side (lua/fff/main.lua):

local options = fff.GrepSearchOptions {
    max_file_size = max_file_size,
    max_matches_per_file = max_matches_per_file,
    smart_case = smart_case,
    file_offset = file_offset,
    page_limit = page_size,
    mode = mode,
    time_budget_ms = time_budget_ms,
    before_context = opts.before_context or 0, -- new parameter
}

Creating a Custom Picker Keybinding

You can expose new functionality by wrapping the existing API in your contribution tests or documentation examples:

-- Example: Find Neovim config files only
vim.keymap.set('n', '<leader>fc', function()
  require('fff').find_files({
    title = 'Neovim Config Files',
    cwd = vim.fn.stdpath('config'),
    query = 'lua/**/*.lua',
  })
end, { desc = 'FFF – Find Neovim config files' })

This works because find_files forwards the options table to picker_ui.open (defined in lua/fff/main.lua), where cwd overrides the base path and query acts as an initial filter.

Programmatic Search Integration

Plugins can leverage the Rust search engine directly without opening the UI:

local function open_first_match(word)
  local results = require('fff').search(word, 5)
  if #results > 0 then
    vim.api.nvim_command('edit ' .. vim.fn.fnameescape(results[1].path))
  else
    vim.notify('No matches for "' .. word .. '"', vim.log.levels.WARN)
  end
end

-- Create a command that uses the fuzzy finder programmatically
vim.api.nvim_create_user_command('FffOpenWord', function(opts)
  open_first_match(opts.args)
end, { nargs = 1 })

The M.search function in lua/fff/main.lua maps directly to the Rust fuzzy_search_files implementation, ensuring identical scoring and frecency boosting.

Common Issues and Solutions

Contributors frequently encounter these specific issues when working with the fff.nvim codebase:

  • Missing LMDB directory: The frecency database defaults to vim.fn.stdpath('cache') .. '/fff_nvim'. If running individual tests manually, create this directory first or the Rust core will fail to initialize.
  • zlob feature errors: Always build with make build rather than raw cargo build, as the Makefile includes --features zlob required for globbing functionality.
  • Formatting failures: CI strictly enforces stylua formatting. Run make format before committing to avoid build failures.
  • Binary path errors in JS packages: After building, run make prepare-node or make prepare-bun to copy the compiled .so or .dll files into the appropriate packages/ subdirectories before running JavaScript tests.

Summary

  • fff.nvim consists of a Rust core (crates/) for indexing and search, and a Lua frontend (lua/fff/) for the Neovim interface.
  • Use make build to compile the project and make test to validate changes across Rust, Lua, and JavaScript targets.
  • Key files for contributions include crates/fff-nvim/src/lib.rs for FFI functions, lua/fff/main.lua for API extensions, and lua/fff/conf.lua for configuration options.
  • Always run make format and make lint before submitting to ensure stylua and cargo clippy compliance.
  • Update both doc/fff.nvim.txt and README.md when modifying public APIs or adding new features.

Frequently Asked Questions

Do I need to know Rust to contribute to fff.nvim?

No, many contributions require only Lua knowledge. You can extend the UI in lua/fff/picker_ui.lua, add configuration options in lua/fff/conf.lua, or improve documentation without touching Rust. However, features involving search algorithms, file indexing, or performance optimizations require modifying the Rust codebase in crates/.

How do I run only the Lua tests during development?

While make test runs the full suite including Rust and JavaScript tests, you can run Lua-specific tests using the standard Plenary test harness. Ensure you have run make test-setup first to install plenary.nvim in the tests/ directory, then execute the specific test file such as tests/fff_core_spec.lua using Plenary's test runner from within Neovim.

Why does my build fail with missing libfff_c errors?

This indicates the Rust library was not built or not copied to the expected location. Run make build to compile the core, and if you are working with the Node.js or Bun packages, additionally run make prepare-node or make prepare-bun. These targets copy the compiled shared objects from target/release/ into the appropriate packages/*/bin/ directories.

Can I add custom keymaps to the fff.nvim picker UI?

Yes, though the default keymaps are defined within lua/fff/picker_ui.lua. To contribute new default keybindings or make mappings configurable, modify the conf.lua file to accept new mapping options, then implement the handling logic in picker_ui.lua. Ensure any new keymaps respect the existing hl (highlight) tables and window management patterns used in the codebase.

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 →