Add a `crate-dep-graph` script, remove a few unnecessary dependencies (#3103)

Max Brunsfeld created

This was motivated by me trying to decide which crate I should put a
`NotificationStore` in.

Run `script/crate-dep-graph` to generate an SVG showing the dependency
graph of our `crates` folder, and open it in a web browser.

After running this command, I noticed a couple of dependencies that
didn't make sense and were easy to remove.

Current dependency graph:

![Screen Shot 2023-10-06 at 1 15 42
PM](https://github.com/zed-industries/zed/assets/326587/b5008235-498a-4562-a826-cc923898c052)

Change summary

Cargo.lock                                |  3 -
crates/call/Cargo.toml                    |  1 
crates/call/src/call.rs                   |  3 -
crates/channel/src/channel.rs             | 10 +++--
crates/channel/src/channel_store.rs       | 11 ++++++
crates/channel/src/channel_store_tests.rs |  4 +-
crates/collab/src/tests/test_server.rs    | 39 ++++++++++--------------
crates/collab_ui/src/channel_view.rs      |  2 
crates/collab_ui/src/chat_panel.rs        |  2 
crates/collab_ui/src/collab_panel.rs      |  2 
crates/fs/Cargo.toml                      |  1 
crates/fs/src/repository.rs               | 20 ------------
crates/language/Cargo.toml                |  1 
crates/project/src/worktree.rs            | 22 ++++++++++++-
crates/workspace/Cargo.toml               |  1 
crates/workspace/src/workspace.rs         |  9 -----
crates/zed/src/main.rs                    |  6 ---
crates/zed/src/zed.rs                     |  1 
script/crate-dep-graph                    | 19 ++++++++++++
19 files changed, 82 insertions(+), 75 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1082,7 +1082,6 @@ dependencies = [
  "anyhow",
  "async-broadcast",
  "audio",
- "channel",
  "client",
  "collections",
  "fs",
@@ -2832,7 +2831,6 @@ dependencies = [
  "parking_lot 0.11.2",
  "regex",
  "rope",
- "rpc",
  "serde",
  "serde_derive",
  "serde_json",
@@ -9972,7 +9970,6 @@ dependencies = [
  "async-recursion 1.0.5",
  "bincode",
  "call",
- "channel",
  "client",
  "collections",
  "context_menu",

crates/call/Cargo.toml 🔗

@@ -20,7 +20,6 @@ test-support = [
 
 [dependencies]
 audio = { path = "../audio" }
-channel = { path = "../channel" }
 client = { path = "../client" }
 collections = { path = "../collections" }
 gpui = { path = "../gpui" }

crates/call/src/call.rs 🔗

@@ -5,7 +5,6 @@ pub mod room;
 use anyhow::{anyhow, Result};
 use audio::Audio;
 use call_settings::CallSettings;
-use channel::ChannelId;
 use client::{
     proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore,
     ZED_ALWAYS_ACTIVE,
@@ -79,7 +78,7 @@ impl ActiveCall {
         }
     }
 
-    pub fn channel_id(&self, cx: &AppContext) -> Option<ChannelId> {
+    pub fn channel_id(&self, cx: &AppContext) -> Option<u64> {
         self.room()?.read(cx).channel_id()
     }
 

crates/channel/src/channel.rs 🔗

@@ -2,19 +2,21 @@ mod channel_buffer;
 mod channel_chat;
 mod channel_store;
 
+use client::{Client, UserStore};
+use gpui::{AppContext, ModelHandle};
+use std::sync::Arc;
+
 pub use channel_buffer::{ChannelBuffer, ChannelBufferEvent, ACKNOWLEDGE_DEBOUNCE_INTERVAL};
 pub use channel_chat::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId};
 pub use channel_store::{
     Channel, ChannelData, ChannelEvent, ChannelId, ChannelMembership, ChannelPath, ChannelStore,
 };
 
-use client::Client;
-use std::sync::Arc;
-
 #[cfg(test)]
 mod channel_store_tests;
 
-pub fn init(client: &Arc<Client>) {
+pub fn init(client: &Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
+    channel_store::init(client, user_store, cx);
     channel_buffer::init(client);
     channel_chat::init(client);
 }

crates/channel/src/channel_store.rs 🔗

@@ -2,6 +2,7 @@ mod channel_index;
 
 use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat};
 use anyhow::{anyhow, Result};
+use channel_index::ChannelIndex;
 use client::{Client, Subscription, User, UserId, UserStore};
 use collections::{hash_map, HashMap, HashSet};
 use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
@@ -14,7 +15,11 @@ use serde_derive::{Deserialize, Serialize};
 use std::{borrow::Cow, hash::Hash, mem, ops::Deref, sync::Arc, time::Duration};
 use util::ResultExt;
 
-use self::channel_index::ChannelIndex;
+pub fn init(client: &Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
+    let channel_store =
+        cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
+    cx.set_global(channel_store);
+}
 
 pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
 
@@ -71,6 +76,10 @@ enum OpenedModelHandle<E: Entity> {
 }
 
 impl ChannelStore {
+    pub fn global(cx: &AppContext) -> ModelHandle<Self> {
+        cx.global::<ModelHandle<Self>>().clone()
+    }
+
     pub fn new(
         client: Arc<Client>,
         user_store: ModelHandle<UserStore>,

crates/channel/src/channel_store_tests.rs 🔗

@@ -340,10 +340,10 @@ fn init_test(cx: &mut AppContext) -> ModelHandle<ChannelStore> {
 
     cx.foreground().forbid_parking();
     cx.set_global(SettingsStore::test(cx));
-    crate::init(&client);
     client::init(&client, cx);
+    crate::init(&client, user_store, cx);
 
-    cx.add_model(|cx| ChannelStore::new(client, user_store, cx))
+    ChannelStore::global(cx)
 }
 
 fn update_channels(

crates/collab/src/tests/test_server.rs 🔗

@@ -44,6 +44,7 @@ pub struct TestServer {
 pub struct TestClient {
     pub username: String,
     pub app_state: Arc<workspace::AppState>,
+    channel_store: ModelHandle<ChannelStore>,
     state: RefCell<TestClientState>,
 }
 
@@ -206,15 +207,12 @@ impl TestServer {
         let fs = FakeFs::new(cx.background());
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
         let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
-        let channel_store =
-            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
         let mut language_registry = LanguageRegistry::test();
         language_registry.set_executor(cx.background());
         let app_state = Arc::new(workspace::AppState {
             client: client.clone(),
             user_store: user_store.clone(),
             workspace_store,
-            channel_store: channel_store.clone(),
             languages: Arc::new(language_registry),
             fs: fs.clone(),
             build_window_options: |_, _, _| Default::default(),
@@ -231,7 +229,7 @@ impl TestServer {
             workspace::init(app_state.clone(), cx);
             audio::init((), cx);
             call::init(client.clone(), user_store.clone(), cx);
-            channel::init(&client);
+            channel::init(&client, user_store, cx);
         });
 
         client
@@ -242,6 +240,7 @@ impl TestServer {
         let client = TestClient {
             app_state,
             username: name.to_string(),
+            channel_store: cx.read(ChannelStore::global).clone(),
             state: Default::default(),
         };
         client.wait_for_current_user(cx).await;
@@ -310,10 +309,9 @@ impl TestServer {
         admin: (&TestClient, &mut TestAppContext),
         members: &mut [(&TestClient, &mut TestAppContext)],
     ) -> u64 {
-        let (admin_client, admin_cx) = admin;
-        let channel_id = admin_client
-            .app_state
-            .channel_store
+        let (_, admin_cx) = admin;
+        let channel_id = admin_cx
+            .read(ChannelStore::global)
             .update(admin_cx, |channel_store, cx| {
                 channel_store.create_channel(channel, parent, cx)
             })
@@ -321,9 +319,8 @@ impl TestServer {
             .unwrap();
 
         for (member_client, member_cx) in members {
-            admin_client
-                .app_state
-                .channel_store
+            admin_cx
+                .read(ChannelStore::global)
                 .update(admin_cx, |channel_store, cx| {
                     channel_store.invite_member(
                         channel_id,
@@ -337,9 +334,8 @@ impl TestServer {
 
             admin_cx.foreground().run_until_parked();
 
-            member_client
-                .app_state
-                .channel_store
+            member_cx
+                .read(ChannelStore::global)
                 .update(*member_cx, |channels, _| {
                     channels.respond_to_channel_invite(channel_id, true)
                 })
@@ -447,7 +443,7 @@ impl TestClient {
     }
 
     pub fn channel_store(&self) -> &ModelHandle<ChannelStore> {
-        &self.app_state.channel_store
+        &self.channel_store
     }
 
     pub fn user_store(&self) -> &ModelHandle<UserStore> {
@@ -614,8 +610,8 @@ impl TestClient {
     ) {
         let (other_client, other_cx) = user;
 
-        self.app_state
-            .channel_store
+        cx_self
+            .read(ChannelStore::global)
             .update(cx_self, |channel_store, cx| {
                 channel_store.invite_member(channel, other_client.user_id().unwrap(), true, cx)
             })
@@ -624,11 +620,10 @@ impl TestClient {
 
         cx_self.foreground().run_until_parked();
 
-        other_client
-            .app_state
-            .channel_store
-            .update(other_cx, |channels, _| {
-                channels.respond_to_channel_invite(channel, true)
+        other_cx
+            .read(ChannelStore::global)
+            .update(other_cx, |channel_store, _| {
+                channel_store.respond_to_channel_invite(channel, true)
             })
             .await
             .unwrap();

crates/collab_ui/src/channel_view.rs 🔗

@@ -73,7 +73,7 @@ impl ChannelView {
     ) -> Task<Result<ViewHandle<Self>>> {
         let workspace = workspace.read(cx);
         let project = workspace.project().to_owned();
-        let channel_store = workspace.app_state().channel_store.clone();
+        let channel_store = ChannelStore::global(cx);
         let markdown = workspace
             .app_state()
             .languages

crates/collab_ui/src/chat_panel.rs 🔗

@@ -81,7 +81,7 @@ impl ChatPanel {
     pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
         let fs = workspace.app_state().fs.clone();
         let client = workspace.app_state().client.clone();
-        let channel_store = workspace.app_state().channel_store.clone();
+        let channel_store = ChannelStore::global(cx);
         let languages = workspace.app_state().languages.clone();
 
         let input_editor = cx.add_view(|cx| {

crates/collab_ui/src/collab_panel.rs 🔗

@@ -648,7 +648,7 @@ impl CollabPanel {
                 channel_editing_state: None,
                 selection: None,
                 user_store: workspace.user_store().clone(),
-                channel_store: workspace.app_state().channel_store.clone(),
+                channel_store: ChannelStore::global(cx),
                 project: workspace.project().clone(),
                 subscriptions: Vec::default(),
                 match_candidates: Vec::default(),

crates/fs/Cargo.toml 🔗

@@ -13,7 +13,6 @@ rope = { path = "../rope" }
 text = { path = "../text" }
 util = { path = "../util" }
 sum_tree = { path = "../sum_tree" }
-rpc = { path = "../rpc" }
 
 anyhow.workspace = true
 async-trait.workspace = true

crates/fs/src/repository.rs 🔗

@@ -2,7 +2,6 @@ use anyhow::Result;
 use collections::HashMap;
 use git2::{BranchType, StatusShow};
 use parking_lot::Mutex;
-use rpc::proto;
 use serde_derive::{Deserialize, Serialize};
 use std::{
     cmp::Ordering,
@@ -23,6 +22,7 @@ pub struct Branch {
     /// Timestamp of most recent commit, normalized to Unix Epoch format.
     pub unix_timestamp: Option<i64>,
 }
+
 #[async_trait::async_trait]
 pub trait GitRepository: Send {
     fn reload_index(&self);
@@ -358,24 +358,6 @@ impl GitFileStatus {
             }
         }
     }
-
-    pub fn from_proto(git_status: Option<i32>) -> Option<GitFileStatus> {
-        git_status.and_then(|status| {
-            proto::GitStatus::from_i32(status).map(|status| match status {
-                proto::GitStatus::Added => GitFileStatus::Added,
-                proto::GitStatus::Modified => GitFileStatus::Modified,
-                proto::GitStatus::Conflict => GitFileStatus::Conflict,
-            })
-        })
-    }
-
-    pub fn to_proto(self) -> i32 {
-        match self {
-            GitFileStatus::Added => proto::GitStatus::Added as i32,
-            GitFileStatus::Modified => proto::GitStatus::Modified as i32,
-            GitFileStatus::Conflict => proto::GitStatus::Conflict as i32,
-        }
-    }
 }
 
 #[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)]

crates/language/Cargo.toml 🔗

@@ -22,7 +22,6 @@ test-support = [
 ]
 
 [dependencies]
-client = { path = "../client" }
 clock = { path = "../clock" }
 collections = { path = "../collections" }
 fuzzy = { path = "../fuzzy" }

crates/project/src/worktree.rs 🔗

@@ -4310,7 +4310,7 @@ impl<'a> From<&'a Entry> for proto::Entry {
             is_symlink: entry.is_symlink,
             is_ignored: entry.is_ignored,
             is_external: entry.is_external,
-            git_status: entry.git_status.map(|status| status.to_proto()),
+            git_status: entry.git_status.map(git_status_to_proto),
         }
     }
 }
@@ -4337,7 +4337,7 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
                 is_symlink: entry.is_symlink,
                 is_ignored: entry.is_ignored,
                 is_external: entry.is_external,
-                git_status: GitFileStatus::from_proto(entry.git_status),
+                git_status: git_status_from_proto(entry.git_status),
             })
         } else {
             Err(anyhow!(
@@ -4366,3 +4366,21 @@ fn combine_git_statuses(
         unstaged
     }
 }
+
+fn git_status_from_proto(git_status: Option<i32>) -> Option<GitFileStatus> {
+    git_status.and_then(|status| {
+        proto::GitStatus::from_i32(status).map(|status| match status {
+            proto::GitStatus::Added => GitFileStatus::Added,
+            proto::GitStatus::Modified => GitFileStatus::Modified,
+            proto::GitStatus::Conflict => GitFileStatus::Conflict,
+        })
+    })
+}
+
+fn git_status_to_proto(status: GitFileStatus) -> i32 {
+    match status {
+        GitFileStatus::Added => proto::GitStatus::Added as i32,
+        GitFileStatus::Modified => proto::GitStatus::Modified as i32,
+        GitFileStatus::Conflict => proto::GitStatus::Conflict as i32,
+    }
+}

crates/workspace/Cargo.toml 🔗

@@ -22,7 +22,6 @@ test-support = [
 db = { path = "../db" }
 call = { path = "../call" }
 client = { path = "../client" }
-channel = { path = "../channel" }
 collections = { path = "../collections" }
 context_menu = { path = "../context_menu" }
 drag_and_drop = { path = "../drag_and_drop" }

crates/workspace/src/workspace.rs 🔗

@@ -12,7 +12,6 @@ mod workspace_settings;
 
 use anyhow::{anyhow, Context, Result};
 use call::ActiveCall;
-use channel::ChannelStore;
 use client::{
     proto::{self, PeerId},
     Client, TypedEnvelope, UserStore,
@@ -450,7 +449,6 @@ pub struct AppState {
     pub languages: Arc<LanguageRegistry>,
     pub client: Arc<Client>,
     pub user_store: ModelHandle<UserStore>,
-    pub channel_store: ModelHandle<ChannelStore>,
     pub workspace_store: ModelHandle<WorkspaceStore>,
     pub fs: Arc<dyn fs::Fs>,
     pub build_window_options:
@@ -487,8 +485,6 @@ impl AppState {
         let http_client = util::http::FakeHttpClient::with_404_response();
         let client = Client::new(http_client.clone(), cx);
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
-        let channel_store =
-            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
         let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
 
         theme::init((), cx);
@@ -500,7 +496,7 @@ impl AppState {
             fs,
             languages,
             user_store,
-            channel_store,
+            // channel_store,
             workspace_store,
             initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
             build_window_options: |_, _, _| Default::default(),
@@ -3527,15 +3523,12 @@ impl Workspace {
         let client = project.read(cx).client();
         let user_store = project.read(cx).user_store();
 
-        let channel_store =
-            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
         let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
         let app_state = Arc::new(AppState {
             languages: project.read(cx).languages().clone(),
             workspace_store,
             client,
             user_store,
-            channel_store,
             fs: project.read(cx).fs().clone(),
             build_window_options: |_, _, _| Default::default(),
             initialize_workspace: |_, _, _, _| Task::ready(Ok(())),

crates/zed/src/main.rs 🔗

@@ -3,7 +3,6 @@
 
 use anyhow::{anyhow, Context, Result};
 use backtrace::Backtrace;
-use channel::ChannelStore;
 use cli::{
     ipc::{self, IpcSender},
     CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME,
@@ -138,8 +137,6 @@ fn main() {
 
         languages::init(languages.clone(), node_runtime.clone(), cx);
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
-        let channel_store =
-            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
         let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
 
         cx.set_global(client.clone());
@@ -156,7 +153,7 @@ fn main() {
         outline::init(cx);
         project_symbols::init(cx);
         project_panel::init(Assets, cx);
-        channel::init(&client);
+        channel::init(&client, user_store.clone(), cx);
         diagnostics::init(cx);
         search::init(cx);
         semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);
@@ -184,7 +181,6 @@ fn main() {
             languages,
             client: client.clone(),
             user_store,
-            channel_store,
             fs,
             build_window_options,
             initialize_workspace,

crates/zed/src/zed.rs 🔗

@@ -2424,6 +2424,7 @@ mod tests {
             state.build_window_options = build_window_options;
             theme::init((), cx);
             audio::init((), cx);
+            channel::init(&app_state.client, app_state.user_store.clone(), cx);
             call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
             workspace::init(app_state.clone(), cx);
             Project::init_settings(cx);

script/crate-dep-graph 🔗

@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -e
+
+if [[ -x cargo-depgraph ]]; then
+    cargo install cargo-depgraph
+fi
+
+graph_file=target/crate-graph.html
+
+cargo depgraph \
+    --workspace-only \
+    --offline \
+    --root=zed,cli,collab \
+    --dedup-transitive-deps \
+    | dot -Tsvg > $graph_file
+
+echo "open $graph_file"
+open $graph_file