第 13 章:Git、搜索与导航——大型工程中的“跨文件视图”
Git diff、project search、references 和 diagnostics 的共同本质:先产生跨 Buffer 的结构化结果, 再投影成 MultiBuffer/Editor 或 Panel,而不是自己实现文本浏览器。
1. Git 三层架构
git crate
Repository trait / RealRepository / command execution
↓
project::GitStore + Repository Entity
local/remote、status、diff、checkpoint、事件
↓
git_ui
GitPanel、diff view、commit view、branch picker底层 Repository trait 暴露 status、stage、commit、blame、branches 等操作,不依赖 GPUI 产品 UI。
2. Worktree 与 GitStore 如何分工
Worktree scanner 发现 .git、common dir、work dir 和 repo root,并 watch refs/index。GitStore 为每个 发现的 repo 创建 Repository Entity,维护:
- branch/head/upstream;
- file status;
- staged/unstaged diff;
- commit message Buffer;
- operation queue;
- local backend 或 remote client。
源码入口:crates/project/src/git_store.rs:99-172、Repository 类型 :497-610。
3. 为什么 Git 状态也用 Snapshot + Event
git status 是一次昂贵、可能过时的 I/O。Repository 后台刷新后原子替换 RepositorySnapshot 并 emit RepositoryEvent。GitPanel、ProjectPanel decorations、Editor gutter 从同一 Snapshot 读取, 避免各自跑 Git 命令。
4. Diff 不是纯字符串比较
Zed 区分:
- working tree vs index(unstaged);
- index vs HEAD(staged);
- working tree vs HEAD(uncommitted);
- buffer current text vs disk/repo base;
- 任意 commit/base comparison。
BufferDiff 保存 base text、hunks、secondary status 和 word diff,并通过 Anchor/row 映射到当前 Buffer。Editor DisplayMap 再把 hunk background、deleted block 和 controls 投影到视觉行。
5. Stage hunk 为什么要经过文本映射
用户看到的 Buffer 可能有未保存 edit;Git index 基于磁盘/HEAD。Stage 当前视觉 hunk 时必须明确:
- hunk 针对哪个 base 和 Buffer version;
- 当前 Anchor range 是否仍对应同一文本;
- 生成 patch 应应用到 index 还是 worktree;
- 完成后刷新 status/diff;
- 远程模式把操作发送 host。
Project GitStore 集中处理这些语义,GitPanel 不直接运行 git add -p。
6. Agent checkpoint 复用 GitStore
Agent 修改前可创建 GitStoreCheckpoint,记录相关 repository/file 状态;用户 reject/undo 时恢复。 这比 Agent 自己复制文件更了解未跟踪文件、index 和多 repo,也能与 Editor transaction 联动。
7. Project search 的两阶段管道
SearchQuery(text/regex/case/include/exclude)
→ Worktree/remote host 找 candidate paths
→ 已打开 Buffer + 磁盘文件并行扫描
→ SearchResult stream
→ 按 Buffer/行聚合
→ 构建 MultiBuffer excerpts
→ ProjectSearchView 使用 Editor 显示项目层先找 candidate,UI 层边收到结果边更新,而不是等整个仓库扫描完才显示。
8. 已打开 Buffer 必须优先于磁盘
用户可能有未保存编辑。Search 如果只读磁盘会漏掉当前文本,甚至点击结果后位置错误。
ProjectSearch 会把 open Buffer 作为真实来源;磁盘 worker 跳过已打开路径。结果 range 用 Anchor 保存,后续输入后仍可解析。
9. Search cancellation 与 generation
每次 query 变化都会产生新 search task/generation:
- 取消旧 worker;
- 丢弃晚到的旧结果;
- 清理旧 MultiBuffer excerpts;
- 保留当前 selection 的合理迁移;
- 限制结果数量和并行读取。
Task 所有权和 query id 共同避免“快速输入时旧搜索覆盖新搜索”。
10. Search result 如何变成可编辑视图
matches_to_multibuffer 不是复制匹配文本,而是为每个 match 创建底层 Buffer 的 Excerpt,并加入 上下文行。源码:crates/search/src/text_finder/delegate.rs:605。
结果因此天然支持:
- syntax highlight;
- selection/copy;
- replace all transaction;
- 跳回原文件;
- Buffer 后续变化同步到结果。
11. Replace all 的原子边界
跨文件替换:
- 根据当前 SearchResult Anchor 重新解析 range;
- 检查 overlap/失效 match;
- 按 Buffer 分组 edits;
- 每个 Buffer 开 transaction;
- 汇总 ProjectTransaction;
- 更新 MultiBuffer 和 selection;
- 用户一次 undo 恢复多文件变化。
这与 LSP rename、Agent multi-file edit 共用相同的 transaction 思想。
12. File finder 与 project search 的不同
File finder 使用 worktree path index + fuzzy matcher,目标是“找到一个路径”;Project search 扫描 文件内容,目标是“产生多组文本 range”。两者都异步、可取消、受 ignore/visibility 约束,但结果 模型不同。
13. Symbols 与 references 再次复用同一模式
- project symbols:LSP 返回 locations → ProjectPath/Anchor → picker;
- references:多个 location → MultiBuffer excerpts;
- diagnostics:DiagnosticSet → MultiBuffer/Panel;
- Git diff:hunks → MultiBuffer/Display blocks。
“结果生产器 + 稳定 location + 通用视图”是 Zed 工程功能的重复架构。
14. Local/Remote 搜索
Remote Project 不把整个仓库下载到本地。candidate discovery 和磁盘内容扫描在 host;已同步的 open Buffer 仍可在本地参与。Result 通过 proto 回来后转换为 ProjectPath/Anchor,再走同一 MultiBuffer。
15. 可迁移经验
- 后台服务只产出结构化 range,不产出 UI;
- open in-memory document 优先于 disk;
- 跨 await 的 match 使用 Anchor/version;
- 跨文件结果投影到通用 composite document;
- replace/rename/Agent edit 共享多 Buffer transaction;
- local/remote 分叉放在 Project store,不放在结果视图。