diff --git a/Cargo.lock b/Cargo.lock index 4cfb831a58..bd93ae3e27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1629,6 +1629,7 @@ dependencies = [ "postage", "project", "rand 0.8.3", + "rpc", "serde", "smallvec", "smol", diff --git a/crates/client/src/channel.rs b/crates/client/src/channel.rs index 18a0e156db..ac235dc19e 100644 --- a/crates/client/src/channel.rs +++ b/crates/client/src/channel.rs @@ -181,7 +181,7 @@ impl Entity for Channel { impl Channel { pub fn init(rpc: &Arc) { - rpc.add_entity_message_handler(Self::handle_message_sent); + rpc.add_model_message_handler(Self::handle_message_sent); } pub fn new( diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 59110f73c6..b56017c789 100644 --- a/crates/client/src/client.rs +++ b/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 u64>>, _reconnect_task: Option>, 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, - model_types_by_message_type: HashMap, + entity_types_by_message_type: HashMap, message_handlers: HashMap< TypeId, Arc< dyn Send + Sync + Fn( - AnyModelHandle, + AnyEntityHandle, Box, 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( + self: &Arc, + remote_id: u64, + cx: &mut ViewContext, + ) -> Subscription { + let handle = AnyViewHandle::from(cx.handle()); + let mut state = self.state.write(); + let id = (TypeId::of::(), 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( self: &Arc, remote_id: u64, @@ -322,8 +349,8 @@ impl Client { let mut state = self.state.write(); let id = (TypeId::of::(), 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::().unwrap(); let envelope = envelope.into_any().downcast::>().unwrap(); if let Some(client) = client.upgrade() { @@ -374,7 +406,60 @@ impl Client { } } - pub fn add_entity_message_handler(self: &Arc, handler: H) + pub fn add_view_message_handler(self: &Arc, handler: H) + where + M: EntityMessage, + E: View, + H: 'static + + Send + + Sync + + Fn(ViewHandle, TypedEnvelope, Arc, AsyncAppContext) -> F, + F: 'static + Future>, + { + let entity_type_id = TypeId::of::(); + let message_type_id = TypeId::of::(); + + 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::>() + .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::().unwrap(); + let envelope = envelope.into_any().downcast::>().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(self: &Arc, 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::().unwrap(); - let envelope = envelope.into_any().downcast::>().unwrap(); if let Some(client) = client.upgrade() { + let model = handle.downcast::().unwrap(); + let envelope = envelope.into_any().downcast::>().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, TypedEnvelope, Arc, AsyncAppContext) -> F, F: 'static + Future>, { - 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 { + 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 { 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, _: TypedEnvelope, _, cx| { match model.read_with(&cx, |model, _| model.id) { 1 => done_tx1.try_send(()).unwrap(), diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 609e92af0f..02069fb610 100644 --- a/crates/editor/Cargo.toml +++ b/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" } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index d865511a62..522c490cfa 100644 --- a/crates/editor/src/items.rs +++ b/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, + project: ModelHandle, + state: &mut Option, + cx: &mut gpui::MutableAppContext, + ) -> Option>>> { + 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, cx: &mut ViewContext) { if let Some(data) = data.downcast_ref::() { let buffer = self.buffer.read(cx).read(cx); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c44364adac..4e2abbc2fe 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -124,6 +124,7 @@ pub enum Event { DiskBasedDiagnosticsUpdated, DiskBasedDiagnosticsFinished, DiagnosticsUpdated(ProjectPath), + RemoteIdChanged(Option), } enum LanguageServerEvent { @@ -253,19 +254,19 @@ impl ProjectEntryId { impl Project { pub fn init(client: &Arc) { - 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 { diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index ce3cfff646..23f7e1c182 100644 --- a/crates/server/src/rpc.rs +++ b/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)); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b80372a981..25159fa689 100644 --- a/crates/workspace/src/workspace.rs +++ b/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, 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(cx: &mut MutableAppContext) { @@ -119,7 +124,7 @@ pub fn register_project_item(cx: &mut MutableAppContext) { }); } -pub fn register_followed_item(cx: &mut MutableAppContext) { +pub fn register_followed_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) {} fn navigate(&mut self, _: Box, _: &mut ViewContext) {} 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, project: ModelHandle, state: &mut Option, cx: &mut MutableAppContext, - ) -> Option>>>; + ) -> Option>>> + 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, client: Arc, user_store: ModelHandle, + remote_entity_subscription: Option, fs: Arc, modal: Option, center: PaneGroup, @@ -481,6 +492,17 @@ impl Workspace { }) .detach(); + cx.subscribe(¶ms.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 { @@ -1008,6 +1033,15 @@ impl Workspace { }); } + fn project_remote_id_changed(&mut self, remote_id: Option, cx: &mut ViewContext) { + 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) -> Task> { 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, + envelope: TypedEnvelope, + _: Arc, + cx: AsyncAppContext, + ) -> Result { + Ok(proto::FollowResponse { + current_view_id: 0, + views: Default::default(), + }) + } + + async fn handle_unfollow( + this: ViewHandle, + envelope: TypedEnvelope, + _: Arc, + cx: AsyncAppContext, + ) -> Result<()> { + Ok(()) + } } impl Entity for Workspace { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index cf149b2469..0543783494 100644 --- a/crates/zed/src/main.rs +++ b/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); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2b61279a2c..c33a96a94b 100644 --- a/crates/zed/src/zed.rs +++ b/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();