# 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//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` を作らない** — 万能箱の代わりに責務を限定した小さな箱を用意する クレート数の多さは管理コストではなく、**境界を守るためのコスト払い**である。