06|项目搜索与 Quickfix
项目里只有一个文件时,/ 通常就够用了。等文件多起来,还得再认识两个面向整个项目的工具:
- Space /:在整个项目中搜索;
- Quickfix:把多条搜索结果保留在底部,方便逐项跳转。
这一章最后,我们会直接用 Quicker 编辑 Quickfix 结果,再把修改写回多个文件。动到的范围比较大,所以开始前先留一份 Git 基线。
1. 开始前的项目检查点
src/pocket_tasks/model.py 应为:
from dataclasses import dataclass
from datetime import datetime
@dataclass(slots=True)
class Task:
title: str
created_at: datetime
completed: bool = False
README 里应该已经有 Goals 和 Quick start。如果内容还是对不上,先照上一章结尾的检查点补齐。
先提交一份便于恢复的基线
右侧内置终端要到第 10 章才会介绍,这一节先用外部终端。
- 在 Neovim 中按 F1,输入
wall,选中同名命令后连按两次 Enter。wall会保存所有已修改文件。 - 按 F1,输入
wqa,连按两次 Enter 退回终端。 - 在
pocket-tasks目录中依次运行:
git status --short
git add .
git commit -m "checkpoint before quickfix"如果因为没有 Git 身份而提交失败,可以只给当前练习仓库设一个临时身份,然后重新提交:
git config user.name "Nvim Student"
git config user.email "nvim@example.invalid"
git commit -m "checkpoint before quickfix"这两项只会写进当前仓库的 .git/config,不会改到其他项目,更不会动全局 Git 身份。
如果第三条命令提示没有可提交的内容,说明手头已经有一份可用的 Git 基线,可以继续。然后重新打开项目:
nvim .如果后面的批量修改出了问题,先退出 Neovim,再到项目根目录运行 git diff,看看究竟改了哪些地方。只有确定要放弃这一章的全部未提交改动时,才能用 git restore .。这个命令会丢掉当前项目里所有还没提交的修改,运行前一定要再次确认自己所在的目录。
2. 工作流一:用 Space / 准确定位内容
这一轮只记四件事:
| 按键 | 在项目搜索 Picker 中的效果 |
|---|---|
| Space / | 打开项目全文搜索 |
| Ctrl+n / Ctrl+p | 选中下一条 / 上一条结果 |
| Enter | 打开当前结果并跳到命中位置 |
| Esc | 关闭 Picker,不打开任何结果 |
使用搜索定位 README
确认在普通模式,按 Space /。
Picker 出现后,直接输入
Quick start。候选会随着输入不断缩小,不用先按回车。

- 若有多条结果,用 Ctrl+n 和 Ctrl+p 上下选择。
- 选中 README 里的
## Quick start,按 Enter。
Picker 会随即关闭,README.md 会在主编辑窗口里打开,光标落在匹配位置附近。

现在按 G 到文件末尾,按 o 新建一行,输入下面这段:
## Data model
A task stores:
- `title`
- `created_at`
- `completed`输完以后按 Esc。
按 F1,输入 write。

按第一次 Enter,让命令行形成 :write。
再按一次 Enter 保存。

后面的跨文件搜索练习,就要用到这三个字段。
关闭一次 Picker
- 按 Space /。
- 输入
pocket。

按几次 Ctrl+n 看看不同文件的预览。
按 Esc。

文件和光标都会留在打开 Picker 之前的位置。如果发现搜错了,直接按 Esc 取消就行。
Space / 和 / 的分工
| 场景 | 按键 | 搜索范围 |
|---|---|---|
| 已知道内容在当前文件 | /文字 → Enter | 当前 Buffer |
| 只知道它在项目某处 | Space /,再输入文字 | 当前项目 |
Space / 底层用的是 ripgrep,也会遵守项目的 Git ignore 规则,所以搜索结果里通常不会混进构建产物。
3. 工作流二:选择部分结果并加入 Quickfix
搜索 Picker 适合找到一处就马上打开。如果想连续查看好几个结果,可以把它们放进 Quickfix,让结果列表一直留在底部。
这一轮新增:
| 按键 | 效果 |
|---|---|
| Tab | 选中当前 Picker 结果,并移到下一条 |
| Ctrl+q | 把已选结果加入 Quickfix;没有选中项时会加入当前全部结果 |
| ] q / [ q | 跳到下一条 / 上一条 Quickfix 项 |
只挑两条 tasks
- 按 Space /,输入
tasks。

用 Ctrl+n / Ctrl+p 找到一条想保留的结果。
按 Tab。当前行出现选中标记,高亮自动移到下一条。
再选一条,按 Tab。

- 按 Ctrl+q。
Picker 会关闭,底部随即打开 Quickfix 窗口。刚才明确选了两项,所以这里也只有两条。Quicker 会分别给文件名、行号和源代码加上样式,看起来更清楚。

在 Quickfix 中逐项查看
- Quickfix 刚打开时,焦点在底部。按 j / k 选择其中一条。
- 按 Enter。对应文件会在上方编辑窗口打开,光标跳到命中位置;底部 Quickfix 列表继续保留。

- 在上方代码中按 ] q,跳到下一条。

- 按 [ q,返回上一条。
Quickfix 可以带着你在不同文件之间来回跳。上方窗口显示当前结果对应的代码,底部的结果列表则会一直留着。
检查完成后,按 F1,输入 cclose。

连按两次 Enter 关闭 Quickfix 窗口。

4. 工作流三:在 Quickfix 里跨文件改名
Quicker 允许直接编辑 Quickfix Buffer,改结果行就跟改普通文本一样。
保存 Quickfix 会立即写回源文件
在 Quickfix 里按 u,可以撤销还没保存的编辑。一旦保存,改动就会写回源文件。前面留下的 Git 基线,就是拿来检查或恢复这些改动的。
把 completed 批量改成 done
- 按 Space /,输入
completed。 - 确认结果包含
model.py中的字段和 README 中的列表项。

- 这次不按 Tab,直接按 Ctrl+q。没有手动选中项时,当前全部结果都会进入 Quickfix。

- 在底部 Quickfix 中输入
/completed,按 Enter。这里的 / 搜索当前 Quickfix Buffer。 - 按 c i w,输入
done,按 Esc。

- 按 n 到下一个
completed,再按 . 重放刚才的修改。
这时改动还只在 Quickfix Buffer 里,底部状态会提示它已经修改。先把两行都看一遍,确认两处都变成了 done。如果改错,就按 u 撤销,修好再继续。

确认无误后:
按 F1,输入
write。用 Ctrl+n / Ctrl+p 选中准确的
write命令。连按两次 Enter。第一下把命令送到底部命令行,第二下执行写回。

这次保存会让 Quicker 正式应用修改。按照当前设置,没有其他未保存内容的源 Buffer 会同时写入磁盘;事先已经修改过的源 Buffer 则会继续保持“已修改”状态,仍需单独保存。
按 F1,输入 cclose。
连按两次 Enter 收起底部窗口。
再按 Space /,搜索 completed。

- 应该没有任何结果;
- 按 Esc 关闭无结果的 Picker;
- 重新按 Space /,搜索
done,应看到 README 和model.py的两处结果。
5. 工作流四:从 Quickfix 删除条目,再修改源文件
眼下这个小工具还用不上 created_at。它一共出现在两行:
model.py的字段定义;- README 的字段列表。
这一轮要分清两种“删除”:在 Quickfix 里按 d d,只是去掉一条搜索结果;只有在源码 Buffer 里按 d d,才会真的删掉代码行。
- 按 Space /,输入
created_at。 - 确认 Picker 正好有上面两条结果。如果数量更多,请通过 Tab 只选这两条。

- 按 Ctrl+q 加入 Quickfix。

- 按 g g 到 Quickfix 第一行。
- 看清当前行的文件名,按 d d。

当前结果会从底部的 Quickfix 列表里消失。按 u 可以把这一条找回来。

再按 d d 删除同一条目。
检查完以后,按 F1 → write → Enter → Enter。这次保存只会更新 Quickfix 列表,从列表里删掉结果,并不会连带删除源文件里的那一行。
按 F1 → cclose → Enter → Enter。再用 Space / 搜索 created_at,两份源文件里的结果应该都还在。这里一定要分清:修改 Quickfix 行里的文字会写回源文件,删除整条 Quickfix 结果却只会改变列表本身。
现在回到真正的源码删除流程:
- 在当前搜索 Picker 中选中
model.py里的字段结果。

按 Enter 打开它。
光标落到
created_at: datetime后按 d d。输入
/from datetime,按 Enter,再按 d d 删除已经无用的导入。

按 F1,输入
write。连按两次 Enter 保存
model.py。按 Space /,再次搜索
created_at;此时只应剩 README 的列表项。

- 按 Enter 打开它,光标落在
- \created_at`` 上后按 d d。

- 按 F1 →
write→ Enter → Enter 保存 README。
最后用 Space / 搜索 created_at,应得到空结果。

如果还有结果,按 Enter 打开,先把文件和上下文看清楚,再决定要不要删。
再搜索 class Task,确认模型类仍然完整。

使用 dd 前先确认当前 Buffer
在 Quickfix 里,d d 删除的是结果条目;在源码 Buffer 里,d d 删除的才是代码行。动手前先确认自己在哪个窗口。
6. 结果检查
用 Space / 搜索 class Task,按 Enter 打开 model.py。它现在应该是:
from dataclasses import dataclass
@dataclass(slots=True)
class Task:
title: str
done: bool = FalseREADME 末尾应该是:
## Data model
A task stores:
- `title`
- `done`按 F1 → wall → Enter → Enter,再把所有文件保存一次。这一章的改动可以先留在 Git 工作区,等第 11 章再统一处理。
本章肌肉记忆
| 目标 | 按键 |
|---|---|
| 项目全文搜索 | Space / |
| 在 Picker 中上下选择 | Ctrl+n / Ctrl+p |
| 打开结果 / 关闭 Picker | Enter / Esc |
| 选中多条 Picker 结果 | Tab |
| 把已选项或全部结果加入 Quickfix | Ctrl+q |
| 前后遍历 Quickfix | [ q / ] q |
| 将 Quicker 编辑应用到源文件 | 在 Quickfix Buffer 中保存 |
| 从 Quickfix 移除当前结果 | Quickfix 中按 d d;源文件保持原样 |
| 关闭 Quickfix 窗口 | F1 → cclose → Enter → Enter |
上一章:包围与注释 · 下一章:Buffer、分屏与 Tab