yoi/docs/ref/zed-workspace.md
2026-04-21 17:39:43 +09:00

239 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Zed モノレポのワークスペース・クレート規則
[zed-industries/zed](https://github.com/zed-industries/zed) のソースコードを読み解くための、ワークスペース構成とクレート分割に関する規則のまとめ。
---
## 1. ワークスペース構成
### 単一ワークスペース
- リポジトリ直下の `Cargo.toml``[workspace]` を持ち、`crates/` 以下のすべてのクレート200個超を members として束ねる単一ワークスペース。
- `default-members = ["crates/zed"]` が指定されており、ルートで `cargo run` するとエディタ本体が起動する。
- `Cargo.lock` はルートに1つだけ。すべてのクレートで共有される。
- ルートに `rust-toolchain.toml` / `clippy.toml` / `rustfmt.toml` / `.cargo/` を置き、ワークスペース全体に共通設定を効かせる。
### 内部依存は `[workspace.dependencies]` に集約
ルート `Cargo.toml`**すべての内部クレートを path 指定で列挙** する:
```toml
[workspace.dependencies]
acp_thread = { path = "crates/acp_thread" }
action_log = { path = "crates/action_log" }
agent = { path = "crates/agent" }
agent_ui = { path = "crates/agent_ui" }
anthropic = { path = "crates/anthropic" }
# ... 200個以上続く
```
外部クレートserde, tokio, フォーク版 calloop など)も同じ場所に集約され、バージョンや git rev を一元管理する。
### 各クレートは `.workspace = true` で参照
個々の `crates/<name>/Cargo.toml` ではバージョン番号もパスも書かない:
```toml
[dependencies]
gpui.workspace = true
project.workspace = true
serde.workspace = true
```
これにより:
- バージョン更新やフォーク差し替えがルート1ファイルで完結
- 同じ依存が複数バージョンに分裂する事故が起きない
- 新しいクレートの追加は「フォルダ作成 → ルートに1行追加 → 使う側で `name.workspace = true`」だけ
### `publish = false`
ワークスペース全体で `publish = false`。crates.io には公開されないため、`editor` や `project`、`language` のような一般名詞を平気で使える。
---
## 2. クレート命名規則
### 表記ルール
- **`snake_case` で統一**。ハイフンや CamelCase は使わない。
- **全部小文字**、略語も小文字(`lsp`, `rpc`, `acp`, `aws`, `ui`)。
- **ディレクトリ名 = `package.name` = `[workspace.dependencies]` のキー** で完全一致。grep しやすさを優先。
### 命名パターン
#### 1. コア基盤は1語の名詞
`zed` / `gpui` / `editor` / `project` / `workspace` / `language` / `theme` / `ui` / `lsp` / `rpc` / `audio` / `assets` / `askpass`
#### 2. 機能ファミリーは「共通プレフィックス + サフィックス」
```
agent / agent_ui / agent_ui_v2 / agent_settings / agent_servers
auto_update / auto_update_ui / auto_update_helper
assistant_text_thread / assistant_slash_command / assistant_slash_commands
acp_thread / acp_tools
```
サフィックスの慣習:
| サフィックス | 役割 |
|---|---|
| `_ui` | 機能のビュー/ウィジェット層(コアロジックとは分離) |
| `_settings` | 設定スキーマと読み込み |
| `_servers` | 外部プロセス連携アダプタ |
| `_helper` | 補助バイナリ |
| `_v2` | 既存版と並行する新実装の隔離 |
| `_tools` | デバッグ/開発者向けユーティリティ |
#### 3. 外部プロトコル/ベンダー連携はその名前そのまま
`anthropic` / `bedrock` / `aws_http_client` のように、相手のサービスや仕様の名前をそのまま使う。`acp_*` (Agent Client Protocol) のようにプロトコルの略号を頭に付けて系列化するのも同様。
---
## 3. クレート分割の方針
### `zed` クレートは薄いシェル
`crates/zed/` の中身は最小限:
- `main.rs` — エントリポイント、CLI 引数、シングルインスタンス処理、クラッシュハンドラ、パス初期化
- `zed.rs` — グローバル action ハンドラ登録、`initialize_workspace` でのパネル組み立て
- `build.rs` / `RELEASE_CHANNEL`
`app.run(...)` の中身は **200個以上のクレートの `init(cx)` を正しい順序で呼ぶだけ**:
```rust
settings::init(cx);
theme::init(cx);
client::init(&client, cx);
workspace::init(app_state.clone(), cx);
editor::init(cx);
project_panel::init(cx);
agent_ui::init(fs, client, prompt_builder, languages, cx);
git_ui::init(cx);
// ...
```
`zed` は依存グラフの頂点に立つ唯一のクレートで、機能の置き場所ではなく **配線盤 (wiring)** として存在する。**「どこに置くか迷ったら `zed` 以外のどこかに置く」** が鉄則。
### 境界を切る基準
#### ① レイヤー(下から上への単方向依存)
```
gpui ← UI フレームワーク
text / language / fs / rpc / settings / ← ドメインの基本型
theme / ui
project / lsp / git / terminal / ← サービス層
multi_buffer
editor / workspace ← 中核機能
project_panel / git_ui / agent_ui / ← 個別機能 (パネル/ビュー)
diagnostics / search / ...
zed ← 配線だけ
```
下のレイヤーは上のレイヤーを知らない。逆方向の拡張点は trait`workspace::Item`、`workspace::Panel` など)として下層が公開し、上層が実装する。
#### ② ロジックと UI の分離
GPUI 依存を上層に閉じ込めるため、ロジックと UI を別クレートに切る:
| ロジック | UI |
|---|---|
| `agent` | `agent_ui` |
| `auto_update` | `auto_update_ui` |
| `git` | `git_ui` |
| `project` | `project_panel` |
UI 側はロジック側を知るが、ロジック側は UI 側を知らない。
#### ③ `Project` と `Workspace` の二分
Zed の設計で最も象徴的な分割:
- **`project`** — worktree、LSP、ファイルシステム、Git といったサービスを束ねる。ヘッドレスでも動く側。
- **`workspace`** — ペイン分割、ドック、パネル、ステータスバーといったウィンドウ表示を束ねる。GPUI に強く依存する側。
これにより `Project` をリモートマシンで走らせ `Workspace` をローカルで走らせる、というリモート開発が素直に成立する。
#### ④ trait 境界 + Fake 実装
下層の重要な抽象は trait で公開し、テストでは Fake を差し込む:
- `fs::Fs``FakeFs`
- `git::GitRepository` ↔ Fake 実装
- `language::LanguageRegistry`
- `gpui::Element`、`workspace::Item`/`Panel`
trait が置かれているクレートがそのまま境界になる。「どこまでがプラグイン可能か」がクレート一覧から読み取れる。
#### ⑤ 外部世界との接点ごとにクレートを切る
新規追加で既存クレートを編集しなくて済むように、外部依存ごとに独立させる:
- `lsp` / `dap` / `rpc` (プロトコル層)
- `anthropic` / `bedrock` / `open_ai` (LLM プロバイダごと)
- `aws_http_client` (特定の HTTP クライアント実装)
- `acp_thread` / `acp_tools` (Agent Client Protocol)
- `extension_host` + `extension_api` (拡張機能 WASM サンドボックス境界)
#### ⑥ コンパイル時間最適化
頻繁に編集される `editor` のような大きいクレートを切り離すことで、並列ビルドとインクリメンタルコンパイルの効率を上げる。ルート `Cargo.toml` には単一ファイルクレートに `codegen-units = 1` を効かせる調整も含まれる。
---
## 4. 「core」クレートを作らない
Zed には `core` / `common` / `shared` / `zed_core` といったクレートが**意図的に存在しない**。「コア」になりそうなものは機能軸で水平に分割される:
| ありがちな "core" の中身 | Zed での置き場所 |
|---|---|
| 基本データ型 (Rope, Point, Anchor) | `text` |
| ファイルシステム抽象 | `fs` (`Fs` trait + `FakeFs`) |
| 設定の読み書き | `settings` |
| テーマ・色 | `theme` |
| 共通 UI コンポーネント | `ui` |
| 汎用ユーティリティ関数 | `util` |
| RPC/シリアライズ | `rpc` / `proto` |
| ロギング | `zlog` |
| 言語抽象 | `language` |
### `core` を作らない理由
1. **ビルドグラフの直列化を避ける**`core` を作るとほぼ全クレートがそこに依存し、1行触るたびに数百クレートが再コンパイルされる。並列ビルドの利点が消える。
2. **"ゴミ捨て場" 化の防止** — `core``common` は置き場所に迷ったコードが流れ込む磁石になり、責務が肥大化して循環依存の温床になる。Zed は **「迷ったら新しいクレートを作る」** 方向に振る。
3. **依存方向のドキュメント化** — クレートを細かく分けると `Cargo.toml` を見るだけで何に依存し何に依存していないかが一覧できる。`core` があるとこの情報量がゼロになる。
### 例外的な `util`
`crates/util` は雑多な汎用ヘルパ(`ResultExt`, paths, debouncer 等)が入る "ややコアっぽい" クレートだが:
- 名前は `core` ではなく `util` (補助関数の入れ物の慣習)
- GPUI にも `editor` にも依存しない、完全な下層
- trait や中核データ型は置かない (それは `text``fs` の役目)
責務を「補助関数集」に限定することでゴミ捨て場化を防いでいる。
---
## 5. まとめ
Zed のクレート分割の優先順位:
1. **`zed` には何も入れない** — 機能は必ず別クレートに押し出し、`zed` は init 呼び出しと CLI と main だけを持つ
2. **下から上への単方向依存** — 上層が必要な拡張点は trait で下層に公開する
3. **ロジック / UI を分ける**`xxx``xxx_ui` のペア、GPUI 依存を上層に閉じ込める
4. **`Project``Workspace` を分ける** — ヘッドレス/リモート実行可能性のため
5. **外部世界 (プロトコル・ベンダー・拡張) ごとにクレートを切る** — 新規追加を「ファイル追加だけ」で済ませる
6. **Fake が置けるところに trait を置く** — テスト境界 = アーキテクチャ境界
7. **`core` を作らない** — 万能箱の代わりに責務を限定した小さな箱を用意する
クレート数の多さは管理コストではなく、**境界を守るためのコスト払い**である。