Skip to content

第 11 章:Workspace、Action 与 Settings——产品外壳怎样保持可组合

Workspace 是一个窗口中的产品级所有权根:一个 Project、中心 PaneGroup、三个 Dock、状态栏、 item registry、导航历史与持久化。Action 负责意图路由,Settings 负责分层配置合并。

1. Workspace 不等于 Project

Project 表示工程语义,Workspace 表示窗口布局与交互状态。同一个 Project 可以被不同窗口打开, 一个 Workspace 也可能包含多个 worktree。

Workspace 源码注释定义了典型组成:Project、central pane group、3 docks、status bar。 源码:crates/workspace/src/workspace.rs:1366-1372

2. Item:中心区域的共同协议

Editor、ProjectSearch、Settings、Markdown Preview、Git Diff、Terminal(center mode)都可成为 Item。 Item trait 组合:

  • Focusable;
  • EventEmitter;
  • Render;
  • tab title/icon/dirty state;
  • save/reload/navigation hooks;
  • clone/split capability;
  • optional serialization。

源码入口:crates/workspace/src/item.rs:170

ItemHandle 做类型擦除,让 Pane 能持有异构 item,同时保留 downcast 到具体 Entity 的能力。

3. Pane 与 PaneGroup

Pane 管一组 tabs:

  • active item;
  • preview item;
  • pinned/dirty tabs;
  • activation history;
  • drag/drop;
  • close/save prompt;
  • zoom state。

PaneGroup 是 split tree:叶子是 Pane,内部节点是水平/垂直 axis 和 flex。拆分编辑器不是创建 特殊 window,而是修改这棵布局树。

4. Dock 与 Panel

左、右、下 Dock 持有实现 Panel trait 的实体,例如 ProjectPanel、AgentPanel、GitPanel、 TerminalPanel。Panel 提供:

  • position/size/default width;
  • activation/focus;
  • icon/tooltip;
  • zoom 和 persistence id。

源码:crates/workspace/src/dock.rs:36-105

Item 和 Panel 分开,避免“工具面板”和“主文档 tab”共享一堆不自然的行为。

5. 打开路径时谁决定用哪种 Item

Workspace 不硬编码所有文件类型。ProjectItemRegistry 保存 open handler,按注册逆序让每种 Item 尝试处理目标:图片、CSV、Markdown preview 或普通 Editor。

源码注释:crates/workspace/src/workspace.rs:961-963

扩展一个新 viewer 的关键不是改 Workspace::open_path 巨型 match,而是注册新的 ProjectItem。

6. Action 从窗口全局到焦点局部

Action dispatch 沿焦点树冒泡:

text
Keymap 匹配 Action
  → 当前 focused element handler
  → Editor/Panel handler
  → Pane handler
  → Workspace handler
  → App global handler

于是 cancel 在 completion menu、Editor、modal、Workspace 中可以有不同含义;最靠近焦点且能处理 的对象优先。

Context-sensitive keymap

元素在 prepaint 建立 key context,例如 Editor && vim_mode == normal。Keymap 不把产品状态写进 按键字符串,而是针对当前 focus path 的 context predicate 选择 binding。

7. 导航历史为何属于 Workspace

Definition、search result、outline、Git hunk 都可能跨 Item/Pane 跳转。单个 Editor 只知道自己的 selection,Workspace 才知道:

  • 从哪个 item/location 离开;
  • 目标在哪个 pane 打开;
  • back/forward 时是否要重建 item;
  • 最近激活 item 的顺序。

所以导航历史放在窗口所有权根,而不是 Buffer。

8. Settings 的多层来源

SettingsStore 合并大致优先级:

text
default
  < server/managed
  < user
  < profile
  < extension contribution
  < project/worktree local settings
  < language-specific override

具体字段通过 SettingsContent::merge 合并;每个强类型 Settings 实现 from_settings 投影自己关心 的部分。

9. 强类型 Settings 与动态 JSON 的桥

用户编辑 JSONC,但业务代码读取:

rust
let settings = EditorSettings::get_global(cx);
let local = EditorSettings::get(Some(SettingsLocation { ... }), cx);

SettingsStore 为每种注册类型保存 SettingValue<T>:global value 加多个 (WorktreeId, RelPath, T) local value。源码: crates/settings/src/settings_store.rs:252-271

这让热路径无需反复查询 JSON Value,同时保留按路径覆盖。

10. Settings 更新为何串行

文件 watcher、UI 编辑、profile 切换、project settings 可能同时更新。SettingsStore 使用 channel 顺序处理 setting file update,解析、合并、更新强类型值后再 notify Global observer。

源码:crates/settings/src/settings_store.rs:287-335

串行化比给每种 Settings 单独 watch 更容易保证“所有类型看到同一版 merged settings”。

11. Workspace 持久化不是内存快照

可恢复状态只保存稳定描述:

  • local/remote workspace location;
  • worktree roots;
  • pane split tree 和 active tab;
  • serializable item kind + item-specific payload;
  • dock/panel state;
  • recent navigation/scroll/selection(由 item 决定)。

SerializableItem trait 要求 kind、serialize、deserialize、should_serialize。源码: crates/workspace/src/item.rs:408-473

恢复时 registry 按 kind 找反序列化器;单个 item 失败不会阻止整个 Workspace 恢复。

12. Dirty close 是编排而不是一个 confirm

关闭窗口可能涉及多个 Pane 和 Item:

  1. 收集所有 dirty、可保存 item;
  2. 合并同一 Buffer 的重复视图;
  3. 根据 CloseIntent/Autosave 决定保存、丢弃或提示;
  4. await save task;
  5. 让 terminal/debug/remote connection 执行 shutdown;
  6. 写最终 Workspace state;
  7. 允许 Window 关闭。

这也是为什么 close 逻辑属于 Workspace,而不是每个 tab 自行弹窗。

13. 可迁移设计

  1. 业务文档用 Item protocol,工具面板用 Panel protocol;
  2. path opener 使用 registry,不把所有类型写进中心 switch;
  3. action 通过 focus path 路由,keymap 只表达意图;
  4. JSON config 先合并,再投影成强类型快照;
  5. 持久化稳定描述符,不序列化运行时对象图。

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