Skip to content

第 1 章:全景——Zed 到底是什么 ​

一句话定义:Zed 是一个以 GPUI 实体运行时为外壳、以可快照化协同文本模型为内核、 以 Project 抽象承接本地与分布式工程能力的原生桌面开发环境。

1. 先纠正三个直觉 ​

1.1 它不只是“Rust 写的编辑器” ​

如果只看到 Rust 和 GPU,会漏掉 Zed 更关键的结构:文本从一开始就能产生可复制的操作, Project 从一开始就允许 local/remote 两种实现,UI 状态从一开始就被 GPUI 的实体和事件管理。

也就是说,协作、远程和 AI 不是后来挂在单机编辑器上的三块功能,而是不断复用既有内核:

text
                    ┌──────────────┐
用户动作 / Agent ──▶│ Workspace/UI │
                    └──────┬───────┘
                           │
                    ┌──────▼───────┐
                    │   Project    │  统一工程语义
                    └───┬──────┬───┘
                        │      │
             local store│      │remote store / proto
                        │      │
               ┌────────▼──┐ ┌─▼────────────┐
               │ Worktree  │ │ Remote host  │
               │ LSP / Git │ │ collab host  │
               └─────┬─────┘ └──────┬───────┘
                     └──────┬────────┘
                       ┌────▼────┐
                       │ Buffer  │  operation + snapshot
                       └─────────┘

1.2 GPUI 不只是绘图库 ​

GPUI 同时提供:

  • 应用和窗口事件循环;
  • Entity<T> 状态容器与上下文受控读写;
  • observe/subscribe/notify 反应式传播;
  • 前台与后台 executor;
  • Action、焦点、键位分发;
  • Element 布局、prepaint、paint 与 GPU Scene。

因此应用代码里的 cx 不是“顺手传的上下文”,而是能力边界。没有合适的 cx,就不能 随意修改实体、窗口或全局状态。

1.3 Editor 不是直接编辑文件字符串 ​

最短的数据链也至少经过:

text
filesystem file
  → language::Buffer(文本 + 语法 + 诊断 + 文件状态)
  → MultiBuffer(一个视图拼接一个或多个 Buffer 片段)
  → DisplayMap(inlay → fold → tab → wrap → block → highlight)
  → EditorElement(布局、输入、选择、命中测试)
  → GPUI Scene(quad / glyph / path / surface)

这解释了搜索结果、diff、diagnostics 或 Agent edit 为什么仍能复用同一个 Editor:它们改变 的是 MultiBuffer 的摘录和显示映射,不必另写一套文本控件。

2. 六层架构地图 ​

第 1 层:平台与渲染 ​

代表 crate:gpui_platform、gpui_macos、gpui_linux、gpui_windows、 gpui_wgpu、gpui。

职责是接收 OS 事件、维护窗口、排版文字、组织场景并提交 GPU。平台差异被压在底层, 上层只面对 Application、Window、Element 和 Scene。

第 2 层:基础数据结构 ​

代表 crate:sum_tree、rope、text、clock、collections。

这里解决大文档增量编辑、多个坐标维度快速跳转、持久化快照、操作排序和逻辑位置稳定性。 这是 Zed “快”与“可协作”的共同地基。

第 3 层:编辑语义 ​

代表 crate:language、multi_buffer、editor、buffer_diff、markdown。

language::Buffer 把纯文本提升成源码文件;MultiBuffer 把多个摘录合成可编辑文档; Editor 管选择、命令、补全和显示状态。

第 4 层:工程模型 ​

代表 crate:fs、worktree、project、lsp、git、task、terminal、dap。

这里把文件树、打开的 Buffer、语言服务器、Git 仓库、任务、终端和调试会话组合成一个 可本地也可远程的 Project。

第 5 层:产品工作区 ​

代表 crate:workspace、ui、project_panel、git_ui、terminal_view、 settings_ui、theme。

Workspace 对应一个窗口中的完整产品状态:中心 pane group、三个 dock、status bar、 Project 以及所有可打开 item。

第 6 层:分布式与智能 ​

代表 crate:client、rpc、proto、collab、remote、agent、agent_ui、 language_model、extension_host。

这一层把同一套工程模型连接到云端房间、远程主机、语言模型和 Wasm 扩展。

3. 一次编辑的完整旅程 ​

假设用户在一个 Rust 文件中输入 x。

步骤 A:平台事件进入 GPUI ​

平台适配器把键盘输入交给 Window。Keymap 根据焦点路径和 context 匹配 Action;如果是文本 输入,则通过输入处理器交给 Editor。

步骤 B:Editor 把显示坐标还原为 Buffer 坐标 ​

屏幕上的 cursor 使用 DisplayPoint。Editor 借助 DisplaySnapshot 依次穿过 block、wrap、 tab、fold、inlay 层,最终解析到 MultiBuffer 和底层 Buffer 的 Anchor/offset。

步骤 C:Buffer 产生 operation ​

language::Buffer 委托 text::Buffer::edit。后者不只修改 Rope,还生成带 Lamport 时间戳的 EditOperation,更新版本向量、undo history 与 Anchor 可解析结构。

源码入口:crates/text/src/text.rs:747-909。

步骤 D:事件向上冒泡 ​

文本 Buffer 触发 operation/edited 事件,language::Buffer 重新安排 Tree-sitter parse, MultiBuffer 汇总底层 edit,DisplayMap 把失效范围逐层变换,Editor 通知 GPUI 重绘。

步骤 E:副作用并行展开 ​

  • LSP Store 发送 didChange;
  • collaboration host 广播 UpdateBuffer;
  • Git diff 重新计算受影响 hunk;
  • edit prediction 更新上下文;
  • Agent/diagnostics 观察者可能刷新界面。

关键点是这些消费者不需要把完整文档重新读一遍。它们拿到 operation、版本或局部 patch, 各自进行增量工作。

4. 本地、协作、远程为什么能共用上层 UI ​

Project 是语义门面 ​

Project 的源码注释直接说明:它负责 tasks、LSP、collab queries,并同步 worktree 状态; 同一个 Project 可以是 local 或 remote。

证据:crates/project/src/project.rs:210-215。

上层调用 project.open_buffer(...)、project.references(...)、project.format(...),不需要 先判断目标是在本机、协作 host 还是 SSH server。

Store 内部分叉 ​

Project 不是把所有分支塞进一个巨大 if remote。它组合多个 Store:

Store本地职责远程职责
WorktreeStore扫描文件系统接收 worktree proto 更新
BufferStore加载/保存文件创建远端 Buffer 副本、同步 operation
LspStore启动 language server转发 LSP request/response
GitStore调用 Git backend请求 host 执行 Git 操作
TaskStore解析并运行任务请求远端终端/任务

这是一种“门面稳定、能力下沉、传输可替换”的分布式客户端架构。

5. 规模数字应该怎样解读 ​

按 Rust 文本行数粗略统计,最大的几个领域是:

crate约行数为什么大
editor161k显示、输入、选择、补全、LSP UI 与大量测试
project104k文件、LSP、Git、任务、调试、local/remote 双路径
agent86kloop、工具、权限、沙箱、持久化与 eval
agent_ui84k会话、消息、diff、选择器、审批和历史界面
gpui75k状态运行时、Element、窗口、文本排版和测试设施
workspace50kpane、dock、item、持久化与窗口级动作

行数不是架构重要性的排名。例如 sum_tree 只有约 3.3k 行,却被 Rope、Buffer、 MultiBuffer、DisplayMap、worktree 等热路径反复复用。

6. Zed 的四个核心设计选择 ​

6.1 用持久化摘要树统一“序列 + 索引” ​

SumTree 的每个内部节点都保存子树 Summary,并允许同一 Summary 提供多个 Dimension。 Rope 可以按 byte、Point、UTF-16 point 跳转,显示映射也能用同一模式维护输入/输出坐标。

6.2 用 Entity 约束可变状态,用 Snapshot 扩散读取 ​

可变状态留在 GPUI 前台实体;昂贵或异步计算拿不可变快照。这比到处共享锁更容易推理, 也让 UI 的生命周期和异步任务取消更清晰。

6.3 用 operation 表达可合并变化 ​

Operation 既服务 undo/redo,也服务协作复制、远程同步和版本等待。协作不是一条额外路径, 而是文本状态变化本身就具备分布式语义。

6.4 用组合根显式装配产品 ​

crates/zed/src/main.rs 中数十个 init 调用看起来很长,但它让产品能力的注册顺序、全局 依赖和平台分支集中可见。下一章先理解 crate 地图,再进入这个组合根。

独立源码学习笔记 · 文档采用 CC BY-SA 4.0