当前位置: 首页 > news >正文

FFF:比 ripgrep 和 fzf 更快的文件搜索工具包,多场景性能优势显著!

FFF:面向人类与 AI 代理的文件搜索工具包

FFF 是一款专为人类和 AI 代理设计的文件搜索工具包,搜索速度极快。它支持具备抗拼写错误能力的路径和内容搜索,基于访问频率排序的文件访问,拥有后台监控器,以及轻量级的内存内容索引。在任何需要多次搜索的长期运行进程中,FFF 的速度都远超 `ripgrep` 和 `fzf` 等 CLI 工具。

1. 起源与用途

FFF 最初是一个深受用户喜爱的 Neovim 插件,后来发现许多 AI 工具和代码编辑器都需要一个准确、快速的文件搜索库,FFF 便应运而生。它可以作为 MCP 服务器,与 Claude Code、Codex、OpenCode、Cursor、Cline 等任何支持 MCP 的客户端配合使用,减少 `grep` 的往返次数,减少无用上下文,从而更快地给出答案。

2. 安装方法

Linux / macOS

```bash
curl -L https://dmtrkovalenko.dev/install-fff-mcp.sh | bash
```

Windows (PowerShell)

```powershell
irm https://raw.githubusercontent.com/dmtrKovalenko/fff.nvim/main/install-mcp.ps1 | iex
```

如果你想先查看脚本内容,脚本位于 `install-mcp.sh` 和 `install-mcp.ps1`。安装脚本会打印出客户端的具体配置说明。服务器连接成功后,你可以让代理“使用 FFF”,它就能调用 `ffgrep`、`fffind` 和 `fff-multi-grep` 工具。

3. 推荐的代理提示

将以下内容添加到项目的 `CLAUDE.md` 或类似文件中:在当前 Git 索引目录中进行任何文件搜索或 `grep` 操作时,请使用 FFF 工具。

4. 主要特性

访问频率记忆

实际打开过的文件下次搜索时排名会更高。同时,它会自动根据 Git 的文件操作历史进行预热。

定义优先提示

在 Rust 端对看起来像代码定义的行进行分类,避免在搜索提示中使用正则表达式带来的开销。

智能大小写与自动模糊回退

例如,`IsOffTheRecord` 能找到 `snake_case` 变体;当精确匹配为零时,查询会自动以模糊模式重试,找出最佳近似匹配结果。

Git 感知注释

标记修改、未跟踪和暂存的文件,方便代理快速定位正在修改的文件。

5. MCP 服务器

MCP 服务器为任何代理提供了一个比内置工具更快、更节省令牌的文件搜索工具。

6. Pi 代理扩展

安装

```bash
pi install npm:@ff-labs/pi-fff
```

三种操作模式

可在运行时使用 `/fff-mode` 切换:

  • tools-and-ui(默认):添加 `ffgrep` 和 `fffind` 工具,用 FFF 替换 `@` 提及的自动补全功能。
  • tools-only:仅注入工具,保留 Pi 的原生编辑器自动补全功能。
  • override:用 FFF 的实现替换 Pi 内置的 `grep`、`find` 和 `multi_grep` 工具。
环境变量和标志

支持的环境变量有 `PI_FFF_MODE`、`FFF_FRECENCY_DB`、`FFF_HISTORY_DB`;支持的标志有 `--fff-mode`、`--fff-frecency-db`、`--fff-history-db`。

面向代理的工具
  • ffgrep:内容搜索。支持指定路径、排除项(用逗号、空格或数组表示,可加前置 `!`)、大小写敏感、上下文以及光标分页。能自动检测正则表达式,精确匹配为零时自动回退到模糊匹配,会直接拒绝仅含 `.*` 样式通配符的模式。
  • fffind:路径和文件名搜索。匹配整个相对于仓库的路径,而非仅文件名。具备访问频率感知功能,弱匹配检测器会在模糊噪声淹没代理上下文之前进行标记。
命令
  • `/fff-mode [tools-and-ui | tools-only | override]`:显示或切换模式。
  • `/fff-health`:查看选择器、访问频率和 Git 集成状态。
  • `/fff-rescan`:强制重新扫描。

Pi 扩展会用 FFF 的实现替换 Pi 的原生工具,并根据访问频率排序的索引为交互式编辑器的 `@` 提及自动补全提供数据。

7. fff.nvim

演示

在 Linux 内核仓库(100k 文件,8GB)上的演示视频:`fff_nvim_demo.mp4`

安装
lazy.nvim

```lua
{
'dmtrKovalenko/fff.nvim',
build = function()
-- 下载预构建的二进制文件,若失败则使用 cargo 构建
require("fff.download").download_or_build_binary()
end,
-- 对于 NixOS:
-- build = "nix run .#release",
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 = '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

```lua
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' })
```

公共 API
  • `require('fff').find_files()`:在当前仓库中查找文件。
  • `require('fff').live_grep()`:实时内容搜索。
  • `require('fff').scan_files()`:强制重新扫描。
  • `require('fff').refresh_git_status()`:刷新 Git 状态。
  • `require('fff').find_files_in_dir(path)`:在指定目录中查找文件。
  • `require('fff').change_indexing_directory(new_path)`:更改索引根目录。
  • `require('fff').file_search(query, opts)`:模糊搜索文件、目录或混合搜索。返回结构化结果 `{ items, scores, total_matched, total_files?, total_dirs?, location? }`。
  • `require('fff').content_search(query, opts)`:程序化 `grep` 搜索。返回 `GrepResult { items, total_matched, total_files_searched, total_files, filtered_file_count, next_file_offset, regex_fallback_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', -- 或 'top'
preview_position = 'right', -- 'left' | 'right' | 'top' | 'bottom'
preview_size = 0.5,
flex = { size = 130, wrap = 'top' },
min_list_height = 10,
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 = '',
select = '',
select_split = '',
select_vsplit = '',
select_tab = '',
move_up = { '', '' },
move_down = { '', '' },
preview_scroll_up = '',
preview_scroll_down = '',
toggle_debug = '',
cycle_grep_modes = '',
cycle_previous_query = '',
toggle_select = '',
send_to_quickfix = '',
focus_list = 'l',
focus_preview = '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,则根据 Git 状态为文件名着色
},
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,
location_format = ':%d:%d', -- grep 结果中行:列前缀的 printf 格式,例如 ':%d' 仅显示行号
},
debug = {
enabled = false, -- 在预览旁边显示文件信息面板
show_scores = false, -- 在文件列表中显示内联分数
show_file_info = {
file_info = true, -- 显示文件大小、类型、Git 状态、访问频率分数
score_breakdown = true, -- 显示总分数、匹配类型、奖励、修饰符、惩罚
timings = true, -- 显示修改和访问时间戳
full_path = true, -- 在底部显示相对路径(过长时会换行)
},
},
logging = {
enabled = true,
log_file = vim.fn.stdpath('log') .. '/fff.log',
log_level = 'info',
},
})
```

实时搜索模式

支持在普通、正则和模糊模式之间循环切换。可通过 `grep.modes` 配置搜索模式列表,单模式设置会完全隐藏模式指示器。也可在每次调用时覆盖默认模式:

```lua
require('fff').live_grep({ grep = { modes = { 'fuzzy', 'plain' } } })
require('fff').live_grep({ query = 'search term' }) -- 预填充搜索词
```

约束条件

搜索和 `grep` 操作都支持以下约束条件来细化查询:

  • `git:modified`:可指定 `modified`、`staged`、`deleted`、`renamed`、`untracked`、`ignored` 等状态。
  • `test/`:匹配 `test/` 目录下的所有子文件。
  • `!something`、`!test/`、`!git:modified`:排除指定内容。
  • `./**/*.{rs,lua}`:任何有效的 glob 模式,由 `zlob` 提供支持。
  • `*.md`、`*.{c,h}`:仅适用于 `grep` 的扩展名过滤。
  • `src/main.rs`:在单个文件中进行 `grep` 搜索。
多选和快速修复
  • 切换选择(在符号列显示粗体 `▊`)。
  • 将选中的文件发送到快速修复列表并关闭选择器。
Git 状态高亮

符号列指示器默认开启。若要根据 Git 状态为文件名文本着色,可设置 `git.status_text_color = true` 并调整 `hl.git_*` 组。具体可查看 `:help fff.nvim`。

浮动窗口颜色

选择器将浮动内容映射到 `NormalFloat`(通过 `hl.normal`),边框映射到 `FloatBorder`。默认情况下,`FloatBorder` 链接到 `NormalFloat`,因此边框和内容共享背景,选择器看起来像一个单独的弹出窗口。可通过覆盖 `hl.normal = 'Normal'` 使选择器与编辑器融合。若需要更精细的控制,可设置 `hl.winhl` 来覆盖每个窗口的 `winhighlight`,它可以是一个应用于所有选择器窗口的字符串,也可以是一个包含 `prompt`、`list`、`preview` 和 `file_info` 可选键的表。缺失的键将使用由 `hl.normal`、`hl.border` 和 `hl.title` 构建的默认值。

```lua
-- 对所有选择器窗口应用相同的 winhighlight
hl = { winhl = 'Normal:NormalFloat,FloatBorder:FloatBorder,FloatTitle:Title' }
-- 或仅覆盖特定窗口
hl = {
winhl = {
prompt = 'Normal:Pmenu,FloatBorder:FloatBorder',
list = 'Normal:NormalFloat,FloatBorder:FloatBorder',
preview = 'Normal:NormalFloat,FloatBorder:FloatBorder',
},
}
```

文件信息面板

启用 `debug.enabled = true` 可显示文件信息面板。该面板位于预览上方,显示文件元数据、分数分解、时间戳和完整绝对路径。它会根据面板宽度自适应显示:窄宽度时各部分垂直堆叠,宽宽度时各部分以两列网格形式显示。可通过 `debug.show_file_info` 单独禁用每个部分。可通过 `hl` 自定义面板:

默认值用途
`file_info_section``Title`部分标题标签
`file_info_separator``FloatBorder`作为部分分隔线的破折号
`file_info_label``Comment`行标签(大小、类型、Git 等)
`file_info_value``Normal`普通值
`file_info_value_dim``NonText`暗淡值,行内分隔符
`file_info_size``Number`文件大小值
`file_info_type``Type`文件类型值
`file_info_path``Directory`完整路径
`file_info_total_score``bold + Number`总分数(粗体)
`file_info_match_type``bold + Special`匹配类型(粗体)
`file_info_score_pos``DiagnosticOk`正分数组件
`file_info_score_neg``DiagnosticError`负分数组件
文件过滤

FFF 会遵循 `.gitignore` 文件。若要仅在选择器中忽略某些文件而不影响 Git,可添加一个同级的 `.ignore` 文件,例如:

```plaintext
*.md
docs/archive/**/*.md
```

运行 `:FFFScan` 可强制重新扫描。

故障排除
  • `:FFFHealth`:验证选择器初始化、可选依赖项和数据库连接。
  • `:FFFOpenLog`:打开日志文件。

FFF 是 Neovim 中最好的文件搜索选择器,具有更快、更直观的查询方式,支持访问频率排序、定义分类等诸多功能。

8. Node & Bun SDK

```bash
npm install @ff-labs/fff-node # 或 bun add @ff-labs/fff-node
```

```javascript
import { FileFinder } from "@ff-labs/fff-node";
const finder = FileFinder.create({ basePath: process.cwd(), aiMode: true });
if (!finder.ok) throw new Error(finder.error);
await finder.value.waitForScan(10_000);
const files = finder.value.fileSearch("incognito profile", { pageSize: 20 });
const hits = finder.value.grep("GetOffTheRecordProfile", { mode: "plain", smartCase: true, beforeContext: 1, afterContext: 1, classifyDefinitions: true });
finder.value.destroy();
```

每个方法都返回一个 `Result ({ ok: true, value } | { ok: false, error })`。完整的类型参考可查看 `packages/fff-node/src/types.ts`。这是一个基于 C 库的 TypeScript 包装器,可用于构建自定义代理工具、CLI 或 IDE 集成。

9. Rust crate

在 `Cargo.toml` 中添加依赖:

```toml
[dependencies]
fff-search = "0.6"
```

完整的 API 文档可查看 [docs.rs/fff-search](https://docs.rs/fff-search)。这是一个原生的 Rust crate,负责所有的搜索操作,稳定且文档完善。

10. C 库

构建

```bash
# 仅构建 C cdylib(最快)
make build-c-lib
# 或直接使用 cargo 构建
cargo build --release -p fff-c --features zlob
```

输出为一个 cdylib(`libfff_c.so` / `libfff_c.dylib` / `fff_c.dll`),头文件位于 `crates/fff-c/include/fff.h`。每个版本(包括主分支上的每次提交)的预构建二进制文件都可在发布页面找到,同样的二进制文件也包含在 `@ff-labs/fff-bin-*` npm 包中。

安装
  • 系统级安装(需要 sudo)
  • ```bash
    sudo make install
    ```

  • 用户级安装(无需 sudo)
  • ```bash
    make install PREFIX=$HOME/.local
    ```

  • 打包安装
  • ```bash
    make install DESTDIR=/tmp/pkgroot PREFIX=/usr
    ```

安装后,`libfff_c.{so,dylib,dll}` 会被放置在 `$(PREFIX)/lib` 目录,头文件会被放置在 `$(PREFIX)/include/fff.h`。使用 `make uninstall` 可移除安装,它会遵循相同的 `PREFIX` 和 `DESTDIR`。安装后,可通过以下方式链接:

```bash
cc my_app.c -lfff_c -o my_app
```

确保 `$(PREFIX)/lib` 在运行时库搜索路径中(Linux 上为 `LD_LIBRARY_PATH`,macOS 上为 `DYLD_LIBRARY_PATH`,或在 `/etc/ld.so.conf.d/` 中添加条目)。

最小示例

```c
#include
#include "fff.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 ", res->error);
fff_free_result(res);
return 1;
}
void *handle = res->handle;
fff_free_result(res);

// 搜索
FffResult *search = fff_search(handle, "main.rs", "", 0, 0, 20, 100, 3);
// ... 从 search->handle 读取 FffSearchResult,然后调用 fff_free_search_result()
fff_destroy(handle);
return 0;
}
```

注意:每个返回 `FffResult*` 的函数都使用 Rust 的 `Box` 进行内存分配,需使用 `fff_free_result` 释放,不要使用 `malloc` 的 `free`。有效负载(搜索结果、`grep` 结果、扫描进度)有各自独立的释放函数,列在头文件中。`handle` 字段中返回的 C 字符串(例如 `fff_get_base_path` 返回的)需使用 `fff_free_string` 释放。

11. 为什么选择 FFF 而非 ripgrep 或 fzf?

FFF 是一个文件搜索库,而非 CLI 工具。`ripgrep` 和 `fzf` 是优秀的命令行程序,但每次调用都会派生一个新进程,重新读取 `.gitignore`,重新统计目录信息,并在内存中重建所需状态后才能给出结果。在 shell 中进行单次 `grep` 操作时,这没问题;但当编辑器或 AI 代理在一个会话中需要进行数百次搜索时,就会出现性能问题。

FFF 将索引和文件缓存驻留在一个长期运行的进程中,并通过四层接口暴露相同的 Rust 核心:原生 crate(`fff-search`)、C 库(`libfff_c`)、Node/Bun SDK(`@ff-labs/fff-node`)和 MCP 服务器。只需调用一次 `FileFinder.create()`,后续的每次搜索都能命中热内存。在一个包含 500k 文件的 Chromium 仓库中,`ripgrep` 每次启动需要 3 - 9 秒,而 FFF 每次查询只需不到 10 毫秒。

FFF 的模糊匹配算法比 `fzf` 更全面,具备抗拼写错误能力,还提供了带有额外约束解析的查询语言用于预过滤。例如,`*.rs !test/ shcema` 对于 FFF 是一个有效的查询,但 `fzf` 即使在 `shcema` 有一个拼写错误的情况下也无法找到任何结果。

12. 程序化 API 的优势

  • 无需进程派生:每次调用都在进程内完成,避免了 `rg` 短时间调用时占主导的 `fork`、`exec`、`argv` 解析和 `stdout` 管道设置。
  • 一次文件系统遍历:在扫描时只进行一次 `.gitignore` 的读取、元数据收集和解析,结果可用于后续的每次搜索。
  • 结构化结果:结果以类型化对象形式返回,无需重新解析文本。SDK 直接提供 `{ relativePath, lineNumber, lineContent, gitStatus, totalFrecencyScore, isDefinition, ... }` 等信息。
  • 游标分页:支持跨调用的游标分页,而 `ripgrep` 没有“匹配结果的第 2 页”的概念。
  • 长期运行进程的优化:长期运行的进程可以进行一些一次性 CLI 无法实现的优化,如热缓存、增量重新索引、跨查询的访问频率排序和共享 SIMD 状态。

13. FFF 的核心功能

  • 基于访问频率的模糊匹配:每个索引文件都有访问分数和修改分数,搜索结果会将近期频繁打开的文件排在前面,类似于 VS Code 的最近打开列表,但应用于所有搜索结果,而非仅侧边栏。
  • 抗拼写错误的路径和内容匹配:`grep` 路径支持 Smith-Waterman 模糊评分;路径搜索使用基于 SIMD 加速的模糊匹配(通过 `frizbee` 派生的核心),能处理字符缺失和重排序的情况。
  • 三种内容搜索模式:普通文本(SIMD `memmem`)、正则表达式(Rust 正则表达式 crate)和模糊匹配(每行使用 Smith-Waterman 算法)。能根据模式自动检测使用哪种模式,普通搜索无结果时会回退到模糊匹配。
  • 多模式 OR 搜索:使用 SIMD Aho-Corasick 算法实现“同时查找 20 个标识符中的任何一个”,比正则表达式交替匹配更快,比 20 次单独的 `ripgrep` 运行快得多。
  • 后台文件监控器:文件更改时索引会自动更新,无需在搜索时进行重新扫描。
  • Git 状态感知:缓存修改、暂存、未跟踪和忽略的状态,并在每个结果中返回,调用者无需调用 `git` 命令即可对结果进行排序或过滤。监控器直接与 `libgit2` 通信,而非派生 `git` CLI。
  • 定义分类器:Rust 端的字节级扫描器会标记以 `struct`、`fn`、`class`、`def`、`impl` 等开头的行。

14. 性能优化

  • 高效的内存分配器和策略:默认使用 `mimaloc`。
  • 并行多线程搜索管道:不受编排逻辑的影响。
  • 优先使用 SIMD 算法:所有操作都采用高效的 SIMD 算法。
  • 高效且无分配的排序:减少内存开销。
  • 特定平台的文件系统优化:如 Linux 上的 `getdents64`、Windows 上的 NTFS API 等。
  • 轻量级实时内容索引:支持抗拼写错误的 `grep`。
  • 内存映射内容缓存:将部分文件存储在虚拟内存中(数量有限)。
  • 连续的字符串块存储:显著减少内存使用,提高 CPU 缓存命中率。

15. 内存使用

FFF 确实比调用单个子进程需要更多的内存,这也是其速度提升的主要原因。实际上,与 Neovim 中最流行的文件搜索选择器之一相比,FFF 使用的 RAM 比多次调用 `ripgrep` 更少。FFF 还维护一个内容索引,每个索引文件约 360 字节,因此对于一个 100k 文件的仓库,大约需要 36 MB。并非所有文件都会被索引,二进制文件、超大文件和不适合 `grep` 的文件会被跳过。如果内存占用仍然过高,索引可以使用内存映射文件而非匿名 RAM 来存储。

16. 实际应用场景

如果正在构建代理、IDE 扩展、预提交检查或任何需要多次搜索同一仓库的长期运行工具,将 FFF 作为库调用比调用 `ripgrep` 更高效。代价是需要占用一定的内存,FFF 将索引存储在 RAM 中并预热内容缓存。对于一个 14k 文件的仓库,大约需要 26 MB 内存;对于像 Chromium 这样的 500k 文件仓库,预计需要几百 MB 内存。作为回报,每次搜索都会包含 Git 状态、访问频率排序、文件元数据、最后访问和编辑时间戳等信息。

如果只是在终端中进行一次 `grep` 操作,`rg` 仍然是合适的工具;但如果在同一进程中进行多次搜索,从第二次调用开始,FFF 的优势就会显现出来。如果使用 AI 代理,FFF 会在 AI 调用之前完成准备工作。

17. 与其他工具的比较

  • ripgrep:FFF 使用相同的底层正则表达式引擎和更高级的纯文本匹配算法,存储内容索引和文件树,在重复搜索工作负载中表现出色,但在“从 bash 中进行一次 `grep` 然后退出”的场景中不如 `ripgrep`。
  • fzf:FFF 的路径搜索与 `fzf` 一样支持模糊匹配,但还具备访问频率感知和 Git 感知功能,并且算法更能容忍拼写错误。`fzf` 是一个纯粹的匹配和过滤工具,而 FFF 会根据文件的实际打开频率对结果进行排序。
  • Telescope / fzf-lua / snacks.picker:FFF 有自己的 Neovim 选择器,使用与 MCP 服务器和 SDK 相同的排序方式,选择器是可选的,核心功能保持一致。
  • Tantivy 或其他全文搜索引擎:它们属于不同类型的工具。Tantivy 用于大规模文档的查询时间评分索引,而 FFF 针对单个仓库进行优化,响应时间在 10 毫秒以内,且不会在磁盘上持久化倒排索引。

18. 仓库布局

  • `crates/fff-search`、`crates/fff-grep`、`crates/fff-query-parser`:Rust 核心代码。
  • `crates/fff-c`:用于所有语言绑定的 C FFI。
  • `crates/fff-nvim`:Neovim 插件的 Lua/mlua 绑定。
  • `crates/fff-mcp`:MCP 服务器二进制文件。
  • `packages/fff-node`:Node.js SDK(`@ff-labs/fff-node`)。
  • `packages/fff-bun`:Bun SDK(`@ff-labs/fff-node`)。
  • `packages/pi-fff`:Pi 扩展(`@ff-labs/pi-fff`)。
  • `lua/`:Neovim 端的插件代码。

19. 贡献

欢迎提交 bug 报告和拉取请求。可以使用自动化编码工具,但必须经过人工审核。

20. 许可证

FFF 采用 MIT 许可证,永远开源。

http://www.jsqmd.com/news/940316/

相关文章:

  • 手把手教你用STM32高级定时器TIM8生成20kHz SPWM波(从正弦表计算到代码实现)
  • 从Boss直聘zp_stoken看前端安全:那些年我们绕过的反爬与检测
  • Beyond Compare 5密钥生成器:5分钟解决文件对比工具激活难题
  • 别再傻傻分不清!CTP API里持仓和持仓明细到底啥区别?一个例子讲透
  • sql.js WASM 深度解析
  • 四足机器人地形自适应运动规划技术解析
  • SPSS/R/SAS三平台直接可用的PROCESS v4.3全套分析文件(含安装指南与模型模板)
  • 告别假货与仿真坑:用LMV358M设计工频信号采集前端,从选型、计算到Proteus验证的完整流程
  • 别再只会conda info --envs了!这5个隐藏技巧帮你高效管理Python环境
  • Halcon仿射变换保姆级教程:从旋转、平移到缩放,手把手搞定图像变形
  • PDF.js实战:如何用自定义事件总线实现PDF切片数据的高亮与精准跳转
  • 2026年6月江西评价高的膨润土品牌哪家专业,地连墙膨润土/盾构膨润土/涂料级膨润土/高黏膨润土,膨润土工厂哪家可靠 - 品牌推荐师
  • 别再手动翻译了!用UE5本地化工具+在线翻译,快速搞定游戏文本国际化
  • 终极AMD处理器调优神器:免费开源硬件调试工具完全指南
  • 如何让10美元鼠标秒变苹果触控板:Mac Mouse Fix终极配置指南
  • 大数据偏见:从数据源头到算法放大的系统性风险与治理实践
  • 微软研究院新英格兰实验室:跨学科融合如何重塑安全、隐私与密码学研究
  • FPGA BRAM不够用?试试这个手写多端口RAM的优化技巧,资源再省20%
  • 用数据说话 一键生成论文工具深度测评与推荐
  • 别再手动调参数了!用UE5材质函数快速搞定下雨积水动态水波纹(附完整材质蓝图)
  • 如何用Happy Island Designer打造梦幻岛屿:5分钟快速上手完整指南
  • Pyperclip实战:用Python打造你的专属剪贴板管理器(支持Windows/Mac)
  • 从监控到调优:深入解读Xilinx Clocking Wizard里那些容易被忽略的高级功能(7系列实测)
  • OpenClaw 私有部署 AI 助手:从零基础到飞书/钉钉智能聊天,4步搞定!
  • AI生成代码的7大安全风险:漏洞模式、检测方法与修复方案
  • 微针阵列技术:无痛生物信号采集与低功耗触觉反馈新突破
  • 从零训练 LLM:解析 GitHub 开源项目 train-llm-from-scratch
  • 保姆级教程:用STM32CubeMX配置FSMC驱动TFTLCD屏幕(STM32F103ZET6实战)
  • 为什么83%的Claude项目卡在机会识别?深度拆解4类隐性盲区与反脆弱识别框架
  • 政府与公共服务:从“群众跑腿”到“数据跑路”,电子签让政务更有温度