WIP

Max Brunsfeld created

Change summary

Cargo.lock                        |   1 
crates/client/src/channel.rs      |   2 
crates/client/src/client.rs       | 156 +++++++++++++++++++++++++++-----
crates/editor/Cargo.toml          |   1 
crates/editor/src/items.rs        |  27 +++++
crates/project/src/project.rs     |  28 +++--
crates/server/src/rpc.rs          |   3 
crates/workspace/src/workspace.rs |  81 ++++++++++++++--
crates/zed/src/main.rs            |   2 
crates/zed/src/zed.rs             |   2 
10 files changed, 245 insertions(+), 58 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1629,6 +1629,7 @@ dependencies = [
  "postage",
  "project",
  "rand 0.8.3",
+ "rpc",
  "serde",
  "smallvec",
  "smol",

crates/client/src/channel.rs 🔗

@@ -181,7 +181,7 @@ impl Entity for Channel {
 
 impl Channel {
     pub fn init(rpc: &Arc<Client>) {
-        rpc.add_entity_message_handler(Self::handle_message_sent);
+        rpc.add_model_message_handler(Self::handle_message_sent);
     }
 
     pub fn new(

crates/client/src/client.rs 🔗

@@ -13,8 +13,8 @@ use async_tungstenite::tungstenite::{
 };
 use futures::{future::LocalBoxFuture, FutureExt, StreamExt};
 use gpui::{
-    action, AnyModelHandle, AnyWeakModelHandle, AsyncAppContext, Entity, ModelContext, ModelHandle,
-    MutableAppContext, Task,
+    action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AsyncAppContext,
+    Entity, ModelContext, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle,
 };
 use http::HttpClient;
 use lazy_static::lazy_static;
@@ -139,16 +139,16 @@ struct ClientState {
     entity_id_extractors: HashMap<TypeId, Box<dyn Send + Sync + Fn(&dyn AnyTypedEnvelope) -> u64>>,
     _reconnect_task: Option<Task<()>>,
     reconnect_interval: Duration,
-    models_by_entity_type_and_remote_id: HashMap<(TypeId, u64), AnyWeakModelHandle>,
+    entities_by_type_and_remote_id: HashMap<(TypeId, u64), AnyWeakEntityHandle>,
     models_by_message_type: HashMap<TypeId, AnyWeakModelHandle>,
-    model_types_by_message_type: HashMap<TypeId, TypeId>,
+    entity_types_by_message_type: HashMap<TypeId, TypeId>,
     message_handlers: HashMap<
         TypeId,
         Arc<
             dyn Send
                 + Sync
                 + Fn(
-                    AnyModelHandle,
+                    AnyEntityHandle,
                     Box<dyn AnyTypedEnvelope>,
                     AsyncAppContext,
                 ) -> LocalBoxFuture<'static, Result<()>>,
@@ -156,6 +156,16 @@ struct ClientState {
     >,
 }
 
+enum AnyWeakEntityHandle {
+    Model(AnyWeakModelHandle),
+    View(AnyWeakViewHandle),
+}
+
+enum AnyEntityHandle {
+    Model(AnyModelHandle),
+    View(AnyViewHandle),
+}
+
 #[derive(Clone, Debug)]
 pub struct Credentials {
     pub user_id: u64,
@@ -171,8 +181,8 @@ impl Default for ClientState {
             _reconnect_task: None,
             reconnect_interval: Duration::from_secs(5),
             models_by_message_type: Default::default(),
-            models_by_entity_type_and_remote_id: Default::default(),
-            model_types_by_message_type: Default::default(),
+            entities_by_type_and_remote_id: Default::default(),
+            entity_types_by_message_type: Default::default(),
             message_handlers: Default::default(),
         }
     }
@@ -195,13 +205,13 @@ impl Drop for Subscription {
             Subscription::Entity { client, id } => {
                 if let Some(client) = client.upgrade() {
                     let mut state = client.state.write();
-                    let _ = state.models_by_entity_type_and_remote_id.remove(id);
+                    let _ = state.entities_by_type_and_remote_id.remove(id);
                 }
             }
             Subscription::Message { client, id } => {
                 if let Some(client) = client.upgrade() {
                     let mut state = client.state.write();
-                    let _ = state.model_types_by_message_type.remove(id);
+                    let _ = state.entity_types_by_message_type.remove(id);
                     let _ = state.message_handlers.remove(id);
                 }
             }
@@ -239,7 +249,7 @@ impl Client {
         state._reconnect_task.take();
         state.message_handlers.clear();
         state.models_by_message_type.clear();
-        state.models_by_entity_type_and_remote_id.clear();
+        state.entities_by_type_and_remote_id.clear();
         state.entity_id_extractors.clear();
         self.peer.reset();
     }
@@ -313,6 +323,23 @@ impl Client {
         }
     }
 
+    pub fn add_view_for_remote_entity<T: View>(
+        self: &Arc<Self>,
+        remote_id: u64,
+        cx: &mut ViewContext<T>,
+    ) -> Subscription {
+        let handle = AnyViewHandle::from(cx.handle());
+        let mut state = self.state.write();
+        let id = (TypeId::of::<T>(), remote_id);
+        state
+            .entities_by_type_and_remote_id
+            .insert(id, AnyWeakEntityHandle::View(handle.downgrade()));
+        Subscription::Entity {
+            client: Arc::downgrade(self),
+            id,
+        }
+    }
+
     pub fn add_model_for_remote_entity<T: Entity>(
         self: &Arc<Self>,
         remote_id: u64,
@@ -322,8 +349,8 @@ impl Client {
         let mut state = self.state.write();
         let id = (TypeId::of::<T>(), remote_id);
         state
-            .models_by_entity_type_and_remote_id
-            .insert(id, handle.downgrade());
+            .entities_by_type_and_remote_id
+            .insert(id, AnyWeakEntityHandle::Model(handle.downgrade()));
         Subscription::Entity {
             client: Arc::downgrade(self),
             id,
@@ -355,6 +382,11 @@ impl Client {
         let prev_handler = state.message_handlers.insert(
             message_type_id,
             Arc::new(move |handle, envelope, cx| {
+                let handle = if let AnyEntityHandle::Model(handle) = handle {
+                    handle
+                } else {
+                    unreachable!();
+                };
                 let model = handle.downcast::<E>().unwrap();
                 let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
                 if let Some(client) = client.upgrade() {
@@ -374,7 +406,60 @@ impl Client {
         }
     }
 
-    pub fn add_entity_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
+    pub fn add_view_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
+    where
+        M: EntityMessage,
+        E: View,
+        H: 'static
+            + Send
+            + Sync
+            + Fn(ViewHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        F: 'static + Future<Output = Result<()>>,
+    {
+        let entity_type_id = TypeId::of::<E>();
+        let message_type_id = TypeId::of::<M>();
+
+        let client = Arc::downgrade(self);
+        let mut state = self.state.write();
+        state
+            .entity_types_by_message_type
+            .insert(message_type_id, entity_type_id);
+        state
+            .entity_id_extractors
+            .entry(message_type_id)
+            .or_insert_with(|| {
+                Box::new(|envelope| {
+                    let envelope = envelope
+                        .as_any()
+                        .downcast_ref::<TypedEnvelope<M>>()
+                        .unwrap();
+                    envelope.payload.remote_entity_id()
+                })
+            });
+
+        let prev_handler = state.message_handlers.insert(
+            message_type_id,
+            Arc::new(move |handle, envelope, cx| {
+                let handle = if let AnyEntityHandle::View(handle) = handle {
+                    handle
+                } else {
+                    unreachable!();
+                };
+                let model = handle.downcast::<E>().unwrap();
+                let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
+                if let Some(client) = client.upgrade() {
+                    handler(model, *envelope, client.clone(), cx).boxed_local()
+                } else {
+                    async move { Ok(()) }.boxed_local()
+                }
+            }),
+        );
+        if prev_handler.is_some() {
+            panic!("registered handler for the same message twice");
+        }
+    }
+
+    pub fn add_model_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
     where
         M: EntityMessage,
         E: Entity,
@@ -390,7 +475,7 @@ impl Client {
         let client = Arc::downgrade(self);
         let mut state = self.state.write();
         state
-            .model_types_by_message_type
+            .entity_types_by_message_type
             .insert(message_type_id, model_type_id);
         state
             .entity_id_extractors
@@ -408,9 +493,15 @@ impl Client {
         let prev_handler = state.message_handlers.insert(
             message_type_id,
             Arc::new(move |handle, envelope, cx| {
-                let model = handle.downcast::<E>().unwrap();
-                let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
                 if let Some(client) = client.upgrade() {
+                    let model = handle.downcast::<E>().unwrap();
+                    let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
+                    let handle = if let AnyEntityHandle::Model(handle) = handle {
+                        handle
+                    } else {
+                        unreachable!();
+                    };
+
                     handler(model, *envelope, client.clone(), cx).boxed_local()
                 } else {
                     async move { Ok(()) }.boxed_local()
@@ -432,7 +523,7 @@ impl Client {
             + Fn(ModelHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
         F: 'static + Future<Output = Result<M::Response>>,
     {
-        self.add_entity_message_handler(move |model, envelope, client, cx| {
+        self.add_model_message_handler(move |model, envelope, client, cx| {
             let receipt = envelope.receipt();
             let response = handler(model, envelope, client.clone(), cx);
             async move {
@@ -561,24 +652,26 @@ impl Client {
                             .models_by_message_type
                             .get(&payload_type_id)
                             .and_then(|model| model.upgrade(&cx))
+                            .map(AnyEntityHandle::Model)
                             .or_else(|| {
-                                let model_type_id =
-                                    *state.model_types_by_message_type.get(&payload_type_id)?;
+                                let entity_type_id =
+                                    *state.entity_types_by_message_type.get(&payload_type_id)?;
                                 let entity_id = state
                                     .entity_id_extractors
                                     .get(&message.payload_type_id())
                                     .map(|extract_entity_id| {
                                         (extract_entity_id)(message.as_ref())
                                     })?;
-                                let model = state
-                                    .models_by_entity_type_and_remote_id
-                                    .get(&(model_type_id, entity_id))?;
-                                if let Some(model) = model.upgrade(&cx) {
-                                    Some(model)
+
+                                let entity = state
+                                    .entities_by_type_and_remote_id
+                                    .get(&(entity_type_id, entity_id))?;
+                                if let Some(entity) = entity.upgrade(&cx) {
+                                    Some(entity)
                                 } else {
                                     state
-                                        .models_by_entity_type_and_remote_id
-                                        .remove(&(model_type_id, entity_id));
+                                        .entities_by_type_and_remote_id
+                                        .remove(&(entity_type_id, entity_id));
                                     None
                                 }
                             });
@@ -891,6 +984,15 @@ impl Client {
     }
 }
 
+impl AnyWeakEntityHandle {
+    fn upgrade(&self, cx: &AsyncAppContext) -> Option<AnyEntityHandle> {
+        match self {
+            AnyWeakEntityHandle::Model(handle) => handle.upgrade(cx).map(AnyEntityHandle::Model),
+            AnyWeakEntityHandle::View(handle) => handle.upgrade(cx).map(AnyEntityHandle::View),
+        }
+    }
+}
+
 fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
     if IMPERSONATE_LOGIN.is_some() {
         return None;
@@ -994,7 +1096,7 @@ mod tests {
 
         let (done_tx1, mut done_rx1) = smol::channel::unbounded();
         let (done_tx2, mut done_rx2) = smol::channel::unbounded();
-        client.add_entity_message_handler(
+        client.add_model_message_handler(
             move |model: ModelHandle<Model>, _: TypedEnvelope<proto::UnshareProject>, _, cx| {
                 match model.read_with(&cx, |model, _| model.id) {
                     1 => done_tx1.try_send(()).unwrap(),

crates/editor/Cargo.toml 🔗

@@ -27,6 +27,7 @@ gpui = { path = "../gpui" }
 language = { path = "../language" }
 lsp = { path = "../lsp" }
 project = { path = "../project" }
+rpc = { path = "../rpc" }
 snippet = { path = "../snippet" }
 sum_tree = { path = "../sum_tree" }
 theme = { path = "../theme" }

crates/editor/src/items.rs 🔗

@@ -6,13 +6,34 @@ use gpui::{
 };
 use language::{Bias, Buffer, Diagnostic, File as _};
 use project::{File, Project, ProjectEntryId, ProjectPath};
-use std::fmt::Write;
-use std::path::PathBuf;
+use rpc::proto;
+use std::{fmt::Write, path::PathBuf};
 use text::{Point, Selection};
 use util::ResultExt;
-use workspace::{Item, ItemHandle, ItemNavHistory, ProjectItem, Settings, StatusItemView};
+use workspace::{
+    FollowedItem, Item, ItemHandle, ItemNavHistory, ProjectItem, Settings, StatusItemView,
+};
+
+impl FollowedItem for Editor {
+    fn for_state_message(
+        pane: ViewHandle<workspace::Pane>,
+        project: ModelHandle<Project>,
+        state: &mut Option<proto::view::Variant>,
+        cx: &mut gpui::MutableAppContext,
+    ) -> Option<Task<Result<Box<dyn ItemHandle>>>> {
+        todo!()
+    }
+
+    fn to_state_message(&self, cx: &mut gpui::MutableAppContext) -> proto::view::Variant {
+        todo!()
+    }
+}
 
 impl Item for Editor {
+    fn as_followed(&self) -> Option<&dyn FollowedItem> {
+        Some(self)
+    }
+
     fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) {
         if let Some(data) = data.downcast_ref::<NavigationData>() {
             let buffer = self.buffer.read(cx).read(cx);

crates/project/src/project.rs 🔗

@@ -124,6 +124,7 @@ pub enum Event {
     DiskBasedDiagnosticsUpdated,
     DiskBasedDiagnosticsFinished,
     DiagnosticsUpdated(ProjectPath),
+    RemoteIdChanged(Option<u64>),
 }
 
 enum LanguageServerEvent {
@@ -253,19 +254,19 @@ impl ProjectEntryId {
 
 impl Project {
     pub fn init(client: &Arc<Client>) {
-        client.add_entity_message_handler(Self::handle_add_collaborator);
-        client.add_entity_message_handler(Self::handle_buffer_reloaded);
-        client.add_entity_message_handler(Self::handle_buffer_saved);
-        client.add_entity_message_handler(Self::handle_start_language_server);
-        client.add_entity_message_handler(Self::handle_update_language_server);
-        client.add_entity_message_handler(Self::handle_remove_collaborator);
-        client.add_entity_message_handler(Self::handle_register_worktree);
-        client.add_entity_message_handler(Self::handle_unregister_worktree);
-        client.add_entity_message_handler(Self::handle_unshare_project);
-        client.add_entity_message_handler(Self::handle_update_buffer_file);
-        client.add_entity_message_handler(Self::handle_update_buffer);
-        client.add_entity_message_handler(Self::handle_update_diagnostic_summary);
-        client.add_entity_message_handler(Self::handle_update_worktree);
+        client.add_model_message_handler(Self::handle_add_collaborator);
+        client.add_model_message_handler(Self::handle_buffer_reloaded);
+        client.add_model_message_handler(Self::handle_buffer_saved);
+        client.add_model_message_handler(Self::handle_start_language_server);
+        client.add_model_message_handler(Self::handle_update_language_server);
+        client.add_model_message_handler(Self::handle_remove_collaborator);
+        client.add_model_message_handler(Self::handle_register_worktree);
+        client.add_model_message_handler(Self::handle_unregister_worktree);
+        client.add_model_message_handler(Self::handle_unshare_project);
+        client.add_model_message_handler(Self::handle_update_buffer_file);
+        client.add_model_message_handler(Self::handle_update_buffer);
+        client.add_model_message_handler(Self::handle_update_diagnostic_summary);
+        client.add_model_message_handler(Self::handle_update_worktree);
         client.add_entity_request_handler(Self::handle_apply_additional_edits_for_completion);
         client.add_entity_request_handler(Self::handle_apply_code_action);
         client.add_entity_request_handler(Self::handle_format_buffers);
@@ -566,6 +567,7 @@ impl Project {
             self.subscriptions
                 .push(self.client.add_model_for_remote_entity(remote_id, cx));
         }
+        cx.emit(Event::RemoteIdChanged(remote_id))
     }
 
     pub fn remote_id(&self) -> Option<u64> {

crates/server/src/rpc.rs 🔗

@@ -4563,6 +4563,9 @@ mod tests {
 
             Channel::init(&client);
             Project::init(&client);
+            cx.update(|cx| {
+                workspace::init(&client, cx);
+            });
 
             let peer_id = PeerId(connection_id_rx.next().await.unwrap().0);
             let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));

crates/workspace/src/workspace.rs 🔗

@@ -7,7 +7,9 @@ pub mod sidebar;
 mod status_bar;
 
 use anyhow::{anyhow, Result};
-use client::{proto, Authenticate, ChannelList, Client, PeerId, User, UserStore};
+use client::{
+    proto, Authenticate, ChannelList, Client, PeerId, Subscription, TypedEnvelope, User, UserStore,
+};
 use clock::ReplicaId;
 use collections::HashMap;
 use gpui::{
@@ -18,9 +20,9 @@ use gpui::{
     json::{self, to_string_pretty, ToJson},
     keymap::Binding,
     platform::{CursorStyle, WindowOptions},
-    AnyModelHandle, AnyViewHandle, AppContext, ClipboardItem, Entity, ImageData, ModelHandle,
-    MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext,
-    ViewHandle, WeakViewHandle,
+    AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Entity, ImageData,
+    ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View,
+    ViewContext, ViewHandle, WeakViewHandle,
 };
 use language::LanguageRegistry;
 use log::error;
@@ -64,7 +66,7 @@ action!(JoinProject, JoinProjectParams);
 action!(Save);
 action!(DebugElements);
 
-pub fn init(cx: &mut MutableAppContext) {
+pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
     pane::init(cx);
     menu::init(cx);
 
@@ -108,6 +110,9 @@ pub fn init(cx: &mut MutableAppContext) {
             None,
         ),
     ]);
+
+    client.add_entity_request_handler(Workspace::handle_follow);
+    client.add_model_message_handler(Workspace::handle_unfollow);
 }
 
 pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
@@ -119,7 +124,7 @@ pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
     });
 }
 
-pub fn register_followed_item<I: FollowedItem>(cx: &mut MutableAppContext) {
+pub fn register_followed_item<I: FollowedItem + Item>(cx: &mut MutableAppContext) {
     cx.update_default_global(|builders: &mut FollowedItemBuilders, _| {
         builders.push(I::for_state_message)
     });
@@ -153,6 +158,9 @@ pub struct JoinProjectParams {
 }
 
 pub trait Item: View {
+    fn as_followed(&self) -> Option<&dyn FollowedItem> {
+        None
+    }
     fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
     fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) {}
     fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
@@ -217,15 +225,17 @@ pub trait ProjectItem: Item {
     ) -> Self;
 }
 
-pub trait FollowedItem: Item {
-    type UpdateMessage;
-
+pub trait FollowedItem {
     fn for_state_message(
         pane: ViewHandle<Pane>,
         project: ModelHandle<Project>,
         state: &mut Option<proto::view::Variant>,
         cx: &mut MutableAppContext,
-    ) -> Option<Task<Result<Box<dyn ItemHandle>>>>;
+    ) -> Option<Task<Result<Box<dyn ItemHandle>>>>
+    where
+        Self: Sized;
+
+    fn to_state_message(&self, cx: &mut MutableAppContext) -> proto::view::Variant;
 }
 
 pub trait ItemHandle: 'static {
@@ -459,6 +469,7 @@ pub struct Workspace {
     weak_self: WeakViewHandle<Self>,
     client: Arc<Client>,
     user_store: ModelHandle<client::UserStore>,
+    remote_entity_subscription: Option<Subscription>,
     fs: Arc<dyn Fs>,
     modal: Option<AnyViewHandle>,
     center: PaneGroup,
@@ -481,6 +492,17 @@ impl Workspace {
         })
         .detach();
 
+        cx.subscribe(&params.project, move |this, project, event, cx| {
+            if let project::Event::RemoteIdChanged(remote_id) = event {
+                this.project_remote_id_changed(*remote_id, cx);
+            }
+            if project.read(cx).is_read_only() {
+                cx.blur();
+            }
+            cx.notify()
+        })
+        .detach();
+
         let pane = cx.add_view(|_| Pane::new());
         let pane_id = pane.id();
         cx.observe(&pane, move |me, _, cx| {
@@ -517,7 +539,7 @@ impl Workspace {
 
         cx.emit_global(WorkspaceCreated(weak_self.clone()));
 
-        Workspace {
+        let mut this = Workspace {
             modal: None,
             weak_self,
             center: PaneGroup::new(pane.clone()),
@@ -525,13 +547,16 @@ impl Workspace {
             active_pane: pane.clone(),
             status_bar,
             client: params.client.clone(),
+            remote_entity_subscription: None,
             user_store: params.user_store.clone(),
             fs: params.fs.clone(),
             left_sidebar: Sidebar::new(Side::Left),
             right_sidebar: Sidebar::new(Side::Right),
             project: params.project.clone(),
             _observe_current_user,
-        }
+        };
+        this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
+        this
     }
 
     pub fn weak_handle(&self) -> WeakViewHandle<Self> {
@@ -1008,6 +1033,15 @@ impl Workspace {
         });
     }
 
+    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
+        if let Some(remote_id) = remote_id {
+            self.remote_entity_subscription =
+                Some(self.client.add_view_for_remote_entity(remote_id, cx));
+        } else {
+            self.remote_entity_subscription.take();
+        }
+    }
+
     pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
         if let Some(project_id) = self.project.read(cx).remote_id() {
             let request = self.client.request(proto::Follow {
@@ -1271,6 +1305,29 @@ impl Workspace {
             None
         }
     }
+
+    // RPC handlers
+
+    async fn handle_follow(
+        this: ViewHandle<Self>,
+        envelope: TypedEnvelope<proto::Follow>,
+        _: Arc<Client>,
+        cx: AsyncAppContext,
+    ) -> Result<proto::FollowResponse> {
+        Ok(proto::FollowResponse {
+            current_view_id: 0,
+            views: Default::default(),
+        })
+    }
+
+    async fn handle_unfollow(
+        this: ViewHandle<Self>,
+        envelope: TypedEnvelope<proto::Unfollow>,
+        _: Arc<Client>,
+        cx: AsyncAppContext,
+    ) -> Result<()> {
+        Ok(())
+    }
 }
 
 impl Entity for Workspace {

crates/zed/src/main.rs 🔗

@@ -69,7 +69,7 @@ fn main() {
         project::Project::init(&client);
         client::Channel::init(&client);
         client::init(client.clone(), cx);
-        workspace::init(cx);
+        workspace::init(&client, cx);
         editor::init(cx);
         go_to_line::init(cx);
         file_finder::init(cx);

crates/zed/src/zed.rs 🔗

@@ -252,7 +252,7 @@ mod tests {
     async fn test_new_empty_workspace(cx: &mut TestAppContext) {
         let app_state = cx.update(test_app_state);
         cx.update(|cx| {
-            workspace::init(cx);
+            workspace::init(&app_state.client, cx);
         });
         cx.dispatch_global_action(workspace::OpenNew(app_state.clone()));
         let window_id = *cx.window_ids().first().unwrap();