From 10e6d82c3ec95cf9a86979ee47794726057df9c9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 18 Mar 2022 14:20:09 +0100 Subject: [PATCH] WIP: Start on sending view updates to followers --- crates/editor/src/items.rs | 29 ++++++- crates/gpui/src/app.rs | 8 ++ crates/server/src/rpc.rs | 11 +++ crates/workspace/src/pane.rs | 2 +- crates/workspace/src/workspace.rs | 139 +++++++++++++++++++++--------- 5 files changed, 145 insertions(+), 44 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 2c454dedb5ccc70cea74d0f80e4d08f3c7a2dd97..98610c4a07fa035d40d8813a8c67606b364c2371 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -72,13 +72,34 @@ impl FollowedItem for Editor { newest_selection: Some(language::proto::serialize_selection(&selection)), }) } -} -impl Item for Editor { - fn as_followed(&self) -> Option<&dyn FollowedItem> { - Some(self) + fn to_update_message( + &self, + event: &Self::Event, + cx: &AppContext, + ) -> Option { + match event { + Event::SelectionsChanged => { + let selection = self.newest_anchor_selection(); + let selection = Selection { + id: selection.id, + start: selection.start.text_anchor.clone(), + end: selection.end.text_anchor.clone(), + reversed: selection.reversed, + goal: Default::default(), + }; + Some(proto::view_update::Variant::Editor( + proto::view_update::Editor { + newest_selection: Some(language::proto::serialize_selection(&selection)), + }, + )) + } + _ => None, + } } +} +impl Item for Editor { 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/gpui/src/app.rs b/crates/gpui/src/app.rs index a4dcd52f5ad01fea3a4f843b586cb42800d30025..f8f505ee783fe274e9f4b182bbbe68c4d34cc760 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -2121,6 +2121,10 @@ impl AppContext { &self.platform } + pub fn has_global(&self) -> bool { + self.globals.contains_key(&TypeId::of::()) + } + pub fn global(&self) -> &T { self.globals .get(&TypeId::of::()) @@ -3654,6 +3658,10 @@ impl AnyViewHandle { view_type: self.view_type, } } + + pub fn view_type(&self) -> TypeId { + self.view_type + } } impl Clone for AnyViewHandle { diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 785de7ea089d9ae427026057bce1ff0ccd12d952..7668533a35e75fe60845662e0d73d87795dbfe5e 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -4248,6 +4248,17 @@ mod tests { .project_path(cx)), Some((worktree_id, "2.txt").into()) ); + + // When client A activates a different editor, client B does so as well. + workspace_a.update(cx_a, |workspace, cx| { + workspace.activate_item(editor_a1.as_ref(), cx) + }); + workspace_b + .condition(cx_b, |workspace, cx| { + let active_item = workspace.active_item(cx).unwrap(); + active_item.project_path(cx) == Some((worktree_id, "1.txt").into()) + }) + .await; } #[gpui::test(iterations = 100)] diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 997aae3d96379c42f2e1d6412af6305c4d41e372..43aa1f54a11e4b16b7831c87e51d2f8c2325ef71 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -288,7 +288,7 @@ impl Pane { } } - pub fn open_item( + pub(crate) fn open_item( &mut self, project_entry_id: ProjectEntryId, cx: &mut ViewContext, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index eba7f12981d59bc69aac67130b492b8fa42fe4fa..6f013d910f3525986b49de5c3b36a7cb8ef7868e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -11,7 +11,7 @@ use client::{ proto, Authenticate, ChannelList, Client, PeerId, Subscription, TypedEnvelope, User, UserStore, }; use clock::ReplicaId; -use collections::HashMap; +use collections::{HashMap, HashSet}; use gpui::{ action, color::Color, @@ -49,13 +49,18 @@ type ProjectItemBuilders = HashMap< fn(usize, ModelHandle, AnyModelHandle, &mut MutableAppContext) -> Box, >; -type FollowedItemBuilders = Vec< - fn( - ViewHandle, - ModelHandle, - &mut Option, - &mut MutableAppContext, - ) -> Option>>>, +type FollowedItemBuilder = fn( + ViewHandle, + ModelHandle, + &mut Option, + &mut MutableAppContext, +) -> Option>>>; +type FollowedItemBuilders = HashMap< + TypeId, + ( + FollowedItemBuilder, + fn(AnyViewHandle) -> Box, + ), >; action!(Open, Arc); @@ -124,9 +129,14 @@ 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) + builders.insert( + TypeId::of::(), + (I::for_state_message, |this| { + Box::new(this.downcast::().unwrap()) + }), + ); }); } @@ -158,9 +168,6 @@ 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; @@ -225,7 +232,7 @@ pub trait ProjectItem: Item { ) -> Self; } -pub trait FollowedItem { +pub trait FollowedItem: Item { fn for_state_message( pane: ViewHandle, project: ModelHandle, @@ -234,8 +241,40 @@ pub trait FollowedItem { ) -> Option>>> where Self: Sized; + fn to_state_message(&self, cx: &AppContext) -> proto::view::Variant; + fn to_update_message( + &self, + event: &Self::Event, + cx: &AppContext, + ) -> Option; +} +pub trait FollowedItemHandle { + fn id(&self) -> usize; fn to_state_message(&self, cx: &AppContext) -> proto::view::Variant; + fn to_update_message( + &self, + event: &dyn Any, + cx: &AppContext, + ) -> Option; +} + +impl FollowedItemHandle for ViewHandle { + fn id(&self) -> usize { + self.id() + } + + fn to_state_message(&self, cx: &AppContext) -> proto::view::Variant { + self.read(cx).to_state_message(cx) + } + + fn to_update_message( + &self, + event: &dyn Any, + cx: &AppContext, + ) -> Option { + self.read(cx).to_update_message(event.downcast_ref()?, cx) + } } pub trait ItemHandle: 'static { @@ -262,8 +301,7 @@ pub trait ItemHandle: 'static { cx: &mut MutableAppContext, ) -> Task>; fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option; - fn can_be_followed(&self, cx: &AppContext) -> bool; - fn to_state_message(&self, cx: &AppContext) -> Option; + fn to_followed_item_handle(&self, cx: &AppContext) -> Option>; } pub trait WeakItemHandle { @@ -318,15 +356,22 @@ impl ItemHandle for ViewHandle { pane.close_item(item.id(), cx); return; } + if T::should_activate_item_on_event(event) { if let Some(ix) = pane.index_for_item(&item) { pane.activate_item(ix, cx); pane.activate(cx); } } + if T::should_update_tab_on_event(event) { cx.notify() } + + if let Some(message) = item + .to_followed_item_handle(cx) + .and_then(|i| i.to_update_message(event, cx)) + {} }) .detach(); } @@ -380,14 +425,14 @@ impl ItemHandle for ViewHandle { self.read(cx).act_as_type(type_id, self, cx) } - fn can_be_followed(&self, cx: &AppContext) -> bool { - self.read(cx).as_followed().is_some() - } - - fn to_state_message(&self, cx: &AppContext) -> Option { - self.read(cx) - .as_followed() - .map(|item| item.to_state_message(cx)) + fn to_followed_item_handle(&self, cx: &AppContext) -> Option> { + if cx.has_global::() { + let builders = cx.global::(); + let item = self.to_any(); + Some(builders.get(&item.view_type())?.1(item)) + } else { + None + } } } @@ -487,9 +532,16 @@ pub struct Workspace { active_pane: ViewHandle, status_bar: ViewHandle, project: ModelHandle, + leader_state: LeaderState, _observe_current_user: Task<()>, } +#[derive(Default)] +struct LeaderState { + followers: HashSet, + subscriptions: Vec, +} + impl Workspace { pub fn new(params: &WorkspaceParams, cx: &mut ViewContext) -> Self { cx.observe(¶ms.project, |_, project, cx| { @@ -561,6 +613,7 @@ impl Workspace { left_sidebar: Sidebar::new(Side::Left), right_sidebar: Sidebar::new(Side::Right), project: params.project.clone(), + leader_state: Default::default(), _observe_current_user, }; this.project_remote_id_changed(this.project.read(cx).remote_id(), cx); @@ -1068,6 +1121,13 @@ impl Workspace { let (project, pane) = this.read_with(&cx, |this, _| { (this.project.clone(), this.active_pane().clone()) }); + let item_builders = cx.update(|cx| { + cx.default_global::() + .values() + .map(|b| b.0) + .collect::>() + .clone() + }); for view in &mut response.views { let variant = view .variant @@ -1075,7 +1135,7 @@ impl Workspace { .ok_or_else(|| anyhow!("missing variant"))?; cx.update(|cx| { let mut variant = Some(variant); - for build_item in cx.default_global::().clone() { + for build_item in &item_builders { if let Some(task) = build_item(pane.clone(), project.clone(), &mut variant, cx) { @@ -1323,28 +1383,29 @@ impl Workspace { async fn handle_follow( this: ViewHandle, - _: TypedEnvelope, + envelope: TypedEnvelope, _: Arc, - cx: AsyncAppContext, + mut cx: AsyncAppContext, ) -> Result { - this.read_with(&cx, |this, cx| { - let current_view_id = if let Some(active_item) = this.active_item(cx) { - if active_item.can_be_followed(cx) { - Some(active_item.id() as u64) - } else { - None - } - } else { - None - }; + this.update(&mut cx, |this, cx| { + this.leader_state + .followers + .insert(envelope.original_sender_id()?); + + let current_view_id = this + .active_item(cx) + .and_then(|i| i.to_followed_item_handle(cx)) + .map(|i| i.id() as u64); Ok(proto::FollowResponse { current_view_id, views: this .items(cx) .filter_map(|item| { - let variant = item.to_state_message(cx)?; + let id = item.id() as u64; + let item = item.to_followed_item_handle(cx)?; + let variant = item.to_state_message(cx); Some(proto::View { - id: item.id() as u64, + id, variant: Some(variant), }) })