From f2825ccebacd77d71cc63dc642ad99740774d1ab Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 17 Sep 2021 16:14:44 +0200 Subject: [PATCH] Start on `Presence` as the backing model for the people panel --- server/src/rpc.rs | 14 ++++- zed/src/lib.rs | 4 ++ zed/src/main.rs | 5 +- zed/src/people_panel.rs | 31 ++++++++++ zed/src/presence.rs | 129 ++++++++++++++++++++++++++++++++++++++++ zed/src/test.rs | 2 + zed/src/workspace.rs | 7 ++- zrpc/proto/zed.proto | 5 +- 8 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 zed/src/people_panel.rs create mode 100644 zed/src/presence.rs diff --git a/server/src/rpc.rs b/server/src/rpc.rs index 950b6749dc8a7f6daee42c7d6c750162f8888902..1b65ed6d67dedcaa7abe56da47d83d033aabe942 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -1,6 +1,6 @@ use super::{ auth, - db::{ChannelId, MessageId, User, UserId}, + db::{ChannelId, MessageId, UserId}, AppState, }; use anyhow::anyhow; @@ -280,11 +280,20 @@ impl Server { request: TypedEnvelope, ) -> tide::Result<()> { let receipt = request.receipt(); + let user_id = self + .state + .read() + .await + .user_id_for_connection(request.sender_id)?; let mut collaborator_user_ids = Vec::new(); for github_login in request.payload.collaborator_logins { match self.app_state.db.create_user(&github_login, false).await { - Ok(user_id) => collaborator_user_ids.push(user_id), + Ok(collaborator_user_id) => { + if collaborator_user_id != user_id { + collaborator_user_ids.push(collaborator_user_id); + } + } Err(err) => { let message = err.to_string(); self.peer @@ -717,6 +726,7 @@ impl Server { is_online: true, }); host.worktrees.push(proto::CollaboratorWorktree { + root_name: worktree.root_name.clone(), is_shared: worktree.share().is_ok(), participants, }); diff --git a/zed/src/lib.rs b/zed/src/lib.rs index c9cec56f46da05851471cd20f9e4adb9133dc18c..e07d04a5a4af726109f4fce68206ab52d17d8a59 100644 --- a/zed/src/lib.rs +++ b/zed/src/lib.rs @@ -8,6 +8,8 @@ mod fuzzy; pub mod http; pub mod language; pub mod menus; +pub mod people_panel; +pub mod presence; pub mod project_browser; pub mod rpc; pub mod settings; @@ -26,6 +28,7 @@ use channel::ChannelList; use gpui::{action, keymap::Binding, ModelHandle}; use parking_lot::Mutex; use postage::watch; +use presence::Presence; use std::sync::Arc; pub use settings::Settings; @@ -46,6 +49,7 @@ pub struct AppState { pub user_store: Arc, pub fs: Arc, pub channel_list: ModelHandle, + pub presence: ModelHandle, } pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { diff --git a/zed/src/main.rs b/zed/src/main.rs index 87426c1ca7bac448aa9b0f2d49ee341687f5f525..45aeb52dbcf667fff0b8f34a2ec138847479b3da 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -13,7 +13,9 @@ use zed::{ channel::ChannelList, chat_panel, editor, file_finder, fs::RealFs, - http, language, menus, rpc, settings, theme_selector, + http, language, menus, + presence::Presence, + rpc, settings, theme_selector, user::UserStore, workspace::{self, OpenNew, OpenParams, OpenPaths}, AppState, @@ -45,6 +47,7 @@ fn main() { settings, themes, channel_list: cx.add_model(|cx| ChannelList::new(user_store.clone(), rpc.clone(), cx)), + presence: cx.add_model(|cx| Presence::new(user_store.clone(), rpc.clone(), cx)), rpc, user_store, fs: Arc::new(RealFs), diff --git a/zed/src/people_panel.rs b/zed/src/people_panel.rs new file mode 100644 index 0000000000000000000000000000000000000000..7e6d67548966ed1d5940f57dc8a8080d8b927f4d --- /dev/null +++ b/zed/src/people_panel.rs @@ -0,0 +1,31 @@ +use crate::presence::Presence; +use gpui::{ + elements::Empty, Element, ElementBox, Entity, ModelHandle, RenderContext, View, ViewContext, +}; + +pub struct PeoplePanel { + presence: ModelHandle, +} + +impl PeoplePanel { + pub fn new(presence: ModelHandle, cx: &mut ViewContext) -> Self { + cx.observe(&presence, |_, _, cx| cx.notify()); + Self { presence } + } +} + +pub enum Event {} + +impl Entity for PeoplePanel { + type Event = Event; +} + +impl View for PeoplePanel { + fn ui_name() -> &'static str { + "PeoplePanel" + } + + fn render(&mut self, _: &mut RenderContext) -> ElementBox { + Empty::new().boxed() + } +} diff --git a/zed/src/presence.rs b/zed/src/presence.rs new file mode 100644 index 0000000000000000000000000000000000000000..5196a6971cd46304da383cda4508e0900c9e038c --- /dev/null +++ b/zed/src/presence.rs @@ -0,0 +1,129 @@ +use crate::{ + rpc::Client, + user::{User, UserStore}, + util::TryFutureExt, +}; +use anyhow::Result; +use gpui::{Entity, ModelContext, Task}; +use postage::prelude::Stream; +use smol::future::FutureExt; +use std::{collections::HashSet, sync::Arc, time::Duration}; +use zrpc::proto; + +pub struct Presence { + collaborators: Vec, + user_store: Arc, + rpc: Arc, + _maintain_people: Task<()>, +} + +#[derive(Debug)] +struct Collaborator { + user: Arc, + worktrees: Vec, +} + +#[derive(Debug)] +struct CollaboratorWorktree { + root_name: String, + is_shared: bool, + participants: Vec>, +} + +impl Presence { + pub fn new(user_store: Arc, rpc: Arc, cx: &mut ModelContext) -> Self { + let _maintain_collaborators = cx.spawn_weak(|this, mut cx| { + let user_store = user_store.clone(); + let foreground = cx.foreground(); + async move { + let mut current_user = user_store.watch_current_user(); + loop { + let timer = foreground.timer(Duration::from_secs(2)); + let next_current_user = async { + current_user.recv().await; + }; + + next_current_user.race(timer).await; + if current_user.borrow().is_some() { + if let Some(this) = cx.read(|cx| this.upgrade(cx)) { + this.update(&mut cx, |this, cx| this.refresh(cx)) + .log_err() + .await; + } + } + } + } + }); + + Self { + collaborators: Vec::new(), + user_store, + rpc, + _maintain_people: _maintain_collaborators, + } + } + + fn refresh(&self, cx: &mut ModelContext) -> Task> { + cx.spawn(|this, mut cx| { + let rpc = self.rpc.clone(); + let user_store = self.user_store.clone(); + async move { + let response = rpc.request(proto::GetCollaborators {}).await?; + let mut user_ids = HashSet::new(); + for collaborator in &response.collaborators { + user_ids.insert(collaborator.user_id); + user_ids.extend( + collaborator + .worktrees + .iter() + .flat_map(|w| &w.participants) + .copied(), + ); + } + user_store + .load_users(user_ids.into_iter().collect()) + .await?; + + let mut collaborators = Vec::new(); + for collaborator in response.collaborators { + collaborators.push(Collaborator::from_proto(collaborator, &user_store).await?); + } + + this.update(&mut cx, |this, cx| { + this.collaborators = collaborators; + cx.notify(); + }); + + Ok(()) + } + }) + } +} + +pub enum Event {} + +impl Entity for Presence { + type Event = Event; +} + +impl Collaborator { + async fn from_proto( + collaborator: proto::Collaborator, + user_store: &Arc, + ) -> Result { + let user = user_store.fetch_user(collaborator.user_id).await?; + let mut worktrees = Vec::new(); + for worktree in collaborator.worktrees { + let mut participants = Vec::new(); + for participant_id in worktree.participants { + participants.push(user_store.fetch_user(participant_id).await?); + } + worktrees.push(CollaboratorWorktree { + root_name: worktree.root_name, + is_shared: worktree.is_shared, + participants, + }); + } + Ok(Self { user, worktrees }) + } +} diff --git a/zed/src/test.rs b/zed/src/test.rs index 7d027a8a1771b41fd2b68844802a2f6939b6448b..77b284b24580a777a30195b8d22a330b54497aeb 100644 --- a/zed/src/test.rs +++ b/zed/src/test.rs @@ -4,6 +4,7 @@ use crate::{ fs::RealFs, http::{HttpClient, Request, Response, ServerResponse}, language::LanguageRegistry, + presence::Presence, rpc::{self, Client, Credentials, EstablishConnectionError}, settings::{self, ThemeRegistry}, time::ReplicaId, @@ -175,6 +176,7 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { themes, languages: languages.clone(), channel_list: cx.add_model(|cx| ChannelList::new(user_store.clone(), rpc.clone(), cx)), + presence: cx.add_model(|cx| Presence::new(user_store.clone(), rpc.clone(), cx)), rpc, user_store, fs: Arc::new(RealFs), diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index bc918bb30fd5ca3f65c8cae463e4edfbf30b430b..bcdbe17a545c4d9479effaf7eaa840f2ab7e1ca6 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -7,6 +7,7 @@ use crate::{ editor::Buffer, fs::Fs, language::LanguageRegistry, + people_panel::PeoplePanel, project_browser::ProjectBrowser, rpc, settings::Settings, @@ -378,7 +379,11 @@ impl Workspace { }) .into(), ); - right_sidebar.add_item("icons/user-16.svg", cx.add_view(|_| ProjectBrowser).into()); + right_sidebar.add_item( + "icons/user-16.svg", + cx.add_view(|cx| PeoplePanel::new(app_state.presence.clone(), cx)) + .into(), + ); let mut current_user = app_state.user_store.watch_current_user().clone(); let mut connection_status = app_state.rpc.status().clone(); diff --git a/zrpc/proto/zed.proto b/zrpc/proto/zed.proto index 30a282f6b59a1b7af5fcdb7588209ed06e33530a..c1fd85ea277fc08fc847a6c1f8ef0ebd3edec15e 100644 --- a/zrpc/proto/zed.proto +++ b/zrpc/proto/zed.proto @@ -342,6 +342,7 @@ message Collaborator { } message CollaboratorWorktree { - bool is_shared = 1; - repeated uint64 participants = 2; + string root_name = 1; + bool is_shared = 2; + repeated uint64 participants = 3; }