dmtrKovalenko/fff.nvim
GitHub: dmtrKovalenko/fff.nvim
FFF.nvim 是一款带智能记忆功能的快速文件搜索器,同时服务于 Neovim 用户和 AI 编程助手,通过多维评分让搜索结果越用越精准。
Stars: 1728 | Forks: 75
FFF
AI agents (MCP) | Neovim 用户
一款为你的 AI 和 neovim 准备的快速文件搜索工具,内置记忆功能
**FFF** 代表 ~~超快的模糊文件查找器~~ (从中选 3 个 F),它是一款专为你的 AI agent 和 Neovim 打造的、具有鲜明观点的模糊文件选择器。仅仅用于文件搜索,但我们把文件搜索做得真 TM 好。
FFF 是一款用于 grep、模糊文件匹配、globbing 和多重 grep 的工具,重点关注性能和有用的搜索结果。对于人类用户 —— 提供难以置信的抗拼写错误体验;对于 AI agents —— 实现最快的文件搜索,并附带额外的免费“记忆”功能,根据频率/新近度、git 状态 (git status)、文件大小、定义匹配等多种因素建议最佳搜索结果。
## MCP
FFF 是一种通过为你的 AI agent 的文件搜索工具内置一点记忆功能,从而减少时间和 token 消耗的绝佳方式。它让你的 AI 能够更快地找到代码,通过减少往返次数和阅读无用文件来节省 token。

你可以使用一个简单的 bash 脚本将 FFF 作为 AI agent 的依赖项安装:
```
curl -L https://dmtrkovalenko.dev/install-fff-mcp.sh | bash
```
它将打印出如何将其连接到你的 `Claude Code`、`Codex`、`OpenCode` 等工具的说明。连接完成后,只需告诉你的 agent “使用 fff” 即可。
这是一个可以完美运行的 `CLAUDE.md` 示例添加内容:
```
# CLAUDE.md
For any file search or grep in the current git indexed directory use fff tools
```
## Neovim 指南
这是在 linux 仓库 (100k 文件, 8GB) 上的一些演示,但你最好亲自尝试一下,看看它的魔力
https://github.com/user-attachments/assets/5d0e1ce9-642c-4c44-aa88-01b05bb86abb
### 安装说明
FFF.nvim 需要 neovim 0.10.0 或更高版本
#### lazy.nvim
```
{
'dmtrKovalenko/fff.nvim',
build = function()
-- this will download prebuild binary or try to use existing rustup toolchain to build from source
-- (if you are using lazy you can use gb for rebuilding a plugin if needed)
require("fff.download").download_or_build_binary()
end,
-- if you are using nixos
-- build = "nix run .#release",
opts = { -- (optional)
debug = {
enabled = true, -- we expect your collaboration at least during the beta
show_scores = true, -- to help us optimize the scoring system, feel free to share your scores!
},
},
-- No need to lazy-load with lazy.nvim.
-- This plugin initializes itself lazily.
lazy = false,
keys = {
{
"ff", -- try it if you didn't it is a banger keybinding for a picker
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',
},
{
"fc",
function() require('fff').live_grep({ query = vim.fn.expand("
") }) end,
desc = 'Search current word',
},
}
}
```
#### vim.pack
```
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,
})
-- the plugin will automatically lazy load
vim.g.fff = {
lazy_sync = true, -- start syncing only when the picker is open
debug = {
enabled = true,
show_scores = true,
},
}
vim.keymap.set(
'n',
'ff',
function() require('fff').find_files() end,
{ desc = 'FFFind files' }
)
```
### 配置
FFF.nvim 拥有合理的默认设置。以下是包含所有可用选项的完整配置:
```
require('fff').setup({
base_path = vim.fn.getcwd(),
prompt = '🪿 ',
title = 'FFFiles',
max_results = 100,
max_threads = 4,
lazy_sync = true, -- set to false if you want file indexing to start on open
layout = {
height = 0.8,
width = 0.8,
prompt_position = 'bottom', -- or 'top'
preview_position = 'right', -- or 'left', 'right', 'top', 'bottom'
preview_size = 0.5,
flex = { -- set to false to disable flex layout
size = 130, -- column threshold: if screen width >= size, use preview_position; otherwise use wrap
wrap = 'top', -- position to use when screen is narrower than size
},
show_scrollbar = true, -- Show scrollbar for pagination
-- How to shorten long directory paths in the file list:
-- 'middle_number' (default): uses dots for 1-3 hidden (a/./b, a/../b, a/.../b)
-- and numbers for 4+ (a/.4./b, a/.5./b)
-- 'middle': always uses dots (a/./b, a/../b, a/.../b)
-- 'end': truncates from the end (home/user/projects)
path_shorten_strategy = 'middle_number',
},
preview = {
enabled = true,
max_size = 10 * 1024 * 1024, -- Do not try to read files larger than 10MB
chunk_size = 8192, -- Bytes per chunk for dynamic loading (8kb - fits ~100-200 lines)
binary_file_threshold = 1024, -- amount of bytes to scan for binary content (set 0 to disable)
imagemagick_info_format_str = '%m: %wx%h, %[colorspace], %q-bit',
line_numbers = false,
cursorlineopt = 'both', -- the cursorlineopt used for lines in grep file previews, see :h cursorlineopt
wrap_lines = false,
filetypes = {
svg = { wrap_lines = true },
markdown = { wrap_lines = true },
text = { wrap_lines = true },
},
},
keymaps = {
close = '',
select = '',
select_split = '',
select_vsplit = '',
select_tab = '',
-- you can assign multiple keys to any action
move_up = { '', '' },
move_down = { '', '' },
preview_scroll_up = '',
preview_scroll_down = '',
toggle_debug = '',
-- grep mode: cycle between plain text, regex, and fuzzy search
cycle_grep_modes = '',
-- goes to the previous query in history
cycle_previous_query = '',
-- multi-select keymaps for quickfix
toggle_select = '',
send_to_quickfix = '',
-- this are specific for the normal mode (you can exit it using any other keybind like jj)
focus_list = 'l',
focus_preview = 'p',
},
hl = {
border = 'FloatBorder',
normal = 'Normal',
cursor = 'CursorLine', -- Falls back to 'Visual' if CursorLine is not defined
matched = 'IncSearch',
title = 'Title',
prompt = 'Question',
frecency = 'Number',
debug = 'Comment',
combo_header = 'Number',
scrollbar = 'Comment',
directory_path = 'Comment',
-- Multi-select highlights
selected = 'FFFSelected',
selected_active = 'FFFSelectedActive',
-- Git text highlights for file names
git_staged = 'FFFGitStaged',
git_modified = 'FFFGitModified',
git_deleted = 'FFFGitDeleted',
git_renamed = 'FFFGitRenamed',
git_untracked = 'FFFGitUntracked',
git_ignored = 'FFFGitIgnored',
-- Git sign/border highlights
git_sign_staged = 'FFFGitSignStaged',
git_sign_modified = 'FFFGitSignModified',
git_sign_deleted = 'FFFGitSignDeleted',
git_sign_renamed = 'FFFGitSignRenamed',
git_sign_untracked = 'FFFGitSignUntracked',
git_sign_ignored = 'FFFGitSignIgnored',
-- Git sign selected highlights
git_sign_staged_selected = 'FFFGitSignStagedSelected',
git_sign_modified_selected = 'FFFGitSignModifiedSelected',
git_sign_deleted_selected = 'FFFGitSignDeletedSelected',
git_sign_renamed_selected = 'FFFGitSignRenamedSelected',
git_sign_untracked_selected = 'FFFGitSignUntrackedSelected',
git_sign_ignored_selected = 'FFFGitSignIgnoredSelected',
-- Grep highlights
grep_match = 'IncSearch', -- Highlight for matched text in grep results
grep_line_number = 'LineNr', -- Highlight for :line:col location
grep_regex_active = 'DiagnosticInfo', -- Highlight for keybind + label when regex is on
grep_plain_active = 'Comment', -- Highlight for keybind + label when regex is off
grep_fuzzy_active = 'DiagnosticHint', -- Highlight for keybind + label when fuzzy is on
-- Cross-mode suggestion highlights
suggestion_header = 'WarningMsg', -- Highlight for the "No results found. Suggested..." banner
},
-- Store file open frecency
frecency = {
enabled = true,
db_path = vim.fn.stdpath('cache') .. '/fff_nvim',
},
-- Store successfully opened queries with respective matches
history = {
enabled = true,
db_path = vim.fn.stdpath('data') .. '/fff_queries',
min_combo_count = 3, -- Minimum selections before combo boost applies (3 = boost starts on 3rd selection)
combo_boost_score_multiplier = 100, -- Score multiplier for combo matches (files repeatedly opened with same query)
},
-- Git integration
git = {
status_text_color = false, -- Apply git status colors to filename text (default: false, only sign column)
},
debug = {
enabled = false, -- Show file info panel in preview
show_scores = false, -- Show scores inline in the UI
},
logging = {
enabled = true,
log_file = vim.fn.stdpath('log') .. '/fff.log',
log_level = 'info',
},
-- find_files settings
file_picker = {
current_file_label = '(current)',
},
-- grep settings
grep = {
max_file_size = 10 * 1024 * 1024, -- Skip files larger than 10MB
max_matches_per_file = 100, -- Maximum matches per file (set 0 to unlimited)
smart_case = true, -- Case-insensitive unless query has uppercase
time_budget_ms = 150, -- Max search time in ms per call (prevents UI freeze, 0 = no limit)
modes = { 'plain', 'regex', 'fuzzy' }, -- Available grep modes and their cycling order
},
})
```
### 核心功能
#### 可用方法
```
require('fff').find_files() -- Find files in current repository
require('fff').scan_files() -- Trigger rescan of files in the current directory
require('fff').refresh_git_status() -- Refresh git status for the active file list
require('fff').find_files_in_dir(path) -- Find files in a specific directory
require('fff').change_indexing_directory(new_path) -- Change the base directory for the file picker
```
只需跳转到定义处,看看还有哪些暴露的 API,我们有很多
#### 命令
FFF.nvim 提供了几个用于与文件选择器交互的命令:
- `:FFFScan` - 手动触发重新扫描当前目录中的文件
- `:FFFRefreshGit` - 手动刷新所有文件的 git 状态
- `:FFFClearCache [all|frecency|files]` - 清除各种缓存
- `:FFFHealth` - 检查 FFF 健康状态和依赖项
- `:FFFDebug [on|off|toggle]` - 切换分数显示调试
- `:FFFOpenLog` - 在新标签页中打开 FFF 日志文件
#### 调试模式
切换评分信息显示:
- 在选择器中按 `F2`
- 使用 `:FFFDebug` 命令
- 通过 `debug.show_scores = true` 默认启用
#### 多选和 Quickfix 集成
选择多个文件并将其发送到 Neovim 的 quickfix 列表(快捷键可配置):
- `` - 切换当前文件的选择状态(在标号列显示粗边框 `▊`)
- `` - 将选中的文件发送到 quickfix 列表并关闭选择器
#### Live Grep 搜索模式
Live grep 支持三种搜索模式,通过 `` 循环切换:
- **纯文本** (Plain text) (默认) - 查询内容按字面意思匹配。特殊正则字符如 `.`、`*`、`(`、`)`、`$` 没有特殊含义。这是搜索包含正则元字符的代码时最安全的模式。
- **正则** (Regex) - 查询内容被解释为正则表达式。支持字符类 (`[a-z]`)、量词 (`+`, `*`, `{n}`)、交替 (`foo|bar`)、锚点 (`^`, `$`)、单词边界 (`\b`) 等。
- **模糊** (Fuzzy) - 使用 Smith-Waterman 算法进行模糊匹配查询。兼容拼写错误和分散的字符(例如,“mtxlk” 匹配 “mutex_lock”)。结果会经过质量阈值过滤,以避免过于模糊的匹配。
当前模式显示在输入框的右侧(例如 `plain`、`regex`、`fuzzy`),并带有颜色编码的高亮显示。
你可以在配置中全局自定义哪些模式可用及其循环顺序,或者在调用 `live_grep()` 时单独设置。
**全局配置:**
```
require('fff').setup({
grep = {
modes = { 'plain', 'regex' }, -- Only plain and regex, no fuzzy
}
})
```
**单次调用配置:**
```
-- Only fuzzy and plain modes for this specific grep
require('fff').live_grep({
grep = {
modes = { 'fuzzy', 'plain' },
}
})
-- Single mode (hides mode indicator completely)
require('fff').live_grep({
grep = {
modes = { 'fuzzy' },
}
})
-- Pre-fill the search with an initial query
require('fff').live_grep({ query = 'search term' })
```
当只配置了一种模式时,模式指示器会完全隐藏,且循环快捷键将不起作用。
#### 约束条件
你可以使用多种约束条件来优化 grep 和文件搜索模式下的搜索:
- `git:modified` - 仅显示已修改的文件 (可选值: `modified`, `staged`, `deleted`, `renamed`, `untracked`, `ignored` 之一)
- `test/` - 任意 test/ 目录下的任何深层嵌套子项
- `!something` - 排除匹配 something 的结果
- `!test/`, `!git:modified` - 与任何其他约束条件组合使用可实现否定效果
- `./**/*.{rs,lua}` - 任何有效的 glob 表达式,通过 [最快的 glob 库](https://github.com/dmtrKovalenko/zlob) 支持
仅适用于 grep:
- `*.md`, `*.{c,h}` - 扩展名过滤
- `src/main.rs` - 在单个文件中 grep
此外,所有约束条件可以组合使用,例如:
```
git:modified src/**/*.rs !src/**/mod.rs user controller
```
这将找到符合所有约束条件的文件,并且:
- 同时匹配 user 和 controller(文件模式)
- 匹配 "user controller"(grep 模式)
#### 跨模式建议
当搜索未返回结果时,FFF 会自动查询相反的搜索模式并将结果显示为建议:
- **文件搜索无匹配** → 显示同一查询的推荐**内容匹配**(grep 结果)
- **Grep 搜索无匹配** → 显示同一查询的推荐**文件名匹配**
建议项会清晰地标记有“未找到结果。建议...”横幅(使用 `hl.suggestion_header` 高亮)。你可以像浏览普通结果一样导航并选择建议项 —— 选择一个 grep 建议将在匹配行处打开文件。
#### Git 状态高亮
FFF 与 git 集成,通过标号列指示符(默认启用)和可选的文件名文本着色来显示文件状态。
**标号列指示符** (Sign Column Indicators)(默认启用) - 在标号列中显示边框字符:
```
hl = {
git_sign_staged = 'FFFGitSignStaged',
git_sign_modified = 'FFFGitSignModified',
git_sign_deleted = 'FFFGitSignDeleted',
git_sign_renamed = 'FFFGitSignRenamed',
git_sign_untracked = 'FFFGitSignUntracked',
git_sign_ignored = 'FFFGitSignIgnored',
}
```
**文本高亮** (Text Highlights)(可选) - 根据 git 状态为文件名应用颜色:
要启用 git 状态文本着色,请设置 `git.status_text_color = true`:
```
require('fff').setup({
git = {
status_text_color = true, -- Enable git status colors on filename text
},
hl = {
git_staged = 'FFFGitStaged', -- Files staged for commit
git_modified = 'FFFGitModified', -- Modified unstaged files
git_deleted = 'FFFGitDeleted', -- Deleted files
git_renamed = 'FFFGitRenamed', -- Renamed files
git_untracked = 'FFFGitUntracked', -- New untracked files
git_ignored = 'FFFGitIgnored', -- Git-ignored files
}
})
```
该插件提供了合理的默认高亮组,链接到常见的 git 高亮组(例如 GitSignsAdd, GitSignsChange)。你可以使用自定义高亮组覆盖它们,以匹配你的配色方案。
**示例 - 自定义明亮文本颜色:**
```
vim.api.nvim_set_hl(0, 'CustomGitModified', { fg = '#FFA500' })
vim.api.nvim_set_hl(0, 'CustomGitUntracked', { fg = '#00FF00' })
require('fff').setup({
git = {
status_text_color = true,
},
hl = {
git_modified = 'CustomGitModified',
git_untracked = 'CustomGitUntracked',
}
})
```
#### 文件过滤
FFF.nvim 自动遵守 `.gitignore` 模式。要在不修改 `.gitignore` 的情况下从选择器中过滤文件,请在项目根目录下创建一个 `.ignore` 文件:
```
# 排除所有 markdown 文件
*.md
# 排除特定子目录
docs/archive/**/*.md
```
如需强制重新扫描,请运行 `:FFFScan`。
### 故障排除
#### 健康检查
运行 `:FFFHealth` 检查 FFF.nvim 及其依赖项的状态。这将验证:
- 文件选择器初始化状态
- 可选依赖项 (git, 图片预览工具)
- 数据库连接
#### 查看日志
如果遇到问题,请检查日志文件:
```
:FFFOpenLog
```
或手动打开位于 `~/.local/state/nvim/log/fff.log` 的日志文件(默认位置)。标签:AI代理, DLL 劫持, HTTP工具, MCP, Neovim, rizin, Rust, 人工智能, 代码导航, 代码编辑器, 内存增强, 可视化界面, 大语言模型, 威胁情报, 开发效率, 开发者工具, 性能优化, 插件, 文件搜索, 文件检索, 检测绕过, 模糊匹配, 模糊查找, 生产力工具, 用户模式Hook绕过, 网络可观测性, 网络安全审计, 网络流量审计, 通知系统