经 AI Skill Hub 精选评估,fff文件搜索工具 获评「强烈推荐」。已获得 6.2k 颗 GitHub Star,这款Agent工作流在功能完整性、社区活跃度和易用性方面表现出色,AI 评分 8.2 分,适合有一定技术背景的用户使用。
为AI智能体和Neovim优化的超快速精准文件搜索工具包。采用Rust高性能实现,提供闪电般的搜索速度和极高准确率。适合需要快速定位文件的开发者、AI应用构建者和Neovim用户,特别是处理大规模代码库场景。
fff文件搜索工具 是一套完整的 AI Agent 自动化工作流方案。通过可视化的节点编排,将复杂的多步骤任务拆解为清晰的自动化流程,实现全程无人值守的智能处理。支持与数百种外部服务和 API 无缝集成,适合构建数据处理管线、业务自动化和 AI 辅助决策系统。
为AI智能体和Neovim优化的超快速精准文件搜索工具包。采用Rust高性能实现,提供闪电般的搜索速度和极高准确率。适合需要快速定位文件的开发者、AI应用构建者和Neovim用户,特别是处理大规模代码库场景。
fff文件搜索工具 是一套完整的 AI Agent 自动化工作流方案。通过可视化的节点编排,将复杂的多步骤任务拆解为清晰的自动化流程,实现全程无人值守的智能处理。支持与数百种外部服务和 API 无缝集成,适合构建数据处理管线、业务自动化和 AI 辅助决策系统。
# 方式一:cargo install(推荐) cargo install fff # 方式二:从源码编译 git clone https://github.com/dmtrKovalenko/fff cd fff cargo build --release # 二进制在 ./target/release/fff
# 查看帮助 fff --help # 基本运行 fff [options] <input> # 详细使用说明请查阅文档 # https://github.com/dmtrKovalenko/fff
# fff 配置说明 # 查看配置选项 fff --config-example > config.yml # 常见配置项 # output_dir: ./output # log_level: info # workers: 4 # 环境变量(覆盖配置文件) export FFF_CONFIG="/path/to/config.yml"
<img alt="FFF" src="./assets/logo-orange.png" width="300">
<p> <i>A file search toolkit for humans and AI agents. Really fast.</i> </p>
Typo-resistant path and content search, frecency-ranked file access, a background watcher, and a lightweight in-memory content index. Way faster than CLIs like ripgrep and fzf in any long-running process that searches more than once.
Powers file search in opencode, nushell, and many more amazing projects!
Originally started as Neovim plugin people loved, but it turned out that plenty of AI harnesses and code editors need the same thing: accurate, fast file search as a library. That is what fff is.
---
Pick what you are interested in:
<details id="mcp-server"> <summary> <h2>MCP server</h2> </summary>
Works with Claude Code, Codex, OpenCode, Cursor, Cline, and any MCP-capable client. Fewer grep roundtrips, less wasted context, faster answers.

FFF is written in Rust, so this is the lowest-overhead way to use it.
[dependencies]
fff-search = "0.6"
Full API documentation: docs.rs/fff-search.
</details>
Native rust crate that is performing all the search. Stable and well documented.
<details id="c-library"> <summary> <h2>C library</h2> </summary>
Linux / macOS:
curl -L https://dmtrkovalenko.dev/install-fff-mcp.sh | bash
Windows (PowerShell):
irm https://raw.githubusercontent.com/dmtrKovalenko/fff.nvim/main/install-mcp.ps1 | iex
The scripts live at install-mcp.sh and install-mcp.ps1 if you want to read them first. They print the exact wiring instructions for your client.
pi install npm:@ff-labs/pi-fff
{
'dmtrKovalenko/fff.nvim',
build = function()
-- downloads a prebuilt binary or falls back to cargo build
require("fff.download").download_or_build_binary()
end,
-- for nixos:
-- build = "nix run .#release",
opts = {
debug = {
enabled = true,
show_scores = true,
},
},
lazy = false, -- the plugin lazy-initialises itself
keys = {
{ "ff", function() require('fff').find_files() end, desc = 'FFFind files' },
{ "fg", function() require('fff').live_grep() end, desc = 'LiFFFe grep' },
{ "fz",
function() require('fff').live_grep({ grep = { modes = { 'fuzzy', 'plain' } } }) end,
desc = 'Live fffuzy grep',
},
{ "fw",
function() require('fff').live_grep_under_cursor() end,
mode = { 'n', 'x' },
desc = 'Search current word / selection',
},
},
}
vim.pack.add({ 'https://github.com/dmtrKovalenko/fff.nvim' })
vim.api.nvim_create_autocmd('PackChanged', {
callback = function(ev)
local name, kind = ev.data.spec.name, ev.data.kind
if name == 'fff.nvim' and (kind == 'install' or kind == 'update') then
if not ev.data.active then vim.cmd.packadd('fff.nvim') end
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' })
```bash
make build-c-lib
```bash
make install DESTDIR=/tmp/pkgroot PREFIX=/usr
Drops `libfff_c.{so,dylib,dll}` into `$(PREFIX)/lib` and the header into `$(PREFIX)/include/fff.h`. Remove with `make uninstall`, which honours the same `PREFIX` and `DESTDIR`.
Link against it after install:
bash cc my_app.c -lfff_c -o my_app ```
Ensure $(PREFIX)/lib is on your runtime library search path (LD_LIBRARY_PATH on Linux, DYLD_LIBRARY_PATH on macOS, or an entry in /etc/ld.so.conf.d/).
pip install fff-search
Or build and install from source:
cd packages/fff-python
uv sync --all-extras
uv run maturin develop --release
#include <fff.h>
#include <stdio.h>
int main(void) {
FffResult *res = fff_create_instance(
".", // base_path
"", // frecency_db_path (empty = default)
"", // history_db_path
false, // use_unsafe_no_lock
true, // enable_mmap_cache
true, // enable_content_indexing
true, // watch
false // ai_mode
);
if (!res->success) {
fprintf(stderr, "init failed: %s\n", res->error);
fff_free_result(res);
return 1;
}
void *handle = res->handle;
fff_free_result(res);
// Search
FffResult *search = fff_search(handle, "main.rs", "", 0, 0, 20, 100, 3);
// ... read FffSearchResult from search->handle, then fff_free_search_result()
fff_destroy(handle);
return 0;
}
from fff import FileFinder
with FileFinder("/path/to/project", watch=False) as finder:
finder.wait_for_scan_blocking(timeout_ms=5000)
result = finder.search("main")
for item, score in zip(result.items, result.scores):
print(f"{item.relative_path}: {score.total}")
hits = finder.grep("class Profile", mode="plain", before_context=1, after_context=1)
wait_for_scan is a coroutine that polls scan status and yields to the event loop, so it never blocks other tasks. Use wait_for_scan_blocking from synchronous code.
import asyncio
from fff import FileFinder
async def main():
with FileFinder("/path/to/project", watch=False) as finder:
await finder.wait_for_scan(timeout_ms=5000)
result = finder.search("main")
print(result)
asyncio.run(main())
Defaults are sensible. Override only what you care about.
require('fff').setup({
base_path = vim.fn.getcwd(),
prompt = '> ',
title = 'FFFiles',
max_results = 100,
max_threads = 4,
lazy_sync = true,
prompt_vim_mode = false,
follow_symlinks = false,
-- Allow indexing the user's $HOME directory. Enabled by default.
-- Disable if you strictly sure you don't want this, as it makes whole fff error hard
enable_home_dir_scanning = true,
-- Allow indexing a filesystem root (e.g. `/`, `C:\`). Disabled by default
enable_fs_root_scanning = false,
layout = {
height = 0.8,
width = 0.8,
prompt_position = 'bottom', -- or 'top'
preview_position = 'right', -- 'left' | 'right' | 'top' | 'bottom'
preview_size = 0.5,
-- Border style for the picker windows. Leave unset (nil) to follow the
-- global `vim.o.winborder`; set it to override fff's borders independently.
border = nil, -- 'single' | 'double' | 'rounded' | 'solid' | 'shadow' | 'none'
flex = { size = 130, wrap = 'top' },
min_list_height = 10, -- do not display anything except the list below this threshold
show_scrollbar = true,
path_shorten_strategy = 'middle_number', -- 'middle_number' | 'middle' | 'end' | 'start'
anchor = 'center',
},
preview = {
enabled = true,
max_size = 10 * 1024 * 1024,
chunk_size = 8192,
binary_file_threshold = 1024,
imagemagick_info_format_str = '%m: %wx%h, %[colorspace], %q-bit',
line_numbers = false,
cursorlineopt = 'both',
wrap_lines = false,
filetypes = {
svg = { wrap_lines = true },
markdown = { wrap_lines = true },
text = { wrap_lines = true },
},
},
keymaps = {
close = '<Esc>',
select = '<CR>',
select_split = '<C-s>',
select_vsplit = '<C-v>',
select_tab = '<C-t>',
move_up = { '<Up>', '<C-p>' },
move_down = { '<Down>', '<C-n>' },
preview_scroll_up = '<C-u>',
preview_scroll_down = '<C-d>',
toggle_debug = '<F2>',
cycle_grep_modes = '<S-Tab>',
-- grep mode only: jump cursor to first match of next/prev file group
grep_jump_to_next_file = { '<C-A-n>', '<A-Down>' },
grep_jump_to_prev_file = { '<C-A-p>', '<A-Up>' },
cycle_previous_query = '<C-Up>',
toggle_select = '<Tab>',
send_to_quickfix = '<C-q>',
focus_list = '<leader>l',
focus_preview = '<leader>p',
},
frecency = {
enabled = true,
db_path = vim.fn.stdpath('cache') .. '/fff_nvim',
},
history = {
enabled = true,
db_path = vim.fn.stdpath('data') .. '/fff_queries',
min_combo_count = 3,
combo_boost_score_multiplier = 100,
},
git = {
status_text_color = false, -- true to color filenames by git status
},
select = {
-- Return winid to open the chosen file in, or nil to open in the original window
select_window = function(current_buf, action) --[[ default impl ]] end,
},
grep = {
max_file_size = 10 * 1024 * 1024,
max_matches_per_file = 100,
smart_case = true,
time_budget_ms = 150,
modes = { 'plain', 'regex', 'fuzzy' },
trim_whitespace = false,
enable_filename_constraint = false, -- treat filename-like tokens (e.g. `score.rs`) in a grep query as a file-path filter scoping the search; off = searched as literal text
location_format = ':%d:%d', -- printf format for line:col prefix in grep results, e.g. ':%d' for line-only
},
debug = {
enabled = false, -- show the file info panel next to the preview
show_scores = false, -- inline scores in the file list
-- Per-section toggles for the file info panel. Accepts a boolean shorthand
-- (`show_file_info = true|false`) to flip everything at once. The panel
-- adapts to width: narrow renders sections vertically, wide renders them
-- as a two-column grid. Disable a section to also shrink the panel.
show_file_info = {
file_info = true, -- size, type, git status, frecency
score_breakdown = true, -- total + match type, bonuses, modifiers, penalty
-- modified + accessed timestamps; pass a table to hide individual rows:
-- timings = { modified = false, accessed = true }
timings = true,
full_path = true, -- relative path at the bottom (wraps if too long)
},
},
logging = {
-- logs will be written in a parent directory of this file path in files like
-- `<stem>+<UTC-timestamp>+<pid>.<ext>`. Run :FFFOpenLog to open current one
log_file = vim.fn.stdpath('log') .. '/fff.log',
log_level = 'info',
retain_runs = 20,
},
})
For instance creation use FffCreateOptions — a versioned struct that evolves without ABI breaks. C99 designated initializers keep call sites readable and zero-init unspecified fields:
FffResult *res = fff_create_instance_with(&(FffCreateOptions){
.version = FFF_CREATE_OPTIONS_VERSION,
.base_path = "/path/to/repo",
.ai_mode = true,
.watch = true,
.enable_fs_root_scanning = false, // off by default
.enable_home_dir_scanning = false, // off by default
});
require('fff').find_files() -- find files in current repo
require('fff').live_grep() -- live content grep
require('fff').live_grep_under_cursor() -- grep <cword> in normal, selection in visual
require('fff').scan_files() -- force rescan
require('fff').refresh_git_status() -- refresh git status
require('fff').find_files_in_dir(path) -- find in a specific dir
require('fff').change_indexing_directory(new_path) -- change root
-- Programmatic search (no UI). Useful for plugin integrations.
require('fff').file_search(query, opts) -- fuzzy search files / dirs / mixed
require('fff').content_search(query, opts) -- programmatic grep
file_search(query, opts)Returns a structured result { items, scores, total_matched, total_files?, total_dirs?, location? }. Each item has a type field ("file" or "directory") and name / relative_path. File items also expose size, modified, git_status, is_binary, and frecency scores.
local r = require('fff').file_search('button', {
mode = 'mixed', -- 'files' (default) | 'directories' | 'mixed'
max_results = 50,
page = 0, -- 0-based pagination
current_file = nil, -- path to deprioritize for distance scoring
max_threads = 4,
cwd = nil, -- switch indexed root if different (see below)
wait_for_index_ms = nil, -- override the default scan wait timeout
})
for _, item in ipairs(r.items) do
print(item.type, item.relative_path)
end
content_search(query, opts)Returns a GrepResult { items, total_matched, total_files_searched, total_files, filtered_file_count, next_file_offset, regex_fallback_error? }. Each match item has relative_path, name, line_number, col, line_content, match_ranges, plus the same file metadata as file_search.
local r = require('fff').content_search('TODO', {
mode = 'plain', -- 'plain' (default) | 'regex' | 'fuzzy'
max_file_size = 10 * 1024 * 1024,
max_matches_per_file = 100,
smart_case = true,
page_size = 50,
file_offset = 0,
time_budget_ms = 0,
trim_whitespace = false,
cwd = nil, -- switch indexed root if different
wait_for_index_ms = nil, -- override the default scan wait timeout
})
for _, m in ipairs(r.items) do
print(string.format('%s:%d %s', m.relative_path, m.line_number, m.line_content))
end
Both functions accept the same constraint syntax as the UI pickers (e.g. git:modified, *.rs, !test/, glob patterns).
cwd and indexingBoth file_search and content_search honour an optional cwd field. The first call to either function lazily initialises the picker at config.base_path (your Neovim cwd by default).
cwd matches the currently indexed root, the call returns immediately against the existing index.cwd differs, the picker is re-indexed at the new root and the call blocks (default up to 10 s) until the new picker is installed and its initial scan completes — so callers always get results from the right tree.change_indexing_directory, you can pass wait_for_index_ms = N to block for up to N ms regardless of whether cwd triggered the swap. Pass 0 to skip waiting entirely (useful for fire-and-forget calls where partial results are acceptable).cwd paths return an empty result and emit an error via vim.notify.rg invocations..gitignore. The ignore walker runs once at scan time and the result is reused for every search.{ relativePath, lineNumber, lineContent, gitStatus, totalFrecencyScore, isDefinition, ... } directly.---
:FFFHealth verifies picker init, optional dependencies, and DB connectivity.:FFFOpenLog opens the current session's log file.<state>/log/fff+<UTC-timestamp>+<pid>.log (up to 20 files)lldb -- nvim or gdb -- nvim and reproduce</details>
The best file search picker for neovim. Period. Faster and more intuitive queries, frecency ranking, definition classification and much more.
<details id="node-sdk"> <summary> <h2>Node & Bun SDK</h2> </summary>
```bash npm install @ff-labs/fff-node
简介:这是一个用于人类和 AI 代理的快速文件搜索工具。它提供了 typo-resistant 的路径和内容搜索、frecency 排序的文件访问、一个后台监视器和一个轻量级的内存内容索引。它比类似 ripgrep 和 fzf 的 CLI 快得多,特别是在长时间运行的过程中搜索多次时。
环境依赖与系统要求:该项目使用 Rust 编写,因此这是使用它的最低开销的方式。需要添加依赖项:[dependencies] fff-search = "0.6"
安装:一行安装命令 Linux / macOS:`curl -L https://dmtrkovalenko.dev/install-fff-mcp.sh | bash` Windows(PowerShell):`irm https://raw.githubusercontent.com/dmtrKovalenko/fff.nvim/main/install-mcp.ps1 | iex`
使用教程:最小示例 ```c #include <fff.h> #include <stdio.h> int main(void) { FffResult *res = fff_create_instance( ".", // base_path "," // frecency_db_path (empty = default) "," // history_db_path false, // use_unsafe_no_lock true, // enable_mmap_cache true, // enable_content_indexing true, // watch false // ai_mode ); if (!res->success) { fprintf(stderr, "init failed: %s\n", res->error);
配置:默认值是合理的。只覆盖你关心的内容。 ```lua require('fff').setup({ base_path = vim.fn.getcwd(), prompt = '> ', title = 'FFFiles', max_results = 100, max_threads = 4, lazy_sync = true, prompt_vim_mode = false, layout = { height = 0.8, width = 0.8, prompt_position = 'bottom', -- or 'top' preview_position = 'right', -- 'left' | 'r
API/接口说明: ```lua require('fff').find_files() -- find files in current repo require('fff').live_grep() -- live content grep require('fff').scan_files() -- force rescan require('fff').refresh_git_status() -- refresh git status require('fff').find_files_in_dir(path) -- find in a specific dir require('fff').change
FAQ: - `:FFFHealth` 验证选择器初始化、可选依赖项和 DB 连接。 - `:FFFOpenLog` 打开日志文件。
高效的文件搜索解决方案,Rust编写性能卓越,6.2k星活跃项目,AI生态集成良好,是AI工作流的理想搜索组件
AI Skill Hub 为第三方内容聚合平台,本页面信息基于公开数据整理,不对工具功能和质量作任何法律背书。
建议在沙箱或测试环境中充分验证后,再部署至生产环境,并做好必要的安全评估。
✅ MIT 协议 — 最宽松的开源协议之一,可自由商用、修改、分发,仅需保留版权声明。
AI Skill Hub 点评:fff文件搜索工具 的核心功能完整,质量优秀。对于自动化工程师和运维人员来说,这是一个值得纳入个人工具库的选择。建议先在非生产环境试用,再逐步推广。
| 原始名称 | fff |
| 原始描述 | 开源AI工作流:The fastest and the most accurate file search toolkit for AI agents, Neovim, Rus。⭐6.2k · Rust |
| Topics | 文件搜索AI智能体Neovim插件高性能Rust工具 |
| GitHub | https://github.com/dmtrKovalenko/fff |
| License | MIT |
| 语言 | Rust |
收录时间:2026-05-20 · 更新时间:2026-05-30 · License:MIT · AI Skill Hub 不对第三方内容的准确性作法律背书。
选择 Agent 类型,复制安装指令后粘贴到对应客户端