# How to Integrate fff.nvim with Neovim: Complete Setup Guide

> Learn how to integrate fff.nvim with Neovim. Our guide covers installation, compilation, configuration, and key mapping for a seamless file explorer experience.

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

---

**To integrate fff.nvim with Neovim, install the plugin via lazy.nvim or vim.pack, execute the build step to compile the Rust binary, optionally configure settings via `require('fff').setup()`, and map keys to `find_files()` or `live_grep()`.**

**fff.nvim** is a high-performance fuzzy finder and live grep plugin for Neovim that leverages a compiled Rust core for sub-millisecond search speeds. According to the [dmtrKovalenko/fff.nvim](https://github.com/dmtrKovalenko/fff.nvim) source code, the plugin follows a three-layer architecture: configuration management in [`lua/fff/conf.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/conf.lua), a Rust binary bridge in [`lua/fff/core.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/core.lua), and a floating window UI in [`lua/fff/picker_ui.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/picker_ui.lua). This guide covers the complete integration process from installation to advanced configuration.

## Installation Methods

### Option 1: Using lazy.nvim (Recommended)

Add the plugin to your lazy.nvim specification with a `build` function to handle the Rust binary automatically:

```lua
{
  'dmtrKovalenko/fff.nvim',
  build = function()
    -- Downloads a pre-built binary or compiles from source
    require('fff.download').download_or_build_binary()
  end,
  opts = {
    debug = { enabled = true, show_scores = true },
  },
  lazy = false,
  keys = {
    { "ff", function() require('fff').find_files() end, desc = "FFFind files" },
    { "fg", function() require('fff').live_grep() end, desc = "Live grep" },
    { "fz", function()
        require('fff').live_grep({ grep = { modes = { 'fuzzy', 'plain' } } })
      end,
      desc = "Live fuzzy grep" },
  },
}

```

The `build` key ensures the native `fff-core` binary is available by downloading a pre-built release or falling back to compilation. Setting `lazy = false` allows the plugin to self-initialize, though the picker itself opens only when invoked.

### Option 2: Using vim.pack (Built-in Package Manager)

For Neovim versions with native package management (`vim.pack`), use the following configuration:

```lua
vim.pack.add({ 'https://github.com/dmtrKovalenko/fff.nvim' })

vim.api.nvim_create_autocmd('PackChanged', {
  callback = function(event)
    if event.data.updated then
      require('fff.download').download_or_build_binary()
    end
  end,
})

vim.g.fff = {
  lazy_sync = true,
  debug = { enabled = true, show_scores = true },
}

vim.keymap.set('n', 'ff', function() require('fff').find_files() end,
               { desc = 'FFFind files' })
vim.keymap.set('n', 'fg', function() require('fff').live_grep() end,
               { desc = 'Live grep' })

```

This approach uses `vim.pack.add` to clone the repository into `~/.local/share/nvim/site/pack/`, and the `PackChanged` autocmd automatically rebuilds the binary after updates.

## Configuration and Setup

While `fff.nvim` works with sensible defaults, you can customize behavior through `require('fff').setup()`. In [`lua/fff/conf.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/conf.lua), the `M.setup` function deep-merges user options with defaults and stores them in `vim.g.fff`:

```lua
require('fff').setup({
  base_path = vim.fn.getcwd(),
  prompt = '🪿 ',
  title = 'FFFiles',
  max_results = 200,
  layout = {
    height = 0.85,
    width = 0.85,
    prompt_position = 'bottom',
    preview_position = 'right',
    preview_size = 0.5,
    path_shorten_strategy = 'middle_number',
  },
  preview = {
    enabled = true,
    line_numbers = true,
    wrap_lines = false,
    filetypes = { markdown = { wrap_lines = true } },
  },
  keymaps = {
    close = '<Esc>',
    select = '<CR>',
    move_up = { '<Up>', '<C-p>' },
    move_down = { '<Down>', '<C-n>' },
    cycle_grep_modes = '<S-Tab>',
    toggle_debug = '<F2>',
  },
  git = { status_text_color = true },
})

```

The UI module ([`lua/fff/picker_ui.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/picker_ui.lua)) reads `conf.get()` during rendering, ensuring runtime configuration changes (such as toggling debug mode) reflect immediately without restarting Neovim.

## Usage Examples and Key Bindings

### Basic File Finding and Live Grep

After setup, invoke the picker through the public API exposed in [`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/main.lua):

```lua
-- Open file picker with default settings
vim.keymap.set('n', '<leader>p', function() require('fff').find_files() end,
               { desc = 'Pick file' })

-- Live grep with word under cursor pre-filled
vim.keymap.set('n', '<leader>g',
  function()
    require('fff').live_grep({ query = vim.fn.expand('<cword>') })
  end,
  { desc = 'Grep word under cursor' })

```

### Advanced Configuration Examples

**Changing the indexing directory at runtime:**

```lua
vim.keymap.set('n', '<leader>cd',
  function()
    local new_dir = vim.fn.input('Base directory: ', vim.fn.getcwd(), 'dir')
    if new_dir ~= '' then
      require('fff').change_indexing_directory(new_dir)
      vim.notify('FFF index base switched to ' .. new_dir)
    end
  end,
  { desc = 'Change FFF base directory' })

```

**Programmatic search without opening the UI:**

```lua
local results = require('fff').search('init', 30)   -- returns up to 30 items
for _, file in ipairs(results) do
  print(string.format('[%s] %s', file.relative_path, file.frecency_score or 0))
end

```

**Toggling debug scoring:**

Press `<F2>` (default) inside the picker, or execute:

```lua
vim.api.nvim_command('FFFDebug toggle')

```

## Architecture Overview

Understanding the plugin structure helps debug integration issues:

- **[`lua/fff/conf.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/conf.lua)**: Handles configuration merging, deprecation migrations (e.g., `width → layout.width`), and stores runtime state.
- **[`lua/fff/core.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/core.lua)**: Manages the Rust binary lifecycle via `ensure_initialized()` and provides the bridge to `fff-core` for search operations.
- **[`lua/fff/picker_ui.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/picker_ui.lua)**: Creates floating windows, manages the results list, preview pane, and processes keymap interactions.
- **[`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/main.lua)**: Exports the public API (`setup`, `find_files`, `live_grep`, `search`).

When you call `require('fff').find_files()`, the execution flow moves from [`main.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/main.lua) → [`picker_ui.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/picker_ui.lua) (window creation) → [`core.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/core.lua) (Rust binary initialization) → rendering with `conf.get()` for layout calculations.

## Summary

- **Install** via lazy.nvim with a `build` step or vim.pack with a `PackChanged` autocmd to handle the Rust binary.
- **Configure** using `require('fff').setup()` in [`lua/fff/conf.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/conf.lua) to customize layouts, keymaps, and git integration.
- **Invoke** via `find_files()` or `live_grep()` from [`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/main.lua) for fuzzy finding and grep operations.
- **Extend** using `change_indexing_directory()` for runtime path switches or `search()` for programmatic result retrieval.
- **Troubleshoot** with `:FFFHealth` to validate binary installation and `:FFFDebug toggle` to inspect scoring algorithms.

## Frequently Asked Questions

### Do I need to install Rust manually to use fff.nvim?

No. The plugin includes a download mechanism (`require('fff.download').download_or_build_binary()`) that pulls pre-built binaries for supported platforms. Rust is only required if you are on an unsupported platform or want to compile from source manually.

### How do I change the base directory for searches at runtime?

Use the `change_indexing_directory()` method exposed in [`lua/fff/main.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/main.lua). Pass an absolute path string to this function, and the next invocation of `find_files()` or `live_grep()` will search the new directory instead of the original `base_path`.

### What makes fff.nvim faster than Telescope or fzf.vim?

The heavy lifting (file scanning, fuzzy scoring, and grep operations) executes in the compiled Rust binary (`fff-core`) rather than Lua or Vimscript. According to the source in [`lua/fff/core.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/core.lua), this architecture guarantees sub-millisecond response times even on massive codebases, with paginated results handled natively.

### Why does the picker fail to open with a "binary not found" error?

This occurs when the build step hasn't executed. Run `:FFFHealth` to verify the binary status. If missing, manually trigger `require('fff.download').download_or_build_binary()` and restart Neovim. Ensure your package manager's `build` or `run` hook is properly configured as shown in the lazy.nvim and vim.pack examples above.