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

> Ready to contribute to fff.nvim? Learn how to set up your Rust and Lua environment, build the core, and follow best practices for submitting a pull request to this Neovim plugin.

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

---

**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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/main.lua) (containing `find_files` and `live_grep`), [`lua/fff/conf.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/conf.lua) (configuration defaults), and [`lua/fff/picker_ui.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/picker_ui.lua) (UI rendering and window management).
- **Plugin Entry** ([`plugin/fff.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/plugin/fff.lua)): The Vimscript shim that loads the Lua module when Neovim starts.
- **Documentation** ([`doc/fff.nvim.txt`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/doc/fff.nvim.txt) and [`README.md`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/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:

- [`crates/fff-nvim/src/lib.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-nvim/src/lib.rs) – Add new Rust-exposed functions here
- [`crates/fff-grep/src/searcher/core.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-grep/src/searcher/core.rs) – Modify the `GrepSearchOptions` struct or grep engine logic
- [`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/main.lua) – Extend the public Lua API
- [`lua/fff/conf.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/conf.lua) – Add new configuration options
- [`lua/fff/picker_ui.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/picker_ui.lua) – Adjust UI layouts, keymaps, or preview behavior
- [`tests/fff_core_spec.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/tests/fff_core_spec.lua) – Add integration tests for new features

## Development Environment Setup

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

### Prerequisites Installation

```bash

# 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:

```bash
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).

   ```bash
   make test-setup
   ```

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

   ```bash
   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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-nvim/src/lib.rs) for exposing functions to Lua.

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

   ```bash
   make format  # Runs stylua and cargo fmt

   make lint    # Runs luacheck and cargo clippy

   ```

5. **Update documentation**: Modify [`doc/fff.nvim.txt`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/doc/fff.nvim.txt) and [`README.md`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/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:

   ```bash
   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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/rust.yml), [`lua.yml`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-grep/src/searcher/core.rs)):

```rust
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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/main.lua)):

```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:

```lua
-- 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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/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:

```lua
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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-nvim/src/lib.rs) for FFI functions, [`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/main.lua) for API extensions, and [`lua/fff/conf.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/doc/fff.nvim.txt) and [`README.md`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/picker_ui.lua), add configuration options in [`lua/fff/conf.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/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`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/picker_ui.lua). To contribute new default keybindings or make mappings configurable, modify the [`conf.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/conf.lua) file to accept new mapping options, then implement the handling logic in [`picker_ui.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/picker_ui.lua). Ensure any new keymaps respect the existing `hl` (highlight) tables and window management patterns used in the codebase.