Detailed changes
@@ -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<proto::OpenWorktree>,
) -> 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,
});
@@ -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<user::UserStore>,
pub fs: Arc<dyn fs::Fs>,
pub channel_list: ModelHandle<ChannelList>,
+ pub presence: ModelHandle<Presence>,
}
pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
@@ -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),
@@ -0,0 +1,31 @@
+use crate::presence::Presence;
+use gpui::{
+ elements::Empty, Element, ElementBox, Entity, ModelHandle, RenderContext, View, ViewContext,
+};
+
+pub struct PeoplePanel {
+ presence: ModelHandle<Presence>,
+}
+
+impl PeoplePanel {
+ pub fn new(presence: ModelHandle<Presence>, cx: &mut ViewContext<Self>) -> 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<Self>) -> ElementBox {
+ Empty::new().boxed()
+ }
+}
@@ -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<Collaborator>,
+ user_store: Arc<UserStore>,
+ rpc: Arc<Client>,
+ _maintain_people: Task<()>,
+}
+
+#[derive(Debug)]
+struct Collaborator {
+ user: Arc<User>,
+ worktrees: Vec<CollaboratorWorktree>,
+}
+
+#[derive(Debug)]
+struct CollaboratorWorktree {
+ root_name: String,
+ is_shared: bool,
+ participants: Vec<Arc<User>>,
+}
+
+impl Presence {
+ pub fn new(user_store: Arc<UserStore>, rpc: Arc<Client>, cx: &mut ModelContext<Self>) -> 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<Self>) -> Task<Result<()>> {
+ 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<UserStore>,
+ ) -> Result<Self> {
+ 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 })
+ }
+}
@@ -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<AppState> {
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),
@@ -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();
@@ -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;
}