Skip to content

第 17 章:Remote Development——本地绘制,远端拥有工程资源

Remote Zed 不是远程桌面,也不是把仓库全量同步到本地。UI/GPUI 留在本机,Project 的文件、LSP、 Git、terminal 等资源放在 remote server,通过同一套 store/proto 边界重新连接。

1. 进程如何切开

mermaid
flowchart LR
  subgraph Local["本机 zed"]
    GPUI["GPUI / Window"]
    W["Workspace / Editor"]
    RP["Remote Project proxies"]
    RC["RemoteClient"]
    GPUI --> W --> RP --> RC
  end
  subgraph Transport["SSH / WSL / Docker channel"]
    MUX["multiplexed proto + heartbeat"]
  end
  subgraph Remote["远端 zed remote_server"]
    PS["Project stores"]
    FS["Worktree / filesystem"]
    LS["LSP / DAP"]
    GT["Git / terminal / tasks"]
    PS --> FS
    PS --> LS
    PS --> GT
  end
  RC --> MUX --> PS

鼠标、键盘、布局、文本渲染仍在本地,因此交互无需传像素;依赖 repo 的昂贵工作靠近数据执行,因此 不需要先下载整个 worktree。

2. RemoteClient 是长期会话

crates/remote/src/remote_client.rsRemoteClient 持有:

  • ChannelClient/ProtoClient;
  • 唯一 connection id 与 connection options;
  • remote platform、path style;
  • project/connection state;
  • heartbeat 与 reconnect task;
  • 启动过的 setup/workspace server 信息。

它不是一次 SSH command wrapper,而是从启动 remote server 到项目关闭的长期对象。

3. 两层连接状态

公开 ConnectionState 面向 UI:Connecting、Connected、HeartbeatMissed、Reconnecting、Disconnected。 内部 State 还区分 ReconnectFailed、ReconnectExhausted、ServerNotRunning 等恢复细节。

显式状态让调用者知道:

  • HeartbeatMissed 时旧数据仍可显示,但新请求风险升高;
  • Reconnecting 是暂时态,不应立即关闭所有 editor;
  • ReconnectExhausted 才需要用户采取动作;
  • 主动 shutdown 与网络故障不是同一个错误。

源码入口:crates/remote/src/remote_client.rs:307-380

4. 多种 transport,共同的 RemoteConnection

RemoteConnectionOptions 支持 SSH、WSL、Docker 和 Mock。每种 backend 处理自己的连接与进程启动:

  • SSH:认证、host/port、shell 与远端命令;
  • WSL:选择 distribution,通过 Windows/WSL 边界启动;
  • Docker:container/exec 生命周期;
  • Mock:测试断线、重连与协议行为。

后续 Project 不应出现 if ssh/if wsl。transport 都转换为同一个双向 async channel 和 platform/path metadata。

5. Remote server 获取流程

连接建立后,本地先探测远端 OS、architecture 与当前 remote server 状态。若不存在兼容 binary,则下载/ 上传正确构建,校验并启动 headless remote_server,最后握手协议版本。

text
connect transport
  → detect platform + arch + shell
  → locate compatible remote_server
  → acquire/upload if missing
  → launch setup server
  → negotiate protocol
  → launch/attach workspace server
  → open Project

binary 版本必须与本地协议兼容;仅判断“文件存在”会让应用升级后连接到旧 server 并产生难以解释的解码 错误。

6. Setup 与 Workspace connection identity

ConnectionIdentifier 区分 Setup 和 Workspace。Setup 负责准备环境;Workspace 对应真正的 Project 会话。 重连时保持同一 workspace/socket identity,才能重新附着到原 server 进程和已打开资源,而不是悄悄创建第二个 LSP、terminal 和 worktree scanner。

源码:crates/remote/src/remote_client.rs:345-350

7. Project stores 在 local/remote 何处分叉

UI 仍调用 Project 的统一 API;分叉集中在各 store:

能力Local storeRemote store
open/read/save Buffer本地 FS + Bufferproto request + 同步 Buffer
Worktree scan本地 scanner远端 scanner,增量 entries 回传
LSP本机 child process远端 server 进程与 LspStore
Git本机 Repository远端 GitStore
terminal/task本机 PTY/process远端 PTY/process,流式输出
search本机遍历文件远端 candidate/内容扫描

结果回到本机后都转换为同样的 Buffer、ProjectPath、diagnostic、search result 等领域对象,Editor 无需维护 两套 UI。

8. 为什么 Buffer 仍在本地有副本

每次绘制、光标移动和 selection 都依赖文本快照,不能为读取一行去远端 round trip。打开文件后本地建立 Buffer 副本,远端发送初始化内容和后续 operation;本地编辑立即应用,再发往 remote host。

这与协作复用 Buffer operation 基础,但参与者不同:remote 通常是同一用户的 UI 与工程 host,协作是多个 有角色的用户共享项目。

9. Heartbeat 不只是“网络还通”

TCP/SSH channel 可能半开:本地 socket 未报错,但远端进程已冻结或中间设备丢包。应用级 heartbeat 记录 最近响应时间,超过阈值进入 HeartbeatMissed 并触发恢复判断。

heartbeat payload/ack 应走与业务消息相同的复用通道,才能验证整个读写 loop,而不只是另开一个健康端口。

10. 重连的层次

mermaid
sequenceDiagram
  participant UI as Workspace
  participant RC as RemoteClient
  participant T as Transport
  participant RS as Remote Server
  UI->>RC: 普通 project request
  RC--xUI: connection lost
  RC->>UI: ConnectionState::Reconnecting
  RC->>T: 重新建立 SSH/WSL/Docker channel
  T->>RS: attach same workspace identity
  RS-->>RC: handshake + session state
  RC->>RS: resubscribe entities / synchronize buffers
  RS-->>RC: missed deltas / snapshots
  RC->>UI: Connected,恢复 requests

重连不是简单替换 socket。必须依次恢复 transport、协议会话、workspace identity、entity subscription、Buffer 版本和 streaming services。

11. Request 的可重试性

断线发生时有三种情况:未发送、远端未执行、远端已执行但 response 丢失。只读 open/status 通常可重试; 创建 terminal、运行 task、写文件等副作用请求需要 operation id 或显式确认,不能盲目重放。

协议设计应给命令分类:

  • idempotent query;
  • 具有稳定 request/operation id 的可去重 mutation;
  • 必须由上层重新决策的 non-idempotent action。

12. PathStyle 是领域数据

本机可能是 macOS,远端可能是 Windows/WSL。path separator、case sensitivity、可执行文件后缀和 shell quoting 不能用本机 std::path 规则解释。RemoteClient 在握手后保存 remote platform/path style;ProjectPath 通过 worktree id + 相对 path 跨协议传输。

UI 展示可使用友好格式,但查找、比较和命令参数必须遵守 remote 语义。

13. Remote 与 Collaboration 的关系

维度Remote DevelopmentCollaboration
目的操作另一台机器上的工程多人共享同一项目
信任通常同一用户控制 host多用户、role/capability
连接SSH/WSL/Docker 直连cloud/collab WebSocket
权威remote serverproject host + collab server 权限
文本本地副本与 host 同步多客户端 CRDT 收敛
资源全部工程服务在远端host 执行工程服务

二者复用 Project store、proto 与 Buffer operation 思想,但不能混成一个状态机:认证、路由、权限和重连 语义不同。

14. 关闭与孤儿进程

正常关闭需停止 heartbeat/reconnect task、关闭 project channel,并请求 remote server 清理对应 workspace process;异常断线则可能保留 server 以便短时间重附着。过早 kill 会破坏重连,永不回收会留下 LSP/terminal 孤儿进程。

因此 server 端需要 session ownership、租约/超时和显式 shutdown 的组合,而不是把 child process 生命周期 绑死在单个 TCP socket。

15. 测试最重要的不是 happy path

Mock remote backend 应覆盖:

  • handshake 前/后断线;
  • heartbeat 丢失但 channel 未关闭;
  • request 已执行、response 丢失;
  • 重连时 Buffer 又发生编辑;
  • server binary 版本不匹配;
  • remote path style 与本机不同;
  • 同一 workspace 被重复 attach;
  • shutdown 与自动 reconnect 竞争。

16. 可迁移经验

  1. UI 留近用户,工程服务留近数据;
  2. local/remote 分叉收敛在领域 store,不扩散到视图;
  3. transport backend 统一为稳定 channel abstraction;
  4. 协议版本、平台和 path style 在握手时显式协商;
  5. heartbeat 检测半开连接,重连恢复完整会话而非仅 socket;
  6. 副作用请求明确去重/重试语义;
  7. server process 生命周期独立于瞬时网络连接。

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