From f453928b4495adb745b26ca56ce350967d4e01dc Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 26 Nov 2021 10:21:56 -0700 Subject: [PATCH 01/13] Associate the project with an active worktree This is similar to the active entry, but it can remain assigned even if there is no active entry and we can potentially manipulate it in other scenarios such as interaction with the project browser. This prepares the ground to show the collaborators for the active worktree. --- crates/project/src/lib.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index 3e129c8fb8a1d67feb7b7abdebb1bc89c0c5c1fe..d1d48f49ca8679fec8fdaacfe904a1db0ad39537 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -19,6 +19,7 @@ pub use worktree::*; pub struct Project { worktrees: Vec>, + active_worktree: Option, active_entry: Option, languages: Arc, client: Arc, @@ -46,6 +47,7 @@ impl Project { pub fn new(languages: Arc, rpc: Arc, fs: Arc) -> Self { Self { worktrees: Default::default(), + active_worktree: None, active_entry: None, languages, client: rpc, @@ -109,10 +111,25 @@ impl Project { fn add_worktree(&mut self, worktree: ModelHandle, cx: &mut ModelContext) { cx.observe(&worktree, |_, _, cx| cx.notify()).detach(); + if self.active_worktree.is_none() { + self.set_active_worktree(Some(worktree.id()), cx); + } self.worktrees.push(worktree); cx.notify(); } + fn set_active_worktree(&mut self, worktree_id: Option, cx: &mut ModelContext) { + if self.active_worktree != worktree_id { + self.active_worktree = worktree_id; + cx.notify(); + } + } + + pub fn active_worktree(&self) -> Option> { + self.active_worktree + .and_then(|worktree_id| self.worktree_for_id(worktree_id)) + } + pub fn set_active_path(&mut self, entry: Option, cx: &mut ModelContext) { let new_active_entry = entry.and_then(|project_path| { let worktree = self.worktree_for_id(project_path.worktree_id)?; @@ -123,6 +140,9 @@ impl Project { }) }); if new_active_entry != self.active_entry { + if let Some(worktree_id) = new_active_entry.map(|e| e.worktree_id) { + self.set_active_worktree(Some(worktree_id), cx); + } self.active_entry = new_active_entry; cx.emit(Event::ActiveEntryChanged(new_active_entry)); } @@ -184,6 +204,7 @@ impl Project { } pub fn close_remote_worktree(&mut self, id: u64, cx: &mut ModelContext) { + let mut reset_active = None; self.worktrees.retain(|worktree| { let keep = worktree.update(cx, |worktree, cx| { if let Some(worktree) = worktree.as_remote_mut() { @@ -196,9 +217,15 @@ impl Project { }); if !keep { cx.emit(Event::WorktreeRemoved(worktree.id())); + reset_active = Some(worktree.id()); } keep }); + + if self.active_worktree == reset_active { + self.active_worktree = self.worktrees.first().map(|w| w.id()); + cx.notify(); + } } pub fn match_paths<'a>( From 9f29eeda0316d980530c42662dd320323f9bcff5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 26 Nov 2021 10:22:34 -0700 Subject: [PATCH 02/13] Associate Peers with a user_id This will help us render the collaborator avatars for the active worktree when we know its peers. --- crates/rpc/proto/zed.proto | 1 + crates/server/src/rpc.rs | 12 +++++--- crates/server/src/rpc/store.rs | 50 ++++++++++++++-------------------- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 950178be340c9a0ff54e6722b9306e68f852a7fc..741f81c8cd74b6f359983515518112c8d8891e16 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -199,6 +199,7 @@ message UpdateCollaborators { message Peer { uint32 peer_id = 1; uint32 replica_id = 2; + uint64 user_id = 3; } message User { diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index f35bbb00e6185efdc0589d93be11ed3410c31f7a..9349b9538bb847b9b9c4124ba1e630ac0fb653bb 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -173,7 +173,7 @@ impl Server { if let Some(share) = worktree.share { broadcast( connection_id, - share.guest_connection_ids.keys().copied().collect(), + share.guests.keys().copied().collect(), |conn_id| { self.peer .send(conn_id, proto::UnshareWorktree { worktree_id }) @@ -234,6 +234,7 @@ impl Server { let collaborator_user_ids = collaborator_user_ids.into_iter().collect::>(); let worktree_id = self.state_mut().add_worktree(Worktree { host_connection_id: request.sender_id, + host_user_id, collaborator_user_ids: collaborator_user_ids.clone(), root_name: request.payload.root_name, share: None, @@ -260,7 +261,7 @@ impl Server { if let Some(share) = worktree.share { broadcast( request.sender_id, - share.guest_connection_ids.keys().copied().collect(), + share.guests.keys().copied().collect(), |conn_id| { self.peer .send(conn_id, proto::UnshareWorktree { worktree_id }) @@ -341,17 +342,19 @@ impl Server { .join_worktree(request.sender_id, user_id, worktree_id) .and_then(|joined| { let share = joined.worktree.share()?; - let peer_count = share.guest_connection_ids.len(); + let peer_count = share.guests.len(); let mut peers = Vec::with_capacity(peer_count); peers.push(proto::Peer { peer_id: joined.worktree.host_connection_id.0, replica_id: 0, + user_id: joined.worktree.host_user_id.to_proto(), }); - for (peer_conn_id, peer_replica_id) in &share.guest_connection_ids { + for (peer_conn_id, (peer_replica_id, peer_user_id)) in &share.guests { if *peer_conn_id != request.sender_id { peers.push(proto::Peer { peer_id: peer_conn_id.0, replica_id: *peer_replica_id as u32, + user_id: peer_user_id.to_proto(), }); } } @@ -379,6 +382,7 @@ impl Server { peer: Some(proto::Peer { peer_id: request.sender_id.0, replica_id: response.replica_id, + user_id: user_id.to_proto(), }), }, ) diff --git a/crates/server/src/rpc/store.rs b/crates/server/src/rpc/store.rs index ac9c069146745d48b3820cb4275895d973549e2c..424c1fc24c0f821a37891f7a7d372498c15a993f 100644 --- a/crates/server/src/rpc/store.rs +++ b/crates/server/src/rpc/store.rs @@ -1,7 +1,7 @@ use crate::db::{ChannelId, UserId}; use anyhow::anyhow; -use std::collections::{hash_map, HashMap, HashSet}; use rpc::{proto, ConnectionId}; +use std::collections::{hash_map, HashMap, HashSet}; #[derive(Default)] pub struct Store { @@ -21,13 +21,14 @@ struct ConnectionState { pub struct Worktree { pub host_connection_id: ConnectionId, + pub host_user_id: UserId, pub collaborator_user_ids: Vec, pub root_name: String, pub share: Option, } pub struct WorktreeShare { - pub guest_connection_ids: HashMap, + pub guests: HashMap, pub active_replica_ids: HashSet, pub entries: HashMap, } @@ -181,7 +182,7 @@ impl Store { let mut guests = HashSet::new(); if let Ok(share) = worktree.share() { - for guest_connection_id in share.guest_connection_ids.keys() { + for guest_connection_id in share.guests.keys() { if let Ok(user_id) = self.user_id_for_connection(*guest_connection_id) { guests.insert(user_id.to_proto()); } @@ -247,7 +248,7 @@ impl Store { } if let Some(share) = &worktree.share { - for connection_id in share.guest_connection_ids.keys() { + for connection_id in share.guests.keys() { if let Some(connection) = self.connections.get_mut(connection_id) { connection.worktrees.remove(&worktree_id); } @@ -278,7 +279,7 @@ impl Store { if let Some(worktree) = self.worktrees.get_mut(&worktree_id) { if worktree.host_connection_id == connection_id { worktree.share = Some(WorktreeShare { - guest_connection_ids: Default::default(), + guests: Default::default(), active_replica_ids: Default::default(), entries, }); @@ -306,7 +307,7 @@ impl Store { let connection_ids = worktree.connection_ids(); let collaborator_ids = worktree.collaborator_user_ids.clone(); if let Some(share) = worktree.share.take() { - for connection_id in share.guest_connection_ids.into_keys() { + for connection_id in share.guests.into_keys() { if let Some(connection) = self.connections.get_mut(&connection_id) { connection.worktrees.remove(&worktree_id); } @@ -354,7 +355,7 @@ impl Store { replica_id += 1; } share.active_replica_ids.insert(replica_id); - share.guest_connection_ids.insert(connection_id, replica_id); + share.guests.insert(connection_id, (replica_id, user_id)); #[cfg(test)] self.check_invariants(); @@ -372,7 +373,7 @@ impl Store { ) -> Option { let worktree = self.worktrees.get_mut(&worktree_id)?; let share = worktree.share.as_mut()?; - let replica_id = share.guest_connection_ids.remove(&connection_id)?; + let (replica_id, _) = share.guests.remove(&connection_id)?; share.active_replica_ids.remove(&replica_id); if let Some(connection) = self.connections.get_mut(&connection_id) { @@ -427,7 +428,7 @@ impl Store { Ok(self .read_worktree(worktree_id, connection_id)? .share()? - .guest_connection_ids + .guests .keys() .copied() .collect()) @@ -458,10 +459,7 @@ impl Store { .ok_or_else(|| anyhow!("worktree not found"))?; if worktree.host_connection_id == connection_id - || worktree - .share()? - .guest_connection_ids - .contains_key(&connection_id) + || worktree.share()?.guests.contains_key(&connection_id) { Ok(worktree) } else { @@ -484,9 +482,10 @@ impl Store { .ok_or_else(|| anyhow!("worktree not found"))?; if worktree.host_connection_id == connection_id - || worktree.share.as_ref().map_or(false, |share| { - share.guest_connection_ids.contains_key(&connection_id) - }) + || worktree + .share + .as_ref() + .map_or(false, |share| share.guests.contains_key(&connection_id)) { Ok(worktree) } else { @@ -504,11 +503,7 @@ impl Store { for worktree_id in &connection.worktrees { let worktree = &self.worktrees.get(&worktree_id).unwrap(); if worktree.host_connection_id != *connection_id { - assert!(worktree - .share() - .unwrap() - .guest_connection_ids - .contains_key(connection_id)); + assert!(worktree.share().unwrap().guests.contains_key(connection_id)); } } for channel_id in &connection.channels { @@ -544,20 +539,17 @@ impl Store { } if let Some(share) = &worktree.share { - for guest_connection_id in share.guest_connection_ids.keys() { + for guest_connection_id in share.guests.keys() { let guest_connection = self.connections.get(guest_connection_id).unwrap(); assert!(guest_connection.worktrees.contains(worktree_id)); } - assert_eq!( - share.active_replica_ids.len(), - share.guest_connection_ids.len(), - ); + assert_eq!(share.active_replica_ids.len(), share.guests.len(),); assert_eq!( share.active_replica_ids, share - .guest_connection_ids + .guests .values() - .copied() + .map(|(replica_id, _)| *replica_id) .collect::>(), ); } @@ -583,7 +575,7 @@ impl Worktree { pub fn connection_ids(&self) -> Vec { if let Some(share) = &self.share { share - .guest_connection_ids + .guests .keys() .copied() .chain(Some(self.host_connection_id)) From cd2c3c36066a7a66a685deb5d204ef369be17321 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 26 Nov 2021 10:59:41 -0700 Subject: [PATCH 03/13] Rename Collaborators to Contacts This will allow us to use the word "collaborator" to describe users that are actively collaborating on a worktree. Co-Authored-By: Antonio Scandurra --- crates/client/src/user.rs | 64 ++++++++++++--------------- crates/people_panel/src/lib.rs | 26 +++++------ crates/rpc/proto/zed.proto | 8 ++-- crates/rpc/src/proto.rs | 2 +- crates/server/src/rpc.rs | 81 ++++++++++++++++------------------ crates/server/src/rpc/store.rs | 57 ++++++++++++------------ 6 files changed, 113 insertions(+), 125 deletions(-) diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 47ea9ae55c087603137a5c0a573e65b883dff6b4..7ba447883461e7ceffc656f233ee488fdc374090 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -20,7 +20,7 @@ pub struct User { } #[derive(Debug)] -pub struct Collaborator { +pub struct Contact { pub user: Arc, pub worktrees: Vec, } @@ -36,10 +36,10 @@ pub struct WorktreeMetadata { pub struct UserStore { users: HashMap>, current_user: watch::Receiver>>, - collaborators: Arc<[Collaborator]>, + contacts: Arc<[Contact]>, rpc: Arc, http: Arc, - _maintain_collaborators: Task<()>, + _maintain_contacts: Task<()>, _maintain_current_user: Task<()>, } @@ -52,26 +52,26 @@ impl Entity for UserStore { impl UserStore { pub fn new(rpc: Arc, http: Arc, cx: &mut ModelContext) -> Self { let (mut current_user_tx, current_user_rx) = watch::channel(); - let (mut update_collaborators_tx, mut update_collaborators_rx) = - watch::channel::>(); - let update_collaborators_subscription = rpc.subscribe( + let (mut update_contacts_tx, mut update_contacts_rx) = + watch::channel::>(); + let update_contacts_subscription = rpc.subscribe( cx, - move |_: &mut Self, msg: TypedEnvelope, _, _| { - let _ = update_collaborators_tx.blocking_send(Some(msg.payload)); + move |_: &mut Self, msg: TypedEnvelope, _, _| { + let _ = update_contacts_tx.blocking_send(Some(msg.payload)); Ok(()) }, ); Self { users: Default::default(), current_user: current_user_rx, - collaborators: Arc::from([]), + contacts: Arc::from([]), rpc: rpc.clone(), http, - _maintain_collaborators: cx.spawn_weak(|this, mut cx| async move { - let _subscription = update_collaborators_subscription; - while let Some(message) = update_collaborators_rx.recv().await { + _maintain_contacts: cx.spawn_weak(|this, mut cx| async move { + let _subscription = update_contacts_subscription; + while let Some(message) = update_contacts_rx.recv().await { if let Some((message, this)) = message.zip(this.upgrade(&cx)) { - this.update(&mut cx, |this, cx| this.update_collaborators(message, cx)) + this.update(&mut cx, |this, cx| this.update_contacts(message, cx)) .log_err() .await; } @@ -100,35 +100,29 @@ impl UserStore { } } - fn update_collaborators( + fn update_contacts( &mut self, - message: proto::UpdateCollaborators, + message: proto::UpdateContacts, cx: &mut ModelContext, ) -> Task> { let mut user_ids = HashSet::new(); - for collaborator in &message.collaborators { - user_ids.insert(collaborator.user_id); - user_ids.extend( - collaborator - .worktrees - .iter() - .flat_map(|w| &w.guests) - .copied(), - ); + for contact in &message.contacts { + user_ids.insert(contact.user_id); + user_ids.extend(contact.worktrees.iter().flat_map(|w| &w.guests).copied()); } let load_users = self.load_users(user_ids.into_iter().collect(), cx); cx.spawn(|this, mut cx| async move { load_users.await?; - let mut collaborators = Vec::new(); - for collaborator in message.collaborators { - collaborators.push(Collaborator::from_proto(collaborator, &this, &mut cx).await?); + let mut contacts = Vec::new(); + for contact in message.contacts { + contacts.push(Contact::from_proto(contact, &this, &mut cx).await?); } this.update(&mut cx, |this, cx| { - collaborators.sort_by(|a, b| a.user.github_login.cmp(&b.user.github_login)); - this.collaborators = collaborators.into(); + contacts.sort_by(|a, b| a.user.github_login.cmp(&b.user.github_login)); + this.contacts = contacts.into(); cx.notify(); }); @@ -136,8 +130,8 @@ impl UserStore { }) } - pub fn collaborators(&self) -> &Arc<[Collaborator]> { - &self.collaborators + pub fn contacts(&self) -> &Arc<[Contact]> { + &self.contacts } pub fn load_users( @@ -212,19 +206,19 @@ impl User { } } -impl Collaborator { +impl Contact { async fn from_proto( - collaborator: proto::Collaborator, + contact: proto::Contact, user_store: &ModelHandle, cx: &mut AsyncAppContext, ) -> Result { let user = user_store .update(cx, |user_store, cx| { - user_store.fetch_user(collaborator.user_id, cx) + user_store.fetch_user(contact.user_id, cx) }) .await?; let mut worktrees = Vec::new(); - for worktree in collaborator.worktrees { + for worktree in contact.worktrees { let mut guests = Vec::new(); for participant_id in worktree.guests { guests.push( diff --git a/crates/people_panel/src/lib.rs b/crates/people_panel/src/lib.rs index 4a9d2927374225830b1867c91c6b322d15d6eb2a..00e87dc7e6de3094f1abec77efd016ceeb0d9e5b 100644 --- a/crates/people_panel/src/lib.rs +++ b/crates/people_panel/src/lib.rs @@ -1,4 +1,4 @@ -use client::{Collaborator, UserStore}; +use client::{Contact, UserStore}; use gpui::{ action, elements::*, @@ -24,10 +24,10 @@ pub fn init(cx: &mut MutableAppContext) { } pub struct PeoplePanel { - collaborators: ListState, + contacts: ListState, user_store: ModelHandle, settings: watch::Receiver, - _maintain_collaborators: Subscription, + _maintain_contacts: Subscription, } impl PeoplePanel { @@ -37,8 +37,8 @@ impl PeoplePanel { cx: &mut ViewContext, ) -> Self { Self { - collaborators: ListState::new( - user_store.read(cx).collaborators().len(), + contacts: ListState::new( + user_store.read(cx).contacts().len(), Orientation::Top, 1000., { @@ -46,10 +46,10 @@ impl PeoplePanel { let settings = settings.clone(); move |ix, cx| { let user_store = user_store.read(cx); - let collaborators = user_store.collaborators().clone(); + let contacts = user_store.contacts().clone(); let current_user_id = user_store.current_user().map(|user| user.id); Self::render_collaborator( - &collaborators[ix], + &contacts[ix], current_user_id, &settings.borrow().theme, cx, @@ -57,7 +57,7 @@ impl PeoplePanel { } }, ), - _maintain_collaborators: cx.observe(&user_store, Self::update_collaborators), + _maintain_contacts: cx.observe(&user_store, Self::update_contacts), user_store, settings, } @@ -103,14 +103,14 @@ impl PeoplePanel { .update(cx, |p, cx| p.close_remote_worktree(action.0, cx)); } - fn update_collaborators(&mut self, _: ModelHandle, cx: &mut ViewContext) { - self.collaborators - .reset(self.user_store.read(cx).collaborators().len()); + fn update_contacts(&mut self, _: ModelHandle, cx: &mut ViewContext) { + self.contacts + .reset(self.user_store.read(cx).contacts().len()); cx.notify(); } fn render_collaborator( - collaborator: &Collaborator, + collaborator: &Contact, current_user_id: Option, theme: &Theme, cx: &mut LayoutContext, @@ -305,7 +305,7 @@ impl View for PeoplePanel { fn render(&mut self, _: &mut RenderContext) -> ElementBox { let theme = &self.settings.borrow().theme.people_panel; - Container::new(List::new(self.collaborators.clone()).boxed()) + Container::new(List::new(self.contacts.clone()).boxed()) .with_style(theme.container) .boxed() } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 741f81c8cd74b6f359983515518112c8d8891e16..ed979b9342556b0a763e516d92392c6457668f8d 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -38,7 +38,7 @@ message Envelope { OpenWorktree open_worktree = 33; OpenWorktreeResponse open_worktree_response = 34; UnshareWorktree unshare_worktree = 35; - UpdateCollaborators update_collaborators = 36; + UpdateContacts update_contacts = 36; LeaveWorktree leave_worktree = 37; } } @@ -190,8 +190,8 @@ message GetChannelMessagesResponse { bool done = 2; } -message UpdateCollaborators { - repeated Collaborator collaborators = 1; +message UpdateContacts { + repeated Contact contacts = 1; } // Entities @@ -358,7 +358,7 @@ message ChannelMessage { Nonce nonce = 5; } -message Collaborator { +message Contact { uint64 user_id = 1; repeated WorktreeMetadata worktrees = 2; } diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index e9de319a1c6abf8c23efcbfd3e47622d133908dc..8299952c798609712d31b238db77184e182b1168 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -131,7 +131,7 @@ messages!( GetChannelMessagesResponse, GetChannels, GetChannelsResponse, - UpdateCollaborators, + UpdateContacts, GetUsers, GetUsersResponse, JoinChannel, diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 9349b9538bb847b9b9c4124ba1e630ac0fb653bb..88e58a629bd9954787a070c9de8e0a74090b9f03 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -118,8 +118,8 @@ impl Server { let (connection_id, handle_io, mut incoming_rx) = this.peer.add_connection(connection).await; this.state_mut().add_connection(connection_id, user_id); - if let Err(err) = this.update_collaborators_for_users(&[user_id]).await { - log::error!("error updating collaborators for {:?}: {}", user_id, err); + if let Err(err) = this.update_contacts_for_users(&[user_id]).await { + log::error!("error updating contacts for {:?}: {}", user_id, err); } let handle_io = handle_io.fuse(); @@ -196,7 +196,7 @@ impl Server { .await?; } - self.update_collaborators_for_users(removed_connection.collaborator_ids.iter()) + self.update_contacts_for_users(removed_connection.contact_ids.iter()) .await?; Ok(()) @@ -214,12 +214,12 @@ impl Server { let receipt = request.receipt(); let host_user_id = self.state().user_id_for_connection(request.sender_id)?; - let mut collaborator_user_ids = HashSet::new(); - collaborator_user_ids.insert(host_user_id); + let mut contact_user_ids = HashSet::new(); + contact_user_ids.insert(host_user_id); for github_login in request.payload.collaborator_logins { match self.app_state.db.create_user(&github_login, false).await { - Ok(collaborator_user_id) => { - collaborator_user_ids.insert(collaborator_user_id); + Ok(contact_user_id) => { + contact_user_ids.insert(contact_user_id); } Err(err) => { let message = err.to_string(); @@ -231,11 +231,11 @@ impl Server { } } - let collaborator_user_ids = collaborator_user_ids.into_iter().collect::>(); + let contact_user_ids = contact_user_ids.into_iter().collect::>(); let worktree_id = self.state_mut().add_worktree(Worktree { host_connection_id: request.sender_id, host_user_id, - collaborator_user_ids: collaborator_user_ids.clone(), + contact_user_ids: contact_user_ids.clone(), root_name: request.payload.root_name, share: None, }); @@ -243,8 +243,7 @@ impl Server { self.peer .respond(receipt, proto::OpenWorktreeResponse { worktree_id }) .await?; - self.update_collaborators_for_users(&collaborator_user_ids) - .await?; + self.update_contacts_for_users(&contact_user_ids).await?; Ok(()) } @@ -269,7 +268,7 @@ impl Server { ) .await?; } - self.update_collaborators_for_users(&worktree.collaborator_user_ids) + self.update_contacts_for_users(&worktree.contact_user_ids) .await?; Ok(()) } @@ -288,15 +287,14 @@ impl Server { .map(|entry| (entry.id, entry)) .collect(); - let collaborator_user_ids = + let contact_user_ids = self.state_mut() .share_worktree(worktree.id, request.sender_id, entries); - if let Some(collaborator_user_ids) = collaborator_user_ids { + if let Some(contact_user_ids) = contact_user_ids { self.peer .respond(request.receipt(), proto::ShareWorktreeResponse {}) .await?; - self.update_collaborators_for_users(&collaborator_user_ids) - .await?; + self.update_contacts_for_users(&contact_user_ids).await?; } else { self.peer .respond_with_error( @@ -324,7 +322,7 @@ impl Server { .send(conn_id, proto::UnshareWorktree { worktree_id }) }) .await?; - self.update_collaborators_for_users(&worktree.collaborator_ids) + self.update_contacts_for_users(&worktree.contact_ids) .await?; Ok(()) @@ -368,12 +366,12 @@ impl Server { peers, }; let connection_ids = joined.worktree.connection_ids(); - let collaborator_user_ids = joined.worktree.collaborator_user_ids.clone(); - Ok((response, connection_ids, collaborator_user_ids)) + let contact_user_ids = joined.worktree.contact_user_ids.clone(); + Ok((response, connection_ids, contact_user_ids)) }); match response_data { - Ok((response, connection_ids, collaborator_user_ids)) => { + Ok((response, connection_ids, contact_user_ids)) => { broadcast(request.sender_id, connection_ids, |conn_id| { self.peer.send( conn_id, @@ -389,8 +387,7 @@ impl Server { }) .await?; self.peer.respond(request.receipt(), response).await?; - self.update_collaborators_for_users(&collaborator_user_ids) - .await?; + self.update_contacts_for_users(&contact_user_ids).await?; } Err(error) => { self.peer @@ -425,7 +422,7 @@ impl Server { ) }) .await?; - self.update_collaborators_for_users(&worktree.collaborator_ids) + self.update_contacts_for_users(&worktree.contact_ids) .await?; } Ok(()) @@ -595,7 +592,7 @@ impl Server { Ok(()) } - async fn update_collaborators_for_users<'a>( + async fn update_contacts_for_users<'a>( self: &Arc, user_ids: impl IntoIterator, ) -> tide::Result<()> { @@ -604,12 +601,12 @@ impl Server { { let state = self.state(); for user_id in user_ids { - let collaborators = state.collaborators_for_user(*user_id); + let contacts = state.contacts_for_user(*user_id); for connection_id in state.connection_ids_for_user(*user_id) { send_futures.push(self.peer.send( connection_id, - proto::UpdateCollaborators { - collaborators: collaborators.clone(), + proto::UpdateContacts { + contacts: contacts.clone(), }, )); } @@ -2108,7 +2105,7 @@ mod tests { } #[gpui::test] - async fn test_collaborators( + async fn test_contacts( mut cx_a: TestAppContext, mut cx_b: TestAppContext, mut cx_c: TestAppContext, @@ -2145,17 +2142,17 @@ mod tests { user_store_a .condition(&cx_a, |user_store, _| { - collaborators(user_store) == vec![("user_a", vec![("a", vec![])])] + contacts(user_store) == vec![("user_a", vec![("a", vec![])])] }) .await; user_store_b .condition(&cx_b, |user_store, _| { - collaborators(user_store) == vec![("user_a", vec![("a", vec![])])] + contacts(user_store) == vec![("user_a", vec![("a", vec![])])] }) .await; user_store_c .condition(&cx_c, |user_store, _| { - collaborators(user_store) == vec![("user_a", vec![("a", vec![])])] + contacts(user_store) == vec![("user_a", vec![("a", vec![])])] }) .await; @@ -2175,37 +2172,37 @@ mod tests { user_store_a .condition(&cx_a, |user_store, _| { - collaborators(user_store) == vec![("user_a", vec![("a", vec!["user_b"])])] + contacts(user_store) == vec![("user_a", vec![("a", vec!["user_b"])])] }) .await; user_store_b .condition(&cx_b, |user_store, _| { - collaborators(user_store) == vec![("user_a", vec![("a", vec!["user_b"])])] + contacts(user_store) == vec![("user_a", vec![("a", vec!["user_b"])])] }) .await; user_store_c .condition(&cx_c, |user_store, _| { - collaborators(user_store) == vec![("user_a", vec![("a", vec!["user_b"])])] + contacts(user_store) == vec![("user_a", vec![("a", vec!["user_b"])])] }) .await; cx_a.update(move |_| drop(worktree_a)); user_store_a - .condition(&cx_a, |user_store, _| collaborators(user_store) == vec![]) + .condition(&cx_a, |user_store, _| contacts(user_store) == vec![]) .await; user_store_b - .condition(&cx_b, |user_store, _| collaborators(user_store) == vec![]) + .condition(&cx_b, |user_store, _| contacts(user_store) == vec![]) .await; user_store_c - .condition(&cx_c, |user_store, _| collaborators(user_store) == vec![]) + .condition(&cx_c, |user_store, _| contacts(user_store) == vec![]) .await; - fn collaborators(user_store: &UserStore) -> Vec<(&str, Vec<(&str, Vec<&str>)>)> { + fn contacts(user_store: &UserStore) -> Vec<(&str, Vec<(&str, Vec<&str>)>)> { user_store - .collaborators() + .contacts() .iter() - .map(|collaborator| { - let worktrees = collaborator + .map(|contact| { + let worktrees = contact .worktrees .iter() .map(|w| { @@ -2215,7 +2212,7 @@ mod tests { ) }) .collect(); - (collaborator.user.github_login.as_str(), worktrees) + (contact.user.github_login.as_str(), worktrees) }) .collect() } diff --git a/crates/server/src/rpc/store.rs b/crates/server/src/rpc/store.rs index 424c1fc24c0f821a37891f7a7d372498c15a993f..2039ac21d2b9eeedbe367c4980b9db15382edde7 100644 --- a/crates/server/src/rpc/store.rs +++ b/crates/server/src/rpc/store.rs @@ -22,7 +22,7 @@ struct ConnectionState { pub struct Worktree { pub host_connection_id: ConnectionId, pub host_user_id: UserId, - pub collaborator_user_ids: Vec, + pub contact_user_ids: Vec, pub root_name: String, pub share: Option, } @@ -44,7 +44,7 @@ pub type ReplicaId = u16; pub struct RemovedConnectionState { pub hosted_worktrees: HashMap, pub guest_worktree_ids: HashMap>, - pub collaborator_ids: HashSet, + pub contact_ids: HashSet, } pub struct JoinedWorktree<'a> { @@ -54,12 +54,12 @@ pub struct JoinedWorktree<'a> { pub struct UnsharedWorktree { pub connection_ids: Vec, - pub collaborator_ids: Vec, + pub contact_ids: Vec, } pub struct LeftWorktree { pub connection_ids: Vec, - pub collaborator_ids: Vec, + pub contact_ids: Vec, } impl Store { @@ -107,14 +107,14 @@ impl Store { for worktree_id in connection.worktrees.clone() { if let Ok(worktree) = self.remove_worktree(worktree_id, connection_id) { result - .collaborator_ids - .extend(worktree.collaborator_user_ids.iter().copied()); + .contact_ids + .extend(worktree.contact_user_ids.iter().copied()); result.hosted_worktrees.insert(worktree_id, worktree); } else if let Some(worktree) = self.leave_worktree(connection_id, worktree_id) { result .guest_worktree_ids .insert(worktree_id, worktree.connection_ids); - result.collaborator_ids.extend(worktree.collaborator_ids); + result.contact_ids.extend(worktree.contact_ids); } } @@ -171,8 +171,8 @@ impl Store { .copied() } - pub fn collaborators_for_user(&self, user_id: UserId) -> Vec { - let mut collaborators = HashMap::new(); + pub fn contacts_for_user(&self, user_id: UserId) -> Vec { + let mut contacts = HashMap::new(); for worktree_id in self .visible_worktrees_by_user_id .get(&user_id) @@ -190,9 +190,9 @@ impl Store { } if let Ok(host_user_id) = self.user_id_for_connection(worktree.host_connection_id) { - collaborators + contacts .entry(host_user_id) - .or_insert_with(|| proto::Collaborator { + .or_insert_with(|| proto::Contact { user_id: host_user_id.to_proto(), worktrees: Vec::new(), }) @@ -206,14 +206,14 @@ impl Store { } } - collaborators.into_values().collect() + contacts.into_values().collect() } pub fn add_worktree(&mut self, worktree: Worktree) -> u64 { let worktree_id = self.next_worktree_id; - for collaborator_user_id in &worktree.collaborator_user_ids { + for contact_user_id in &worktree.contact_user_ids { self.visible_worktrees_by_user_id - .entry(*collaborator_user_id) + .entry(*contact_user_id) .or_default() .insert(worktree_id); } @@ -255,10 +255,9 @@ impl Store { } } - for collaborator_user_id in &worktree.collaborator_user_ids { - if let Some(visible_worktrees) = self - .visible_worktrees_by_user_id - .get_mut(&collaborator_user_id) + for contact_user_id in &worktree.contact_user_ids { + if let Some(visible_worktrees) = + self.visible_worktrees_by_user_id.get_mut(&contact_user_id) { visible_worktrees.remove(&worktree_id); } @@ -283,7 +282,7 @@ impl Store { active_replica_ids: Default::default(), entries, }); - return Some(worktree.collaborator_user_ids.clone()); + return Some(worktree.contact_user_ids.clone()); } } None @@ -305,7 +304,7 @@ impl Store { } let connection_ids = worktree.connection_ids(); - let collaborator_ids = worktree.collaborator_user_ids.clone(); + let contact_ids = worktree.contact_user_ids.clone(); if let Some(share) = worktree.share.take() { for connection_id in share.guests.into_keys() { if let Some(connection) = self.connections.get_mut(&connection_id) { @@ -318,7 +317,7 @@ impl Store { Ok(UnsharedWorktree { connection_ids, - collaborator_ids, + contact_ids, }) } else { Err(anyhow!("worktree is not shared"))? @@ -339,7 +338,7 @@ impl Store { .worktrees .get_mut(&worktree_id) .and_then(|worktree| { - if worktree.collaborator_user_ids.contains(&user_id) { + if worktree.contact_user_ids.contains(&user_id) { Some(worktree) } else { None @@ -381,14 +380,14 @@ impl Store { } let connection_ids = worktree.connection_ids(); - let collaborator_ids = worktree.collaborator_user_ids.clone(); + let contact_ids = worktree.contact_user_ids.clone(); #[cfg(test)] self.check_invariants(); Some(LeftWorktree { connection_ids, - collaborator_ids, + contact_ids, }) } @@ -530,11 +529,9 @@ impl Store { let host_connection = self.connections.get(&worktree.host_connection_id).unwrap(); assert!(host_connection.worktrees.contains(worktree_id)); - for collaborator_id in &worktree.collaborator_user_ids { - let visible_worktree_ids = self - .visible_worktrees_by_user_id - .get(collaborator_id) - .unwrap(); + for contact_id in &worktree.contact_user_ids { + let visible_worktree_ids = + self.visible_worktrees_by_user_id.get(contact_id).unwrap(); assert!(visible_worktree_ids.contains(worktree_id)); } @@ -558,7 +555,7 @@ impl Store { for (user_id, visible_worktree_ids) in &self.visible_worktrees_by_user_id { for worktree_id in visible_worktree_ids { let worktree = self.worktrees.get(worktree_id).unwrap(); - assert!(worktree.collaborator_user_ids.contains(user_id)); + assert!(worktree.contact_user_ids.contains(user_id)); } } From c8ad5b68e088e242102a510f6e04f931fc640475 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 26 Nov 2021 11:08:34 -0700 Subject: [PATCH 04/13] Rename collaborator_logins to authorized_logins Again, this is about reserving the concept of a "collaborator" for actual collaborators on a worktree. Co-Authored-By: Antonio Scandurra --- crates/project/src/worktree.rs | 6 ++--- crates/rpc/proto/zed.proto | 2 +- crates/server/src/rpc.rs | 12 +++++----- crates/server/src/rpc/store.rs | 43 ++++++++++++++++++---------------- 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index e6b444b5480b21c0deadfef5234356f6b9dd03df..79c235fc75e8a69503cbdd0f11683a7df9674dd6 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -848,13 +848,13 @@ impl LocalWorktree { while let Some(status) = status.recv().await { if let Some(this) = this.upgrade(&cx) { let remote_id = if let client::Status::Connected { .. } = status { - let collaborator_logins = this.read_with(&cx, |this, _| { + let authorized_logins = this.read_with(&cx, |this, _| { this.as_local().unwrap().config.collaborators.clone() }); let response = rpc .request(proto::OpenWorktree { root_name: root_name.clone(), - collaborator_logins, + authorized_logins, }) .await?; @@ -3316,7 +3316,7 @@ mod tests { open_worktree.payload, proto::OpenWorktree { root_name: "the-dir".to_string(), - collaborator_logins: vec!["friend-1".to_string(), "friend-2".to_string()], + authorized_logins: vec!["friend-1".to_string(), "friend-2".to_string()], } ); diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index ed979b9342556b0a763e516d92392c6457668f8d..5b899ae91683e3034fd6d41959eeeaea45490065 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -55,7 +55,7 @@ message Error { message OpenWorktree { string root_name = 1; - repeated string collaborator_logins = 2; + repeated string authorized_logins = 2; } message OpenWorktreeResponse { diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 88e58a629bd9954787a070c9de8e0a74090b9f03..a66e1cf386e944f4b705452edbfd4978752657f6 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -216,7 +216,7 @@ impl Server { let mut contact_user_ids = HashSet::new(); contact_user_ids.insert(host_user_id); - for github_login in request.payload.collaborator_logins { + for github_login in request.payload.authorized_logins { match self.app_state.db.create_user(&github_login, false).await { Ok(contact_user_id) => { contact_user_ids.insert(contact_user_id); @@ -235,7 +235,7 @@ impl Server { let worktree_id = self.state_mut().add_worktree(Worktree { host_connection_id: request.sender_id, host_user_id, - contact_user_ids: contact_user_ids.clone(), + authorized_user_ids: contact_user_ids.clone(), root_name: request.payload.root_name, share: None, }); @@ -268,7 +268,7 @@ impl Server { ) .await?; } - self.update_contacts_for_users(&worktree.contact_user_ids) + self.update_contacts_for_users(&worktree.authorized_user_ids) .await?; Ok(()) } @@ -322,7 +322,7 @@ impl Server { .send(conn_id, proto::UnshareWorktree { worktree_id }) }) .await?; - self.update_contacts_for_users(&worktree.contact_ids) + self.update_contacts_for_users(&worktree.authorized_user_ids) .await?; Ok(()) @@ -366,7 +366,7 @@ impl Server { peers, }; let connection_ids = joined.worktree.connection_ids(); - let contact_user_ids = joined.worktree.contact_user_ids.clone(); + let contact_user_ids = joined.worktree.authorized_user_ids.clone(); Ok((response, connection_ids, contact_user_ids)) }); @@ -422,7 +422,7 @@ impl Server { ) }) .await?; - self.update_contacts_for_users(&worktree.contact_ids) + self.update_contacts_for_users(&worktree.authorized_user_ids) .await?; } Ok(()) diff --git a/crates/server/src/rpc/store.rs b/crates/server/src/rpc/store.rs index 2039ac21d2b9eeedbe367c4980b9db15382edde7..77bb01702fa3988d3ecdc1bd8f612c9ea673a915 100644 --- a/crates/server/src/rpc/store.rs +++ b/crates/server/src/rpc/store.rs @@ -22,7 +22,7 @@ struct ConnectionState { pub struct Worktree { pub host_connection_id: ConnectionId, pub host_user_id: UserId, - pub contact_user_ids: Vec, + pub authorized_user_ids: Vec, pub root_name: String, pub share: Option, } @@ -54,12 +54,12 @@ pub struct JoinedWorktree<'a> { pub struct UnsharedWorktree { pub connection_ids: Vec, - pub contact_ids: Vec, + pub authorized_user_ids: Vec, } pub struct LeftWorktree { pub connection_ids: Vec, - pub contact_ids: Vec, + pub authorized_user_ids: Vec, } impl Store { @@ -108,13 +108,13 @@ impl Store { if let Ok(worktree) = self.remove_worktree(worktree_id, connection_id) { result .contact_ids - .extend(worktree.contact_user_ids.iter().copied()); + .extend(worktree.authorized_user_ids.iter().copied()); result.hosted_worktrees.insert(worktree_id, worktree); } else if let Some(worktree) = self.leave_worktree(connection_id, worktree_id) { result .guest_worktree_ids .insert(worktree_id, worktree.connection_ids); - result.contact_ids.extend(worktree.contact_ids); + result.contact_ids.extend(worktree.authorized_user_ids); } } @@ -211,9 +211,9 @@ impl Store { pub fn add_worktree(&mut self, worktree: Worktree) -> u64 { let worktree_id = self.next_worktree_id; - for contact_user_id in &worktree.contact_user_ids { + for authorized_user_id in &worktree.authorized_user_ids { self.visible_worktrees_by_user_id - .entry(*contact_user_id) + .entry(*authorized_user_id) .or_default() .insert(worktree_id); } @@ -255,9 +255,10 @@ impl Store { } } - for contact_user_id in &worktree.contact_user_ids { - if let Some(visible_worktrees) = - self.visible_worktrees_by_user_id.get_mut(&contact_user_id) + for authorized_user_id in &worktree.authorized_user_ids { + if let Some(visible_worktrees) = self + .visible_worktrees_by_user_id + .get_mut(&authorized_user_id) { visible_worktrees.remove(&worktree_id); } @@ -282,7 +283,7 @@ impl Store { active_replica_ids: Default::default(), entries, }); - return Some(worktree.contact_user_ids.clone()); + return Some(worktree.authorized_user_ids.clone()); } } None @@ -304,7 +305,7 @@ impl Store { } let connection_ids = worktree.connection_ids(); - let contact_ids = worktree.contact_user_ids.clone(); + let authorized_user_ids = worktree.authorized_user_ids.clone(); if let Some(share) = worktree.share.take() { for connection_id in share.guests.into_keys() { if let Some(connection) = self.connections.get_mut(&connection_id) { @@ -317,7 +318,7 @@ impl Store { Ok(UnsharedWorktree { connection_ids, - contact_ids, + authorized_user_ids, }) } else { Err(anyhow!("worktree is not shared"))? @@ -338,7 +339,7 @@ impl Store { .worktrees .get_mut(&worktree_id) .and_then(|worktree| { - if worktree.contact_user_ids.contains(&user_id) { + if worktree.authorized_user_ids.contains(&user_id) { Some(worktree) } else { None @@ -380,14 +381,14 @@ impl Store { } let connection_ids = worktree.connection_ids(); - let contact_ids = worktree.contact_user_ids.clone(); + let authorized_user_ids = worktree.authorized_user_ids.clone(); #[cfg(test)] self.check_invariants(); Some(LeftWorktree { connection_ids, - contact_ids, + authorized_user_ids, }) } @@ -529,9 +530,11 @@ impl Store { let host_connection = self.connections.get(&worktree.host_connection_id).unwrap(); assert!(host_connection.worktrees.contains(worktree_id)); - for contact_id in &worktree.contact_user_ids { - let visible_worktree_ids = - self.visible_worktrees_by_user_id.get(contact_id).unwrap(); + for authorized_user_ids in &worktree.authorized_user_ids { + let visible_worktree_ids = self + .visible_worktrees_by_user_id + .get(authorized_user_ids) + .unwrap(); assert!(visible_worktree_ids.contains(worktree_id)); } @@ -555,7 +558,7 @@ impl Store { for (user_id, visible_worktree_ids) in &self.visible_worktrees_by_user_id { for worktree_id in visible_worktree_ids { let worktree = self.worktrees.get(worktree_id).unwrap(); - assert!(worktree.contact_user_ids.contains(user_id)); + assert!(worktree.authorized_user_ids.contains(user_id)); } } From d78d5712be50a0dca8ee6a82888ef707f237aaf7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 26 Nov 2021 11:13:05 -0700 Subject: [PATCH 05/13] Rename PeoplePanel to ContactsPanel Yeah, it's true they're people, but this is a more specific way in which they're people. Co-Authored-By: Antonio Scandurra --- Cargo.lock | 24 +++++++++---------- .../Cargo.toml | 2 +- .../src/lib.rs | 24 +++++++++---------- crates/server/src/rpc.rs | 4 ++-- crates/theme/src/lib.rs | 4 ++-- crates/zed/Cargo.toml | 2 +- crates/zed/assets/themes/_base.toml | 22 ++++++++--------- crates/zed/src/lib.rs | 10 ++++---- crates/zed/src/main.rs | 2 +- 9 files changed, 48 insertions(+), 46 deletions(-) rename crates/{people_panel => contacts_panel}/Cargo.toml (91%) rename crates/{people_panel => contacts_panel}/src/lib.rs (96%) diff --git a/Cargo.lock b/Cargo.lock index 5b25f51ff283abd7be231be1b6d539c8a01e95b9..851d49181ab0c292820dc36fb49ee05f8bbad436 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1109,6 +1109,17 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "contacts_panel" +version = "0.1.0" +dependencies = [ + "client", + "gpui", + "postage", + "theme", + "workspace", +] + [[package]] name = "cookie" version = "0.14.4" @@ -3197,17 +3208,6 @@ dependencies = [ "regex", ] -[[package]] -name = "people_panel" -version = "0.1.0" -dependencies = [ - "client", - "gpui", - "postage", - "theme", - "workspace", -] - [[package]] name = "percent-encoding" version = "2.1.0" @@ -5673,6 +5673,7 @@ dependencies = [ "chat_panel", "client", "clock", + "contacts_panel", "crossbeam-channel", "ctor", "dirs", @@ -5697,7 +5698,6 @@ dependencies = [ "lsp", "num_cpus", "parking_lot", - "people_panel", "postage", "project", "project_panel", diff --git a/crates/people_panel/Cargo.toml b/crates/contacts_panel/Cargo.toml similarity index 91% rename from crates/people_panel/Cargo.toml rename to crates/contacts_panel/Cargo.toml index dd8e2114be9e686a51722f53773b95586c0ee4c2..ffeb8e6a3d426b40e2b969504984c4ffbac24a8d 100644 --- a/crates/people_panel/Cargo.toml +++ b/crates/contacts_panel/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "people_panel" +name = "contacts_panel" version = "0.1.0" edition = "2018" diff --git a/crates/people_panel/src/lib.rs b/crates/contacts_panel/src/lib.rs similarity index 96% rename from crates/people_panel/src/lib.rs rename to crates/contacts_panel/src/lib.rs index 00e87dc7e6de3094f1abec77efd016ceeb0d9e5b..8cf8b9191b6f478dd2d96fe4276af7208003fab4 100644 --- a/crates/people_panel/src/lib.rs +++ b/crates/contacts_panel/src/lib.rs @@ -17,20 +17,20 @@ action!(ShareWorktree, u64); action!(UnshareWorktree, u64); pub fn init(cx: &mut MutableAppContext) { - cx.add_action(PeoplePanel::share_worktree); - cx.add_action(PeoplePanel::unshare_worktree); - cx.add_action(PeoplePanel::join_worktree); - cx.add_action(PeoplePanel::leave_worktree); + cx.add_action(ContactsPanel::share_worktree); + cx.add_action(ContactsPanel::unshare_worktree); + cx.add_action(ContactsPanel::join_worktree); + cx.add_action(ContactsPanel::leave_worktree); } -pub struct PeoplePanel { +pub struct ContactsPanel { contacts: ListState, user_store: ModelHandle, settings: watch::Receiver, _maintain_contacts: Subscription, } -impl PeoplePanel { +impl ContactsPanel { pub fn new( user_store: ModelHandle, settings: watch::Receiver, @@ -115,7 +115,7 @@ impl PeoplePanel { theme: &Theme, cx: &mut LayoutContext, ) -> ElementBox { - let theme = &theme.people_panel; + let theme = &theme.contacts_panel; let worktree_count = collaborator.worktrees.len(); let font_cache = cx.font_cache(); let line_height = theme.unshared_worktree.name.text.line_height(font_cache); @@ -216,7 +216,7 @@ impl PeoplePanel { .any(|guest| Some(guest.id) == current_user_id); let is_shared = worktree.is_shared; - MouseEventHandler::new::( + MouseEventHandler::new::( worktree_id as usize, cx, |mouse_state, _| { @@ -294,17 +294,17 @@ impl PeoplePanel { pub enum Event {} -impl Entity for PeoplePanel { +impl Entity for ContactsPanel { type Event = Event; } -impl View for PeoplePanel { +impl View for ContactsPanel { fn ui_name() -> &'static str { - "PeoplePanel" + "ContactsPanel" } fn render(&mut self, _: &mut RenderContext) -> ElementBox { - let theme = &self.settings.borrow().theme.people_panel; + let theme = &self.settings.borrow().theme.contacts_panel; Container::new(List::new(self.contacts.clone()).boxed()) .with_style(theme.container) .boxed() diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index a66e1cf386e944f4b705452edbfd4978752657f6..40347893087545fea30dd7daae9662b22865ec66 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -940,6 +940,7 @@ mod tests { self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Credentials, EstablishConnectionError, UserStore, }, + contacts_panel::JoinWorktree, editor::{Editor, EditorSettings, Input}, fs::{FakeFs, Fs as _}, language::{ @@ -947,7 +948,6 @@ mod tests { LanguageServerConfig, Point, }, lsp, - people_panel::JoinWorktree, project::{ProjectPath, Worktree}, test::test_app_state, workspace::Workspace, @@ -1060,7 +1060,7 @@ mod tests { #[gpui::test] async fn test_unshare_worktree(mut cx_a: TestAppContext, mut cx_b: TestAppContext) { - cx_b.update(zed::people_panel::init); + cx_b.update(zed::contacts_panel::init); let mut app_state_a = cx_a.update(test_app_state); let mut app_state_b = cx_b.update(test_app_state); diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index e280b224cc9fc968dc0d49e5a976404c0fa04067..1a484c6db7720510e094fd33eb986251fd1b64a4 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -20,7 +20,7 @@ pub struct Theme { pub name: String, pub workspace: Workspace, pub chat_panel: ChatPanel, - pub people_panel: PeoplePanel, + pub contacts_panel: ContactsPanel, pub project_panel: ProjectPanel, pub selector: Selector, pub editor: EditorStyle, @@ -137,7 +137,7 @@ pub struct ProjectPanelEntry { } #[derive(Deserialize, Default)] -pub struct PeoplePanel { +pub struct ContactsPanel { #[serde(flatten)] pub container: ContainerStyle, pub host_row_height: f32, diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 8e4441b594d150122a07f573c3f1fb016694a794..df274017115a8e5c48b91f8abaa47f9196c00832 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -40,7 +40,7 @@ go_to_line = { path = "../go_to_line" } gpui = { path = "../gpui" } language = { path = "../language" } lsp = { path = "../lsp" } -people_panel = { path = "../people_panel" } +contacts_panel = { path = "../contacts_panel" } project = { path = "../project" } project_panel = { path = "../project_panel" } rpc = { path = "../rpc" } diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 94d44154218dc2b75116ebb88d01aa99da8a44b4..dca2e0602898f6aa2cafeff0deea0429b597345b 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -146,7 +146,7 @@ underline = true extends = "$chat_panel.sign_in_prompt" color = "$text.1.color" -[people_panel] +[contacts_panel] extends = "$panel" host_row_height = 28 host_avatar = { corner_radius = 10, width = 20 } @@ -154,30 +154,30 @@ host_username = { extends = "$text.0", padding.left = 8 } tree_branch_width = 1 tree_branch_color = "$surface.2" -[people_panel.worktree] +[contacts_panel.worktree] height = 24 padding = { left = 8 } guest_avatar = { corner_radius = 8, width = 16 } guest_avatar_spacing = 4 -[people_panel.worktree.name] +[contacts_panel.worktree.name] extends = "$text.1" margin = { right = 6 } -[people_panel.unshared_worktree] -extends = "$people_panel.worktree" +[contacts_panel.unshared_worktree] +extends = "$contacts_panel.worktree" -[people_panel.hovered_unshared_worktree] -extends = "$people_panel.unshared_worktree" +[contacts_panel.hovered_unshared_worktree] +extends = "$contacts_panel.unshared_worktree" background = "$state.hover" corner_radius = 6 -[people_panel.shared_worktree] -extends = "$people_panel.worktree" +[contacts_panel.shared_worktree] +extends = "$contacts_panel.worktree" name.color = "$text.0.color" -[people_panel.hovered_shared_worktree] -extends = "$people_panel.shared_worktree" +[contacts_panel.hovered_shared_worktree] +extends = "$contacts_panel.shared_worktree" background = "$state.hover" corner_radius = 6 diff --git a/crates/zed/src/lib.rs b/crates/zed/src/lib.rs index cace44a81605ce23505846475fe44809dc9ce01c..49e5c30120f2dcb2d722955d443e735410ff692f 100644 --- a/crates/zed/src/lib.rs +++ b/crates/zed/src/lib.rs @@ -7,6 +7,8 @@ pub mod test; use self::language::LanguageRegistry; use chat_panel::ChatPanel; pub use client; +pub use contacts_panel; +use contacts_panel::ContactsPanel; pub use editor; use gpui::{ action, @@ -17,8 +19,6 @@ use gpui::{ }; pub use lsp; use parking_lot::Mutex; -pub use people_panel; -use people_panel::PeoplePanel; use postage::watch; pub use project::{self, fs}; use project_panel::ProjectPanel; @@ -144,8 +144,10 @@ fn build_workspace(params: &WorkspaceParams, cx: &mut ViewContext) -> ); workspace.right_sidebar_mut().add_item( "icons/user-16.svg", - cx.add_view(|cx| PeoplePanel::new(params.user_store.clone(), params.settings.clone(), cx)) - .into(), + cx.add_view(|cx| { + ContactsPanel::new(params.user_store.clone(), params.settings.clone(), cx) + }) + .into(), ); workspace.right_sidebar_mut().add_item( "icons/comment-16.svg", diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index b28066717cecdfe51e65682f39a94700081cb14b..13a3ccf643e2b43843f3d6952413afbbc63523c8 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -40,7 +40,7 @@ fn main() { editor::init(cx, &mut entry_openers); go_to_line::init(cx); file_finder::init(cx); - people_panel::init(cx); + contacts_panel::init(cx); chat_panel::init(cx); project_panel::init(cx); From 21aba54dc3a6c05b3ff9d77fcee1cb04b9661c4e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 26 Nov 2021 11:28:30 -0700 Subject: [PATCH 06/13] Introduce a worktree::Collaborator struct that holds the user_id We can use this to render avatars. Co-Authored-By: Max Brunsfeld Co-Authored-By: Antonio Scandurra --- crates/project/src/worktree.rs | 64 +++++++++++++++++++++++++--------- crates/server/src/rpc.rs | 14 ++++---- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 79c235fc75e8a69503cbdd0f11683a7df9674dd6..08310f2fa2e3eb7029dc435dbbed81df0e7819c6 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -63,6 +63,12 @@ pub enum Event { Closed, } +pub struct Collaborator { + pub user_id: u64, + pub peer_id: PeerId, + pub replica_id: ReplicaId, +} + impl Entity for Worktree { type Event = Event; @@ -259,9 +265,19 @@ impl Worktree { updates_tx, client: rpc.clone(), open_buffers: Default::default(), - peers: peers + collaborators: peers .into_iter() - .map(|p| (PeerId(p.peer_id), p.replica_id as ReplicaId)) + .map(|p| { + let peer_id = PeerId(p.peer_id); + ( + peer_id, + Collaborator { + peer_id, + user_id: p.user_id, + replica_id: p.replica_id as ReplicaId, + }, + ) + }) .collect(), queued_operations: Default::default(), languages, @@ -390,10 +406,10 @@ impl Worktree { .close_remote_buffer(envelope, cx) } - pub fn peers(&self) -> &HashMap { + pub fn collaborators(&self) -> &HashMap { match self { - Worktree::Local(worktree) => &worktree.peers, - Worktree::Remote(worktree) => &worktree.peers, + Worktree::Local(worktree) => &worktree.collaborators, + Worktree::Remote(worktree) => &worktree.collaborators, } } @@ -772,7 +788,7 @@ pub struct LocalWorktree { open_buffers: HashMap>, shared_buffers: HashMap>>, diagnostics: HashMap>, - peers: HashMap, + collaborators: HashMap, queued_operations: Vec<(u64, Operation)>, languages: Arc, rpc: Arc, @@ -887,7 +903,7 @@ impl LocalWorktree { shared_buffers: Default::default(), diagnostics: Default::default(), queued_operations: Default::default(), - peers: Default::default(), + collaborators: Default::default(), languages, rpc, fs, @@ -1085,8 +1101,15 @@ impl LocalWorktree { .peer .as_ref() .ok_or_else(|| anyhow!("empty peer"))?; - self.peers - .insert(PeerId(peer.peer_id), peer.replica_id as ReplicaId); + let peer_id = PeerId(peer.peer_id); + self.collaborators.insert( + peer_id, + Collaborator { + peer_id, + user_id: peer.user_id, + replica_id: peer.replica_id as ReplicaId, + }, + ); cx.notify(); Ok(()) @@ -1099,9 +1122,10 @@ impl LocalWorktree { ) -> Result<()> { let peer_id = PeerId(envelope.payload.peer_id); let replica_id = self - .peers + .collaborators .remove(&peer_id) - .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?; + .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))? + .replica_id; self.shared_buffers.remove(&peer_id); for (_, buffer) in &self.open_buffers { if let Some(buffer) = buffer.upgrade(cx) { @@ -1373,7 +1397,7 @@ pub struct RemoteWorktree { updates_tx: postage::mpsc::Sender, replica_id: ReplicaId, open_buffers: HashMap, - peers: HashMap, + collaborators: HashMap, languages: Arc, queued_operations: Vec<(u64, Operation)>, _subscriptions: Vec, @@ -1501,8 +1525,15 @@ impl RemoteWorktree { .peer .as_ref() .ok_or_else(|| anyhow!("empty peer"))?; - self.peers - .insert(PeerId(peer.peer_id), peer.replica_id as ReplicaId); + let peer_id = PeerId(peer.peer_id); + self.collaborators.insert( + peer_id, + Collaborator { + peer_id, + user_id: peer.user_id, + replica_id: peer.replica_id as ReplicaId, + }, + ); cx.notify(); Ok(()) } @@ -1514,9 +1545,10 @@ impl RemoteWorktree { ) -> Result<()> { let peer_id = PeerId(envelope.payload.peer_id); let replica_id = self - .peers + .collaborators .remove(&peer_id) - .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?; + .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))? + .replica_id; for (_, buffer) in &self.open_buffers { if let Some(buffer) = buffer.upgrade(cx) { buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx)); diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 40347893087545fea30dd7daae9662b22865ec66..9aa65bd8a17e3fbf06b4beaa4b50e7a67fc64d6a 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1005,9 +1005,9 @@ mod tests { let replica_id_b = worktree_b.read_with(&cx_b, |tree, _| tree.replica_id()); worktree_a .condition(&cx_a, |tree, _| { - tree.peers() + tree.collaborators() .values() - .any(|replica_id| *replica_id == replica_id_b) + .any(|collaborator| collaborator.replica_id == replica_id_b) }) .await; @@ -1054,7 +1054,7 @@ mod tests { // Dropping the worktree removes client B from client A's peers. cx_b.update(move |_| drop(worktree_b)); worktree_a - .condition(&cx_a, |tree, _| tree.peers().is_empty()) + .condition(&cx_a, |tree, _| tree.collaborators().is_empty()) .await; } @@ -1492,7 +1492,7 @@ mod tests { .await .unwrap(); worktree_a - .condition(&cx_a, |tree, _| tree.peers().len() == 1) + .condition(&cx_a, |tree, _| tree.collaborators().len() == 1) .await; let buffer_b = cx_b @@ -1501,7 +1501,7 @@ mod tests { cx_b.update(|_| drop(worktree_b)); drop(buffer_b); worktree_a - .condition(&cx_a, |tree, _| tree.peers().len() == 0) + .condition(&cx_a, |tree, _| tree.collaborators().len() == 0) .await; } @@ -1553,13 +1553,13 @@ mod tests { .await .unwrap(); worktree_a - .condition(&cx_a, |tree, _| tree.peers().len() == 1) + .condition(&cx_a, |tree, _| tree.collaborators().len() == 1) .await; // Drop client B's connection and ensure client A observes client B leaving the worktree. client_b.disconnect(&cx_b.to_async()).await.unwrap(); worktree_a - .condition(&cx_a, |tree, _| tree.peers().len() == 0) + .condition(&cx_a, |tree, _| tree.collaborators().len() == 0) .await; } From 9930e924121d51c5d704943d1713f19ce46e3e60 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 26 Nov 2021 19:12:12 -0700 Subject: [PATCH 07/13] WIP: Give worktrees a reference to the UserStore This will allow them to fetch user data when peers are added or removed. Still work to do though. --- crates/client/src/test.rs | 4 + crates/client/src/user.rs | 18 ++-- crates/project/src/lib.rs | 30 ++++-- crates/project/src/worktree.rs | 160 ++++++++++++++++++++++---------- crates/project_panel/src/lib.rs | 1 + crates/server/src/rpc.rs | 25 +++-- crates/workspace/src/lib.rs | 1 + 7 files changed, 166 insertions(+), 73 deletions(-) diff --git a/crates/client/src/test.rs b/crates/client/src/test.rs index e471398b53178c9bbac94ebebabf21781278e422..b91a943002b69747bbe6455e5561c33554e1744f 100644 --- a/crates/client/src/test.rs +++ b/crates/client/src/test.rs @@ -172,6 +172,10 @@ impl FakeHttpClient { handler: Box::new(move |req| Box::pin(handler(req))), }) } + + pub fn with_404_response() -> Arc { + Self::new(|_| async move { Ok(ServerResponse::new(404)) }) + } } impl fmt::Debug for FakeHttpClient { diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 7ba447883461e7ceffc656f233ee488fdc374090..0a387487e1b064e3b387a634dfbbd62a13789e6f 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -37,7 +37,7 @@ pub struct UserStore { users: HashMap>, current_user: watch::Receiver>>, contacts: Arc<[Contact]>, - rpc: Arc, + client: Arc, http: Arc, _maintain_contacts: Task<()>, _maintain_current_user: Task<()>, @@ -50,11 +50,15 @@ impl Entity for UserStore { } impl UserStore { - pub fn new(rpc: Arc, http: Arc, cx: &mut ModelContext) -> Self { + pub fn new( + client: Arc, + http: Arc, + cx: &mut ModelContext, + ) -> Self { let (mut current_user_tx, current_user_rx) = watch::channel(); let (mut update_contacts_tx, mut update_contacts_rx) = watch::channel::>(); - let update_contacts_subscription = rpc.subscribe( + let update_contacts_subscription = client.subscribe( cx, move |_: &mut Self, msg: TypedEnvelope, _, _| { let _ = update_contacts_tx.blocking_send(Some(msg.payload)); @@ -65,7 +69,7 @@ impl UserStore { users: Default::default(), current_user: current_user_rx, contacts: Arc::from([]), - rpc: rpc.clone(), + client: client.clone(), http, _maintain_contacts: cx.spawn_weak(|this, mut cx| async move { let _subscription = update_contacts_subscription; @@ -78,11 +82,11 @@ impl UserStore { } }), _maintain_current_user: cx.spawn_weak(|this, mut cx| async move { - let mut status = rpc.status(); + let mut status = client.status(); while let Some(status) = status.recv().await { match status { Status::Connected { .. } => { - if let Some((this, user_id)) = this.upgrade(&cx).zip(rpc.user_id()) { + if let Some((this, user_id)) = this.upgrade(&cx).zip(client.user_id()) { let user = this .update(&mut cx, |this, cx| this.fetch_user(user_id, cx)) .log_err() @@ -139,7 +143,7 @@ impl UserStore { mut user_ids: Vec, cx: &mut ModelContext, ) -> Task> { - let rpc = self.rpc.clone(); + let rpc = self.client.clone(); let http = self.http.clone(); user_ids.retain(|id| !self.users.contains_key(id)); cx.spawn_weak(|this, mut cx| async move { diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index d1d48f49ca8679fec8fdaacfe904a1db0ad39537..b200db63dd7e9ea3961981f9d4a6a9fda723b443 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -3,7 +3,7 @@ mod ignore; mod worktree; use anyhow::Result; -use client::Client; +use client::{Client, UserStore}; use futures::Future; use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; @@ -23,6 +23,7 @@ pub struct Project { active_entry: Option, languages: Arc, client: Arc, + user_store: ModelHandle, fs: Arc, } @@ -44,13 +45,19 @@ pub struct ProjectEntry { } impl Project { - pub fn new(languages: Arc, rpc: Arc, fs: Arc) -> Self { + pub fn new( + languages: Arc, + client: Arc, + user_store: ModelHandle, + fs: Arc, + ) -> Self { Self { worktrees: Default::default(), active_worktree: None, active_entry: None, languages, - client: rpc, + client, + user_store, fs, } } @@ -72,11 +79,13 @@ impl Project { cx: &mut ModelContext, ) -> Task>> { let fs = self.fs.clone(); - let rpc = self.client.clone(); + let client = self.client.clone(); + let user_store = self.user_store.clone(); let languages = self.languages.clone(); let path = Arc::from(abs_path); cx.spawn(|this, mut cx| async move { - let worktree = Worktree::open_local(rpc, path, fs, languages, &mut cx).await?; + let worktree = + Worktree::open_local(client, user_store, path, fs, languages, &mut cx).await?; this.update(&mut cx, |this, cx| { this.add_worktree(worktree.clone(), cx); }); @@ -91,10 +100,12 @@ impl Project { ) -> Task>> { let rpc = self.client.clone(); let languages = self.languages.clone(); + let user_store = self.user_store.clone(); cx.spawn(|this, mut cx| async move { rpc.authenticate_and_connect(&cx).await?; let worktree = - Worktree::open_remote(rpc.clone(), remote_id, languages, &mut cx).await?; + Worktree::open_remote(rpc.clone(), remote_id, languages, user_store, &mut cx) + .await?; this.update(&mut cx, |this, cx| { cx.subscribe(&worktree, move |this, _, event, cx| match event { worktree::Event::Closed => { @@ -329,6 +340,7 @@ impl Entity for Project { #[cfg(test)] mod tests { use super::*; + use client::{http::ServerResponse, test::FakeHttpClient}; use fs::RealFs; use gpui::TestAppContext; use language::LanguageRegistry; @@ -434,7 +446,9 @@ mod tests { fn build_project(cx: &mut TestAppContext) -> ModelHandle { let languages = Arc::new(LanguageRegistry::new()); let fs = Arc::new(RealFs); - let rpc = client::Client::new(); - cx.add_model(|_| Project::new(languages, rpc, fs)) + let client = client::Client::new(); + let http_client = FakeHttpClient::new(|_| async move { Ok(ServerResponse::new(404)) }); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + cx.add_model(|_| Project::new(languages, client, user_store, fs)) } } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 08310f2fa2e3eb7029dc435dbbed81df0e7819c6..f21abf06de115f1d4df68655c115a5809d4fc3f4 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -4,7 +4,7 @@ use super::{ }; use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; use anyhow::{anyhow, Context, Result}; -use client::{proto, Client, PeerId, TypedEnvelope}; +use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; use clock::ReplicaId; use futures::{Stream, StreamExt}; use fuzzy::CharBag; @@ -64,7 +64,7 @@ pub enum Event { } pub struct Collaborator { - pub user_id: u64, + pub user: Arc, pub peer_id: PeerId, pub replica_id: ReplicaId, } @@ -76,7 +76,7 @@ impl Entity for Worktree { match self { Self::Local(tree) => { if let Some(worktree_id) = *tree.remote_id.borrow() { - let rpc = tree.rpc.clone(); + let rpc = tree.client.clone(); cx.spawn(|_| async move { if let Err(err) = rpc.send(proto::CloseWorktree { worktree_id }).await { log::error!("error closing worktree: {}", err); @@ -124,14 +124,15 @@ impl Entity for Worktree { impl Worktree { pub async fn open_local( - rpc: Arc, + client: Arc, + user_store: ModelHandle, path: impl Into>, fs: Arc, languages: Arc, cx: &mut AsyncAppContext, ) -> Result> { let (tree, scan_states_tx) = - LocalWorktree::new(rpc, path, fs.clone(), languages, cx).await?; + LocalWorktree::new(client, user_store, path, fs.clone(), languages, cx).await?; tree.update(cx, |tree, cx| { let tree = tree.as_local_mut().unwrap(); let abs_path = tree.snapshot.abs_path.clone(); @@ -148,18 +149,22 @@ impl Worktree { } pub async fn open_remote( - rpc: Arc, + client: Arc, id: u64, languages: Arc, + user_store: ModelHandle, cx: &mut AsyncAppContext, ) -> Result> { - let response = rpc.request(proto::JoinWorktree { worktree_id: id }).await?; - Worktree::remote(response, rpc, languages, cx).await + let response = client + .request(proto::JoinWorktree { worktree_id: id }) + .await?; + Worktree::remote(response, client, user_store, languages, cx).await } async fn remote( join_response: proto::JoinWorktreeResponse, - rpc: Arc, + client: Arc, + user_store: ModelHandle, languages: Arc, cx: &mut AsyncAppContext, ) -> Result> { @@ -204,6 +209,26 @@ impl Worktree { }) .await; + let user_ids = peers.iter().map(|peer| peer.user_id).collect(); + user_store + .update(cx, |user_store, cx| user_store.load_users(user_ids, cx)) + .await?; + let mut collaborators = HashMap::with_capacity(peers.len()); + for peer in &peers { + let peer_id = PeerId(peer.peer_id); + let user = user_store + .update(cx, |user_store, cx| user_store.fetch_user(peer.user_id, cx)) + .await?; + collaborators.insert( + peer_id, + Collaborator { + peer_id, + user, + replica_id: peer.replica_id as ReplicaId, + }, + ); + } + let worktree = cx.update(|cx| { cx.add_model(|cx: &mut ModelContext| { let snapshot = Snapshot { @@ -249,12 +274,12 @@ impl Worktree { } let _subscriptions = vec![ - rpc.subscribe_to_entity(remote_id, cx, Self::handle_add_peer), - rpc.subscribe_to_entity(remote_id, cx, Self::handle_remove_peer), - rpc.subscribe_to_entity(remote_id, cx, Self::handle_update), - rpc.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer), - rpc.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved), - rpc.subscribe_to_entity(remote_id, cx, Self::handle_unshare), + client.subscribe_to_entity(remote_id, cx, Self::handle_add_peer), + client.subscribe_to_entity(remote_id, cx, Self::handle_remove_peer), + client.subscribe_to_entity(remote_id, cx, Self::handle_update), + client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer), + client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved), + client.subscribe_to_entity(remote_id, cx, Self::handle_unshare), ]; Worktree::Remote(RemoteWorktree { @@ -263,24 +288,12 @@ impl Worktree { snapshot, snapshot_rx, updates_tx, - client: rpc.clone(), + client: client.clone(), open_buffers: Default::default(), - collaborators: peers - .into_iter() - .map(|p| { - let peer_id = PeerId(p.peer_id); - ( - peer_id, - Collaborator { - peer_id, - user_id: p.user_id, - replica_id: p.replica_id as ReplicaId, - }, - ) - }) - .collect(), + collaborators, queued_operations: Default::default(), languages, + user_store, _subscriptions, }) }) @@ -737,7 +750,7 @@ impl Worktree { Worktree::Local(worktree) => worktree .remote_id .borrow() - .map(|id| (worktree.rpc.clone(), id)), + .map(|id| (worktree.client.clone(), id)), Worktree::Remote(worktree) => Some((worktree.client.clone(), worktree.remote_id)), } { cx.spawn(|worktree, mut cx| async move { @@ -791,7 +804,8 @@ pub struct LocalWorktree { collaborators: HashMap, queued_operations: Vec<(u64, Operation)>, languages: Arc, - rpc: Arc, + client: Arc, + user_store: ModelHandle, fs: Arc, language_servers: HashMap>, } @@ -803,7 +817,8 @@ struct WorktreeConfig { impl LocalWorktree { async fn new( - rpc: Arc, + client: Arc, + user_store: ModelHandle, path: impl Into>, fs: Arc, languages: Arc, @@ -857,7 +872,7 @@ impl LocalWorktree { let (mut remote_id_tx, remote_id_rx) = watch::channel(); let _maintain_remote_id_task = cx.spawn_weak({ - let rpc = rpc.clone(); + let rpc = client.clone(); move |this, cx| { async move { let mut status = rpc.status(); @@ -905,7 +920,8 @@ impl LocalWorktree { queued_operations: Default::default(), collaborators: Default::default(), languages, - rpc, + client, + user_store, fs, language_servers: Default::default(), }; @@ -1106,7 +1122,7 @@ impl LocalWorktree { peer_id, Collaborator { peer_id, - user_id: peer.user_id, + user: todo!(), replica_id: peer.replica_id as ReplicaId, }, ); @@ -1267,7 +1283,7 @@ impl LocalWorktree { pub fn share(&mut self, cx: &mut ModelContext) -> Task> { let snapshot = self.snapshot(); let share_request = self.share_request(cx); - let rpc = self.rpc.clone(); + let rpc = self.client.clone(); cx.spawn(|this, mut cx| async move { let share_request = if let Some(request) = share_request.await { request @@ -1321,7 +1337,7 @@ impl LocalWorktree { pub fn unshare(&mut self, cx: &mut ModelContext) { self.share.take(); - let rpc = self.rpc.clone(); + let rpc = self.client.clone(); let remote_id = self.remote_id(); cx.foreground() .spawn( @@ -1399,6 +1415,7 @@ pub struct RemoteWorktree { open_buffers: HashMap, collaborators: HashMap, languages: Arc, + user_store: ModelHandle, queued_operations: Vec<(u64, Operation)>, _subscriptions: Vec, } @@ -1530,7 +1547,7 @@ impl RemoteWorktree { peer_id, Collaborator { peer_id, - user_id: peer.user_id, + user: todo!(), replica_id: peer.replica_id as ReplicaId, }, ); @@ -2009,7 +2026,7 @@ impl language::File for File { ) -> Task> { self.worktree.update(cx, |worktree, cx| match worktree { Worktree::Local(worktree) => { - let rpc = worktree.rpc.clone(); + let rpc = worktree.client.clone(); let worktree_id = *worktree.remote_id.borrow(); let save = worktree.save(self.path.clone(), text, cx); cx.background().spawn(async move { @@ -2976,7 +2993,7 @@ mod tests { use crate::fs::FakeFs; use anyhow::Result; use buffer::Point; - use client::test::FakeServer; + use client::test::{FakeHttpClient, FakeServer}; use fs::RealFs; use language::{tree_sitter_rust, LanguageServerConfig}; use language::{Diagnostic, LanguageConfig}; @@ -2992,7 +3009,7 @@ mod tests { use util::test::temp_tree; #[gpui::test] - async fn test_traversal(cx: gpui::TestAppContext) { + async fn test_traversal(mut cx: gpui::TestAppContext) { let fs = FakeFs::new(); fs.insert_tree( "/root", @@ -3006,8 +3023,13 @@ mod tests { ) .await; + let client = Client::new(); + let http_client = FakeHttpClient::with_404_response(); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + let tree = Worktree::open_local( - Client::new(), + client, + user_store, Arc::from(Path::new("/root")), Arc::new(fs), Default::default(), @@ -3038,8 +3060,14 @@ mod tests { let dir = temp_tree(json!({ "file1": "the old contents", })); + + let client = Client::new(); + let http_client = FakeHttpClient::with_404_response(); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + let tree = Worktree::open_local( - Client::new(), + client, + user_store, dir.path(), Arc::new(RealFs), Default::default(), @@ -3068,8 +3096,13 @@ mod tests { })); let file_path = dir.path().join("file1"); + let client = Client::new(); + let http_client = FakeHttpClient::with_404_response(); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + let tree = Worktree::open_local( - Client::new(), + client, + user_store, file_path.clone(), Arc::new(RealFs), Default::default(), @@ -3113,9 +3146,12 @@ mod tests { let user_id = 5; let mut client = Client::new(); + let http_client = FakeHttpClient::with_404_response(); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let server = FakeServer::for_client(user_id, &mut client, &cx).await; let tree = Worktree::open_local( client, + user_store.clone(), dir.path(), Arc::new(RealFs), Default::default(), @@ -3170,6 +3206,7 @@ mod tests { peers: Vec::new(), }, Client::new(), + user_store, Default::default(), &mut cx.to_async(), ) @@ -3262,7 +3299,7 @@ mod tests { } #[gpui::test] - async fn test_rescan_with_gitignore(cx: gpui::TestAppContext) { + async fn test_rescan_with_gitignore(mut cx: gpui::TestAppContext) { let dir = temp_tree(json!({ ".git": {}, ".gitignore": "ignored-dir\n", @@ -3274,8 +3311,13 @@ mod tests { } })); + let client = Client::new(); + let http_client = FakeHttpClient::with_404_response(); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + let tree = Worktree::open_local( - Client::new(), + client, + user_store, dir.path(), Arc::new(RealFs), Default::default(), @@ -3313,6 +3355,8 @@ mod tests { let user_id = 100; let mut client = Client::new(); let server = FakeServer::for_client(user_id, &mut client, &cx).await; + let http_client = FakeHttpClient::with_404_response(); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let fs = Arc::new(FakeFs::new()); fs.insert_tree( @@ -3330,6 +3374,7 @@ mod tests { let worktree = Worktree::open_local( client.clone(), + user_store, "/path/to/the-dir".as_ref(), fs, Default::default(), @@ -3376,8 +3421,13 @@ mod tests { "file2": "def", "file3": "ghi", })); + let client = Client::new(); + let http_client = FakeHttpClient::with_404_response(); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + let tree = Worktree::open_local( - Client::new(), + client, + user_store, dir.path(), Arc::new(RealFs), Default::default(), @@ -3509,8 +3559,13 @@ mod tests { let initial_contents = "aaa\nbbbbb\nc\n"; let dir = temp_tree(json!({ "the-file": initial_contents })); + let client = Client::new(); + let http_client = FakeHttpClient::with_404_response(); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + let tree = Worktree::open_local( - Client::new(), + client, + user_store, dir.path(), Arc::new(RealFs), Default::default(), @@ -3619,8 +3674,13 @@ mod tests { "b.rs": "const y: i32 = 1", })); + let client = Client::new(); + let http_client = FakeHttpClient::with_404_response(); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + let tree = Worktree::open_local( - Client::new(), + client, + user_store, dir.path(), Arc::new(RealFs), Arc::new(languages), diff --git a/crates/project_panel/src/lib.rs b/crates/project_panel/src/lib.rs index 385b7dbca2de231d2e9417483da055df442ac2a5..4d05ed317a8e5fd0091f1f5b936ecbbcf18e9255 100644 --- a/crates/project_panel/src/lib.rs +++ b/crates/project_panel/src/lib.rs @@ -621,6 +621,7 @@ mod tests { Project::new( params.languages.clone(), params.client.clone(), + params.user_store.clone(), params.fs.clone(), ) }); diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 9aa65bd8a17e3fbf06b4beaa4b50e7a67fc64d6a..9f4c7247707328e90b0430d8f8ab4c79d573d188 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -961,7 +961,7 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, _) = server.create_client(&mut cx_b, "user_b").await; + let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; cx_a.foreground().forbid_parking(); @@ -998,6 +998,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + user_store_b, &mut cx_b.to_async(), ) .await @@ -1163,8 +1164,8 @@ mod tests { // Connect to a server as 3 clients. let mut server = TestServer::start().await; let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, _) = server.create_client(&mut cx_b, "user_b").await; - let (client_c, _) = server.create_client(&mut cx_c, "user_c").await; + let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; + let (client_c, user_store_c) = server.create_client(&mut cx_c, "user_c").await; let fs = Arc::new(FakeFs::new()); @@ -1201,6 +1202,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + user_store_b, &mut cx_b.to_async(), ) .await @@ -1209,6 +1211,7 @@ mod tests { client_c.clone(), worktree_id, lang_registry.clone(), + user_store_c, &mut cx_c.to_async(), ) .await @@ -1302,7 +1305,7 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, _) = server.create_client(&mut cx_b, "user_b").await; + let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A let fs = Arc::new(FakeFs::new()); @@ -1337,6 +1340,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + user_store_b, &mut cx_b.to_async(), ) .await @@ -1387,7 +1391,7 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, _) = server.create_client(&mut cx_b, "user_b").await; + let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A let fs = Arc::new(FakeFs::new()); @@ -1421,6 +1425,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + user_store_b, &mut cx_b.to_async(), ) .await @@ -1453,7 +1458,7 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, _) = server.create_client(&mut cx_b, "user_b").await; + let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A let fs = Arc::new(FakeFs::new()); @@ -1487,6 +1492,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + user_store_b, &mut cx_b.to_async(), ) .await @@ -1513,7 +1519,7 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, _) = server.create_client(&mut cx_a, "user_b").await; + let (client_b, user_store_b) = server.create_client(&mut cx_a, "user_b").await; // Share a local worktree as client A let fs = Arc::new(FakeFs::new()); @@ -1548,6 +1554,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + user_store_b, &mut cx_b.to_async(), ) .await @@ -1587,7 +1594,7 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, _) = server.create_client(&mut cx_a, "user_b").await; + let (client_b, user_store_b) = server.create_client(&mut cx_a, "user_b").await; // Share a local worktree as client A let fs = Arc::new(FakeFs::new()); @@ -1656,6 +1663,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + user_store_b, &mut cx_b.to_async(), ) .await @@ -2165,6 +2173,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + user_store_b.clone(), &mut cx_b.to_async(), ) .await diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 5901d694d30d4d5862ddf73b67f00b4aec7c3f82..ea75277b9cbbf2af29511f1e3547366498b881a3 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -348,6 +348,7 @@ impl Workspace { Project::new( params.languages.clone(), params.client.clone(), + params.user_store.clone(), params.fs.clone(), ) }); From b307a7e91d56326af64b22106b73572c3d2874e8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 26 Nov 2021 20:35:50 -0700 Subject: [PATCH 08/13] Populate the user data of worktree collaborators This will make it possible for us to render their avatars. Previously we only had the user ids. During rendering, everything needs to be available synchronously. So now, whenever collaborators are added, we perform the async I/O to fetch their user data prior to adding them to the worktree. --- crates/client/src/test.rs | 20 +++- crates/project/src/worktree.rs | 166 +++++++++++++++++---------------- crates/rpc/proto/zed.proto | 14 +-- crates/rpc/src/proto.rs | 8 +- crates/server/src/rpc.rs | 47 ++++++---- 5 files changed, 143 insertions(+), 112 deletions(-) diff --git a/crates/client/src/test.rs b/crates/client/src/test.rs index b91a943002b69747bbe6455e5561c33554e1744f..a40d1ee3a776a93a91e513abc6f41bb78b0a0097 100644 --- a/crates/client/src/test.rs +++ b/crates/client/src/test.rs @@ -2,7 +2,7 @@ use super::Client; use super::*; use crate::http::{HttpClient, Request, Response, ServerResponse}; use futures::{future::BoxFuture, Future}; -use gpui::TestAppContext; +use gpui::{ModelHandle, TestAppContext}; use parking_lot::Mutex; use postage::{mpsc, prelude::Stream}; use rpc::{proto, ConnectionId, Peer, Receipt, TypedEnvelope}; @@ -155,6 +155,24 @@ impl FakeServer { fn connection_id(&self) -> ConnectionId { self.connection_id.lock().expect("not connected") } + + pub async fn build_user_store( + &self, + client: Arc, + cx: &mut TestAppContext, + ) -> ModelHandle { + let http_client = FakeHttpClient::with_404_response(); + let user_store = cx.add_model(|cx| UserStore::new(client, http_client, cx)); + assert_eq!( + self.receive::() + .await + .unwrap() + .payload + .user_ids, + &[self.user_id] + ); + user_store + } } pub struct FakeHttpClient { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index f21abf06de115f1d4df68655c115a5809d4fc3f4..0fec2d42097d0d779cbb7e7299a212cb7534f289 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -69,6 +69,26 @@ pub struct Collaborator { pub replica_id: ReplicaId, } +impl Collaborator { + fn from_proto( + message: proto::Collaborator, + user_store: &ModelHandle, + cx: &mut AsyncAppContext, + ) -> impl Future> { + let user = user_store.update(cx, |user_store, cx| { + user_store.fetch_user(message.user_id, cx) + }); + + async move { + Ok(Self { + peer_id: PeerId(message.peer_id), + user: user.await?, + replica_id: message.replica_id as ReplicaId, + }) + } + } +} + impl Entity for Worktree { type Event = Event; @@ -174,7 +194,6 @@ impl Worktree { let remote_id = worktree.id; let replica_id = join_response.replica_id as ReplicaId; - let peers = join_response.peers; let root_char_bag: CharBag = worktree .root_name .chars() @@ -209,24 +228,18 @@ impl Worktree { }) .await; - let user_ids = peers.iter().map(|peer| peer.user_id).collect(); + let user_ids = join_response + .collaborators + .iter() + .map(|peer| peer.user_id) + .collect(); user_store .update(cx, |user_store, cx| user_store.load_users(user_ids, cx)) .await?; - let mut collaborators = HashMap::with_capacity(peers.len()); - for peer in &peers { - let peer_id = PeerId(peer.peer_id); - let user = user_store - .update(cx, |user_store, cx| user_store.fetch_user(peer.user_id, cx)) - .await?; - collaborators.insert( - peer_id, - Collaborator { - peer_id, - user, - replica_id: peer.replica_id as ReplicaId, - }, - ); + let mut collaborators = HashMap::with_capacity(join_response.collaborators.len()); + for message in join_response.collaborators { + let collaborator = Collaborator::from_proto(message, &user_store, cx).await?; + collaborators.insert(collaborator.peer_id, collaborator); } let worktree = cx.update(|cx| { @@ -274,8 +287,8 @@ impl Worktree { } let _subscriptions = vec![ - client.subscribe_to_entity(remote_id, cx, Self::handle_add_peer), - client.subscribe_to_entity(remote_id, cx, Self::handle_remove_peer), + client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator), + client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator), client.subscribe_to_entity(remote_id, cx, Self::handle_update), client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer), client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved), @@ -347,27 +360,52 @@ impl Worktree { } } - pub fn handle_add_peer( + pub fn user_store(&self) -> &ModelHandle { + match self { + Worktree::Local(worktree) => &worktree.user_store, + Worktree::Remote(worktree) => &worktree.user_store, + } + } + + pub fn handle_add_collaborator( &mut self, - envelope: TypedEnvelope, + mut envelope: TypedEnvelope, _: Arc, cx: &mut ModelContext, ) -> Result<()> { - match self { - Worktree::Local(worktree) => worktree.add_peer(envelope, cx), - Worktree::Remote(worktree) => worktree.add_peer(envelope, cx), - } + let user_store = self.user_store().clone(); + let collaborator = envelope + .payload + .collaborator + .take() + .ok_or_else(|| anyhow!("empty collaborator"))?; + + cx.spawn(|this, mut cx| { + async move { + let collaborator = + Collaborator::from_proto(collaborator, &user_store, &mut cx).await?; + this.update(&mut cx, |this, cx| match this { + Worktree::Local(worktree) => worktree.add_collaborator(collaborator, cx), + Worktree::Remote(worktree) => worktree.add_collaborator(collaborator, cx), + }); + Ok(()) + } + .log_err() + }) + .detach(); + + Ok(()) } - pub fn handle_remove_peer( + pub fn handle_remove_collaborator( &mut self, - envelope: TypedEnvelope, + envelope: TypedEnvelope, _: Arc, cx: &mut ModelContext, ) -> Result<()> { match self { - Worktree::Local(worktree) => worktree.remove_peer(envelope, cx), - Worktree::Remote(worktree) => worktree.remove_peer(envelope, cx), + Worktree::Local(worktree) => worktree.remove_collaborator(envelope, cx), + Worktree::Remote(worktree) => worktree.remove_collaborator(envelope, cx), } } @@ -1107,33 +1145,19 @@ impl LocalWorktree { Ok(()) } - pub fn add_peer( + pub fn add_collaborator( &mut self, - envelope: TypedEnvelope, + collaborator: Collaborator, cx: &mut ModelContext, - ) -> Result<()> { - let peer = envelope - .payload - .peer - .as_ref() - .ok_or_else(|| anyhow!("empty peer"))?; - let peer_id = PeerId(peer.peer_id); - self.collaborators.insert( - peer_id, - Collaborator { - peer_id, - user: todo!(), - replica_id: peer.replica_id as ReplicaId, - }, - ); + ) { + self.collaborators + .insert(collaborator.peer_id, collaborator); cx.notify(); - - Ok(()) } - pub fn remove_peer( + pub fn remove_collaborator( &mut self, - envelope: TypedEnvelope, + envelope: TypedEnvelope, cx: &mut ModelContext, ) -> Result<()> { let peer_id = PeerId(envelope.payload.peer_id); @@ -1316,8 +1340,8 @@ impl LocalWorktree { this.update(&mut cx, |worktree, cx| { let _subscriptions = vec![ - rpc.subscribe_to_entity(remote_id, cx, Worktree::handle_add_peer), - rpc.subscribe_to_entity(remote_id, cx, Worktree::handle_remove_peer), + rpc.subscribe_to_entity(remote_id, cx, Worktree::handle_add_collaborator), + rpc.subscribe_to_entity(remote_id, cx, Worktree::handle_remove_collaborator), rpc.subscribe_to_entity(remote_id, cx, Worktree::handle_open_buffer), rpc.subscribe_to_entity(remote_id, cx, Worktree::handle_close_buffer), rpc.subscribe_to_entity(remote_id, cx, Worktree::handle_update_buffer), @@ -1532,32 +1556,19 @@ impl RemoteWorktree { Ok(()) } - pub fn add_peer( + pub fn add_collaborator( &mut self, - envelope: TypedEnvelope, + collaborator: Collaborator, cx: &mut ModelContext, - ) -> Result<()> { - let peer = envelope - .payload - .peer - .as_ref() - .ok_or_else(|| anyhow!("empty peer"))?; - let peer_id = PeerId(peer.peer_id); - self.collaborators.insert( - peer_id, - Collaborator { - peer_id, - user: todo!(), - replica_id: peer.replica_id as ReplicaId, - }, - ); + ) { + self.collaborators + .insert(collaborator.peer_id, collaborator); cx.notify(); - Ok(()) } - pub fn remove_peer( + pub fn remove_collaborator( &mut self, - envelope: TypedEnvelope, + envelope: TypedEnvelope, cx: &mut ModelContext, ) -> Result<()> { let peer_id = PeerId(envelope.payload.peer_id); @@ -3146,9 +3157,8 @@ mod tests { let user_id = 5; let mut client = Client::new(); - let http_client = FakeHttpClient::with_404_response(); - let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let server = FakeServer::for_client(user_id, &mut client, &cx).await; + let user_store = server.build_user_store(client.clone(), &mut cx).await; let tree = Worktree::open_local( client, user_store.clone(), @@ -3203,7 +3213,7 @@ mod tests { proto::JoinWorktreeResponse { worktree: share_request.await.unwrap().worktree, replica_id: 1, - peers: Vec::new(), + collaborators: Vec::new(), }, Client::new(), user_store, @@ -3355,8 +3365,7 @@ mod tests { let user_id = 100; let mut client = Client::new(); let server = FakeServer::for_client(user_id, &mut client, &cx).await; - let http_client = FakeHttpClient::with_404_response(); - let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + let user_store = server.build_user_store(client.clone(), &mut cx).await; let fs = Arc::new(FakeFs::new()); fs.insert_tree( @@ -3383,11 +3392,6 @@ mod tests { .await .unwrap(); - { - let cx = cx.to_async(); - client.authenticate_and_connect(&cx).await.unwrap(); - } - let open_worktree = server.receive::().await.unwrap(); assert_eq!( open_worktree.payload, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 5b899ae91683e3034fd6d41959eeeaea45490065..775f94d595f8d3aa6f8425fc6ce2b334d46503f8 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -21,8 +21,8 @@ message Envelope { UpdateBuffer update_buffer = 16; SaveBuffer save_buffer = 17; BufferSaved buffer_saved = 18; - AddPeer add_peer = 19; - RemovePeer remove_peer = 20; + AddCollaborator add_collaborator = 19; + RemoveCollaborator remove_collaborator = 20; GetChannels get_channels = 21; GetChannelsResponse get_channels_response = 22; GetUsers get_users = 23; @@ -83,7 +83,7 @@ message LeaveWorktree { message JoinWorktreeResponse { Worktree worktree = 2; uint32 replica_id = 3; - repeated Peer peers = 4; + repeated Collaborator collaborators = 4; } message UpdateWorktree { @@ -96,12 +96,12 @@ message CloseWorktree { uint64 worktree_id = 1; } -message AddPeer { +message AddCollaborator { uint64 worktree_id = 1; - Peer peer = 2; + Collaborator collaborator = 2; } -message RemovePeer { +message RemoveCollaborator { uint64 worktree_id = 1; uint32 peer_id = 2; } @@ -196,7 +196,7 @@ message UpdateContacts { // Entities -message Peer { +message Collaborator { uint32 peer_id = 1; uint32 replica_id = 2; uint64 user_id = 3; diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 8299952c798609712d31b238db77184e182b1168..bfdce85b774b2574751651e8436c8286539f28fe 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -121,7 +121,7 @@ macro_rules! entity_messages { messages!( Ack, - AddPeer, + AddCollaborator, BufferSaved, ChannelMessageSent, CloseBuffer, @@ -145,7 +145,7 @@ messages!( OpenWorktree, OpenWorktreeResponse, Ping, - RemovePeer, + RemoveCollaborator, SaveBuffer, SendChannelMessage, SendChannelMessageResponse, @@ -174,13 +174,13 @@ request_messages!( entity_messages!( worktree_id, - AddPeer, + AddCollaborator, BufferSaved, CloseBuffer, CloseWorktree, OpenBuffer, JoinWorktree, - RemovePeer, + RemoveCollaborator, SaveBuffer, UnshareWorktree, UpdateBuffer, diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 9f4c7247707328e90b0430d8f8ab4c79d573d188..a4c21b4ea469294acaec5d1496cfcd2d993b4529 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -187,7 +187,7 @@ impl Server { broadcast(connection_id, peer_ids, |conn_id| { self.peer.send( conn_id, - proto::RemovePeer { + proto::RemoveCollaborator { worktree_id, peer_id: connection_id.0, }, @@ -341,15 +341,15 @@ impl Server { .and_then(|joined| { let share = joined.worktree.share()?; let peer_count = share.guests.len(); - let mut peers = Vec::with_capacity(peer_count); - peers.push(proto::Peer { + let mut collaborators = Vec::with_capacity(peer_count); + collaborators.push(proto::Collaborator { peer_id: joined.worktree.host_connection_id.0, replica_id: 0, user_id: joined.worktree.host_user_id.to_proto(), }); for (peer_conn_id, (peer_replica_id, peer_user_id)) in &share.guests { if *peer_conn_id != request.sender_id { - peers.push(proto::Peer { + collaborators.push(proto::Collaborator { peer_id: peer_conn_id.0, replica_id: *peer_replica_id as u32, user_id: peer_user_id.to_proto(), @@ -363,7 +363,7 @@ impl Server { entries: share.entries.values().cloned().collect(), }), replica_id: joined.replica_id as u32, - peers, + collaborators, }; let connection_ids = joined.worktree.connection_ids(); let contact_user_ids = joined.worktree.authorized_user_ids.clone(); @@ -375,9 +375,9 @@ impl Server { broadcast(request.sender_id, connection_ids, |conn_id| { self.peer.send( conn_id, - proto::AddPeer { + proto::AddCollaborator { worktree_id, - peer: Some(proto::Peer { + collaborator: Some(proto::Collaborator { peer_id: request.sender_id.0, replica_id: response.replica_id, user_id: user_id.to_proto(), @@ -415,7 +415,7 @@ impl Server { broadcast(sender_id, worktree.connection_ids, |conn_id| { self.peer.send( conn_id, - proto::RemovePeer { + proto::RemoveCollaborator { worktree_id, peer_id: sender_id.0, }, @@ -960,7 +960,7 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; + let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; cx_a.foreground().forbid_parking(); @@ -978,6 +978,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), + user_store_a, "/a".as_ref(), fs, lang_registry.clone(), @@ -1052,7 +1053,7 @@ mod tests { .condition(&cx_a, |tree, cx| !tree.has_open_buffer("b.txt", cx)) .await; - // Dropping the worktree removes client B from client A's peers. + // Dropping the worktree removes client B from client A's collaborators. cx_b.update(move |_| drop(worktree_b)); worktree_a .condition(&cx_a, |tree, _| tree.collaborators().is_empty()) @@ -1089,6 +1090,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( app_state_a.client.clone(), + app_state_a.user_store.clone(), "/a".as_ref(), fs, app_state_a.languages.clone(), @@ -1163,7 +1165,7 @@ mod tests { // Connect to a server as 3 clients. let mut server = TestServer::start().await; - let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; + let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; let (client_c, user_store_c) = server.create_client(&mut cx_c, "user_c").await; @@ -1182,6 +1184,7 @@ mod tests { let worktree_a = Worktree::open_local( client_a.clone(), + user_store_a, "/a".as_ref(), fs.clone(), lang_registry.clone(), @@ -1304,7 +1307,7 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; + let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A @@ -1320,6 +1323,7 @@ mod tests { let worktree_a = Worktree::open_local( client_a.clone(), + user_store_a, "/dir".as_ref(), fs, lang_registry.clone(), @@ -1390,7 +1394,7 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; + let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A @@ -1405,6 +1409,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), + user_store_a, "/dir".as_ref(), fs, lang_registry.clone(), @@ -1457,7 +1462,7 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; + let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A @@ -1472,6 +1477,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), + user_store_a, "/dir".as_ref(), fs, lang_registry.clone(), @@ -1512,14 +1518,14 @@ mod tests { } #[gpui::test] - async fn test_peer_disconnection(mut cx_a: TestAppContext, cx_b: TestAppContext) { + async fn test_peer_disconnection(mut cx_a: TestAppContext, mut cx_b: TestAppContext) { cx_a.foreground().forbid_parking(); let lang_registry = Arc::new(LanguageRegistry::new()); // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_a, "user_b").await; + let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; + let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A let fs = Arc::new(FakeFs::new()); @@ -1534,6 +1540,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), + user_store_a, "/a".as_ref(), fs, lang_registry.clone(), @@ -1593,8 +1600,8 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_a, "user_b").await; + let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; + let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A let fs = Arc::new(FakeFs::new()); @@ -1609,6 +1616,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), + user_store_a, "/a".as_ref(), fs, lang_registry.clone(), @@ -2140,6 +2148,7 @@ mod tests { let worktree_a = Worktree::open_local( client_a.clone(), + user_store_a.clone(), "/a".as_ref(), fs.clone(), lang_registry.clone(), From 4bd43e67ef8da054b605d7ad93ba8af65ad0b958 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 27 Nov 2021 12:33:25 -0700 Subject: [PATCH 09/13] Introduce a TestClient and associate it with a PeerId This makes it easier to integration test peer interactions because now we know their PeerIds. --- crates/project/src/worktree.rs | 1 + crates/rpc/src/peer.rs | 14 +- crates/server/src/rpc.rs | 237 +++++++++++++++++++++------------ 3 files changed, 156 insertions(+), 96 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 0fec2d42097d0d779cbb7e7299a212cb7534f289..50f5b2d044dd3a4947596af94fb72b7317737e72 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -63,6 +63,7 @@ pub enum Event { Closed, } +#[derive(Debug)] pub struct Collaborator { pub user: Arc, pub peer_id: PeerId, diff --git a/crates/rpc/src/peer.rs b/crates/rpc/src/peer.rs index 7752fc231abfcec12c44e357100734d83a3700d4..454881fece48654b5c4a994158eaf80fe5bc15b1 100644 --- a/crates/rpc/src/peer.rs +++ b/crates/rpc/src/peer.rs @@ -8,6 +8,7 @@ use postage::{ prelude::{Sink as _, Stream as _}, }; use smol_timeout::TimeoutExt as _; +use std::sync::atomic::Ordering::SeqCst; use std::{ collections::HashMap, fmt, @@ -81,12 +82,12 @@ impl TypedEnvelope { } pub struct Peer { - connections: RwLock>, + pub connections: RwLock>, next_connection_id: AtomicU32, } #[derive(Clone)] -struct ConnectionState { +pub struct ConnectionState { outgoing_tx: mpsc::Sender, next_message_id: Arc, response_channels: Arc>>>>, @@ -110,10 +111,7 @@ impl Peer { impl Future> + Send, mpsc::Receiver>, ) { - let connection_id = ConnectionId( - self.next_connection_id - .fetch_add(1, atomic::Ordering::SeqCst), - ); + let connection_id = ConnectionId(self.next_connection_id.fetch_add(1, SeqCst)); let (mut incoming_tx, incoming_rx) = mpsc::channel(64); let (outgoing_tx, mut outgoing_rx) = mpsc::channel(64); let connection_state = ConnectionState { @@ -219,9 +217,7 @@ impl Peer { let (tx, mut rx) = mpsc::channel(1); async move { let mut connection = this.connection_state(receiver_id).await?; - let message_id = connection - .next_message_id - .fetch_add(1, atomic::Ordering::SeqCst); + let message_id = connection.next_message_id.fetch_add(1, SeqCst); connection .response_channels .lock() diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index a4c21b4ea469294acaec5d1496cfcd2d993b4529..0e317028c70c54d84860be0c3273c2e813d8ce8c 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -112,11 +112,17 @@ impl Server { connection: Connection, addr: String, user_id: UserId, + mut send_connection_id: Option>, ) -> impl Future { let mut this = self.clone(); async move { let (connection_id, handle_io, mut incoming_rx) = this.peer.add_connection(connection).await; + + if let Some(send_connection_id) = send_connection_id.as_mut() { + let _ = send_connection_id.send(connection_id).await; + } + this.state_mut().add_connection(connection_id, user_id); if let Err(err) = this.update_contacts_for_users(&[user_id]).await { log::error!("error updating contacts for {:?}: {}", user_id, err); @@ -887,6 +893,7 @@ pub fn add_routes(app: &mut tide::Server>, rpc: &Arc) { ), addr, user_id, + None, ) .await; } @@ -925,9 +932,11 @@ mod tests { use gpui::{ModelHandle, TestAppContext}; use parking_lot::Mutex; use postage::{mpsc, watch}; + use rpc::PeerId; use serde_json::json; use sqlx::types::time::OffsetDateTime; use std::{ + ops::Deref, path::Path, sync::{ atomic::{AtomicBool, Ordering::SeqCst}, @@ -960,8 +969,8 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; + let client_a = server.create_client(&mut cx_a, "user_a").await; + let client_b = server.create_client(&mut cx_b, "user_b").await; cx_a.foreground().forbid_parking(); @@ -978,7 +987,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), - user_store_a, + client_a.user_store.clone(), "/a".as_ref(), fs, lang_registry.clone(), @@ -999,17 +1008,31 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), - user_store_b, + client_b.user_store.clone(), &mut cx_b.to_async(), ) .await .unwrap(); - let replica_id_b = worktree_b.read_with(&cx_b, |tree, _| tree.replica_id()); + + let replica_id_b = worktree_b.read_with(&cx_b, |tree, _| { + assert_eq!( + tree.collaborators() + .get(&client_a.peer_id) + .unwrap() + .user + .github_login, + "user_a" + ); + tree.replica_id() + }); worktree_a .condition(&cx_a, |tree, _| { tree.collaborators() - .values() - .any(|collaborator| collaborator.replica_id == replica_id_b) + .get(&client_b.peer_id) + .map_or(false, |collaborator| { + collaborator.replica_id == replica_id_b + && collaborator.user.github_login == "user_b" + }) }) .await; @@ -1068,12 +1091,12 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; - Arc::get_mut(&mut app_state_a).unwrap().client = client_a; - Arc::get_mut(&mut app_state_a).unwrap().user_store = user_store_a; - Arc::get_mut(&mut app_state_b).unwrap().client = client_b; - Arc::get_mut(&mut app_state_b).unwrap().user_store = user_store_b; + let client_a = server.create_client(&mut cx_a, "user_a").await; + let client_b = server.create_client(&mut cx_b, "user_b").await; + Arc::get_mut(&mut app_state_a).unwrap().client = client_a.clone(); + Arc::get_mut(&mut app_state_a).unwrap().user_store = client_a.user_store.clone(); + Arc::get_mut(&mut app_state_b).unwrap().client = client_b.clone(); + Arc::get_mut(&mut app_state_b).unwrap().user_store = client_b.user_store.clone(); cx_a.foreground().forbid_parking(); @@ -1165,9 +1188,9 @@ mod tests { // Connect to a server as 3 clients. let mut server = TestServer::start().await; - let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; - let (client_c, user_store_c) = server.create_client(&mut cx_c, "user_c").await; + let client_a = server.create_client(&mut cx_a, "user_a").await; + let client_b = server.create_client(&mut cx_b, "user_b").await; + let client_c = server.create_client(&mut cx_c, "user_c").await; let fs = Arc::new(FakeFs::new()); @@ -1184,7 +1207,7 @@ mod tests { let worktree_a = Worktree::open_local( client_a.clone(), - user_store_a, + client_a.user_store.clone(), "/a".as_ref(), fs.clone(), lang_registry.clone(), @@ -1205,7 +1228,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), - user_store_b, + client_b.user_store.clone(), &mut cx_b.to_async(), ) .await @@ -1214,7 +1237,7 @@ mod tests { client_c.clone(), worktree_id, lang_registry.clone(), - user_store_c, + client_c.user_store.clone(), &mut cx_c.to_async(), ) .await @@ -1307,8 +1330,8 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; + let client_a = server.create_client(&mut cx_a, "user_a").await; + let client_b = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A let fs = Arc::new(FakeFs::new()); @@ -1323,7 +1346,7 @@ mod tests { let worktree_a = Worktree::open_local( client_a.clone(), - user_store_a, + client_a.user_store.clone(), "/dir".as_ref(), fs, lang_registry.clone(), @@ -1344,7 +1367,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), - user_store_b, + client_b.user_store.clone(), &mut cx_b.to_async(), ) .await @@ -1394,8 +1417,8 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; + let client_a = server.create_client(&mut cx_a, "user_a").await; + let client_b = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A let fs = Arc::new(FakeFs::new()); @@ -1409,7 +1432,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), - user_store_a, + client_a.user_store.clone(), "/dir".as_ref(), fs, lang_registry.clone(), @@ -1430,7 +1453,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), - user_store_b, + client_b.user_store.clone(), &mut cx_b.to_async(), ) .await @@ -1462,8 +1485,8 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; + let client_a = server.create_client(&mut cx_a, "user_a").await; + let client_b = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A let fs = Arc::new(FakeFs::new()); @@ -1477,7 +1500,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), - user_store_a, + client_a.user_store.clone(), "/dir".as_ref(), fs, lang_registry.clone(), @@ -1498,7 +1521,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), - user_store_b, + client_b.user_store.clone(), &mut cx_b.to_async(), ) .await @@ -1524,8 +1547,8 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; + let client_a = server.create_client(&mut cx_a, "user_a").await; + let client_b = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A let fs = Arc::new(FakeFs::new()); @@ -1540,7 +1563,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), - user_store_a, + client_a.user_store.clone(), "/a".as_ref(), fs, lang_registry.clone(), @@ -1561,7 +1584,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), - user_store_b, + client_b.user_store.clone(), &mut cx_b.to_async(), ) .await @@ -1600,8 +1623,8 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; + let client_a = server.create_client(&mut cx_a, "user_a").await; + let client_b = server.create_client(&mut cx_b, "user_b").await; // Share a local worktree as client A let fs = Arc::new(FakeFs::new()); @@ -1616,7 +1639,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), - user_store_a, + client_a.user_store.clone(), "/a".as_ref(), fs, lang_registry.clone(), @@ -1671,7 +1694,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), - user_store_b, + client_b.user_store.clone(), &mut cx_b.to_async(), ) .await @@ -1719,30 +1742,30 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; + let client_a = server.create_client(&mut cx_a, "user_a").await; + let client_b = server.create_client(&mut cx_b, "user_b").await; // Create an org that includes these 2 users. let db = &server.app_state.db; let org_id = db.create_org("Test Org", "test-org").await.unwrap(); - db.add_org_member(org_id, current_user_id(&user_store_a, &cx_a), false) + db.add_org_member(org_id, client_a.current_user_id(&cx_a), false) .await .unwrap(); - db.add_org_member(org_id, current_user_id(&user_store_b, &cx_b), false) + db.add_org_member(org_id, client_b.current_user_id(&cx_b), false) .await .unwrap(); // Create a channel that includes all the users. let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap(); - db.add_channel_member(channel_id, current_user_id(&user_store_a, &cx_a), false) + db.add_channel_member(channel_id, client_a.current_user_id(&cx_a), false) .await .unwrap(); - db.add_channel_member(channel_id, current_user_id(&user_store_b, &cx_b), false) + db.add_channel_member(channel_id, client_b.current_user_id(&cx_b), false) .await .unwrap(); db.create_channel_message( channel_id, - current_user_id(&user_store_b, &cx_b), + client_b.current_user_id(&cx_b), "hello A, it's B.", OffsetDateTime::now_utc(), 1, @@ -1750,7 +1773,8 @@ mod tests { .await .unwrap(); - let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx)); + let channels_a = cx_a + .add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx)); channels_a .condition(&mut cx_a, |list, _| list.available_channels().is_some()) .await; @@ -1774,7 +1798,8 @@ mod tests { }) .await; - let channels_b = cx_b.add_model(|cx| ChannelList::new(user_store_b, client_b, cx)); + let channels_b = cx_b + .add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx)); channels_b .condition(&mut cx_b, |list, _| list.available_channels().is_some()) .await; @@ -1856,19 +1881,20 @@ mod tests { cx_a.foreground().forbid_parking(); let mut server = TestServer::start().await; - let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; + let client_a = server.create_client(&mut cx_a, "user_a").await; let db = &server.app_state.db; let org_id = db.create_org("Test Org", "test-org").await.unwrap(); let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap(); - db.add_org_member(org_id, current_user_id(&user_store_a, &cx_a), false) + db.add_org_member(org_id, client_a.current_user_id(&cx_a), false) .await .unwrap(); - db.add_channel_member(channel_id, current_user_id(&user_store_a, &cx_a), false) + db.add_channel_member(channel_id, client_a.current_user_id(&cx_a), false) .await .unwrap(); - let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx)); + let channels_a = cx_a + .add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx)); channels_a .condition(&mut cx_a, |list, _| list.available_channels().is_some()) .await; @@ -1916,31 +1942,31 @@ mod tests { // Connect to a server as 2 clients. let mut server = TestServer::start().await; - let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; + let client_a = server.create_client(&mut cx_a, "user_a").await; + let client_b = server.create_client(&mut cx_b, "user_b").await; let mut status_b = client_b.status(); // Create an org that includes these 2 users. let db = &server.app_state.db; let org_id = db.create_org("Test Org", "test-org").await.unwrap(); - db.add_org_member(org_id, current_user_id(&user_store_a, &cx_a), false) + db.add_org_member(org_id, client_a.current_user_id(&cx_a), false) .await .unwrap(); - db.add_org_member(org_id, current_user_id(&user_store_b, &cx_b), false) + db.add_org_member(org_id, client_b.current_user_id(&cx_b), false) .await .unwrap(); // Create a channel that includes all the users. let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap(); - db.add_channel_member(channel_id, current_user_id(&user_store_a, &cx_a), false) + db.add_channel_member(channel_id, client_a.current_user_id(&cx_a), false) .await .unwrap(); - db.add_channel_member(channel_id, current_user_id(&user_store_b, &cx_b), false) + db.add_channel_member(channel_id, client_b.current_user_id(&cx_b), false) .await .unwrap(); db.create_channel_message( channel_id, - current_user_id(&user_store_b, &cx_b), + client_b.current_user_id(&cx_b), "hello A, it's B.", OffsetDateTime::now_utc(), 2, @@ -1948,7 +1974,8 @@ mod tests { .await .unwrap(); - let channels_a = cx_a.add_model(|cx| ChannelList::new(user_store_a, client_a, cx)); + let channels_a = cx_a + .add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx)); channels_a .condition(&mut cx_a, |list, _| list.available_channels().is_some()) .await; @@ -1973,7 +2000,8 @@ mod tests { }) .await; - let channels_b = cx_b.add_model(|cx| ChannelList::new(user_store_b.clone(), client_b, cx)); + let channels_b = cx_b + .add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx)); channels_b .condition(&mut cx_b, |list, _| list.available_channels().is_some()) .await; @@ -2000,7 +2028,7 @@ mod tests { // Disconnect client B, ensuring we can still access its cached channel data. server.forbid_connections(); - server.disconnect_client(current_user_id(&user_store_b, &cx_b)); + server.disconnect_client(client_b.current_user_id(&cx_b)); while !matches!( status_b.recv().await, Some(client::Status::ReconnectionError { .. }) @@ -2131,9 +2159,9 @@ mod tests { // Connect to a server as 3 clients. let mut server = TestServer::start().await; - let (client_a, user_store_a) = server.create_client(&mut cx_a, "user_a").await; - let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; - let (_client_c, user_store_c) = server.create_client(&mut cx_c, "user_c").await; + let client_a = server.create_client(&mut cx_a, "user_a").await; + let client_b = server.create_client(&mut cx_b, "user_b").await; + let client_c = server.create_client(&mut cx_c, "user_c").await; let fs = Arc::new(FakeFs::new()); @@ -2148,7 +2176,7 @@ mod tests { let worktree_a = Worktree::open_local( client_a.clone(), - user_store_a.clone(), + client_a.user_store.clone(), "/a".as_ref(), fs.clone(), lang_registry.clone(), @@ -2157,17 +2185,20 @@ mod tests { .await .unwrap(); - user_store_a + client_a + .user_store .condition(&cx_a, |user_store, _| { contacts(user_store) == vec![("user_a", vec![("a", vec![])])] }) .await; - user_store_b + client_b + .user_store .condition(&cx_b, |user_store, _| { contacts(user_store) == vec![("user_a", vec![("a", vec![])])] }) .await; - user_store_c + client_c + .user_store .condition(&cx_c, |user_store, _| { contacts(user_store) == vec![("user_a", vec![("a", vec![])])] }) @@ -2182,36 +2213,42 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), - user_store_b.clone(), + client_b.user_store.clone(), &mut cx_b.to_async(), ) .await .unwrap(); - user_store_a + client_a + .user_store .condition(&cx_a, |user_store, _| { contacts(user_store) == vec![("user_a", vec![("a", vec!["user_b"])])] }) .await; - user_store_b + client_b + .user_store .condition(&cx_b, |user_store, _| { contacts(user_store) == vec![("user_a", vec![("a", vec!["user_b"])])] }) .await; - user_store_c + client_c + .user_store .condition(&cx_c, |user_store, _| { contacts(user_store) == vec![("user_a", vec![("a", vec!["user_b"])])] }) .await; cx_a.update(move |_| drop(worktree_a)); - user_store_a + client_a + .user_store .condition(&cx_a, |user_store, _| contacts(user_store) == vec![]) .await; - user_store_b + client_b + .user_store .condition(&cx_b, |user_store, _| contacts(user_store) == vec![]) .await; - user_store_c + client_c + .user_store .condition(&cx_c, |user_store, _| contacts(user_store) == vec![]) .await; @@ -2264,17 +2301,15 @@ mod tests { } } - async fn create_client( - &mut self, - cx: &mut TestAppContext, - name: &str, - ) -> (Arc, ModelHandle) { + async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient { let user_id = self.app_state.db.create_user(name, false).await.unwrap(); let client_name = name.to_string(); let mut client = Client::new(); let server = self.server.clone(); let connection_killers = self.connection_killers.clone(); let forbid_connections = self.forbid_connections.clone(); + let (connection_id_tx, mut connection_id_rx) = postage::mpsc::channel(16); + Arc::get_mut(&mut client) .unwrap() .override_authenticate(move |cx| { @@ -2294,6 +2329,7 @@ mod tests { let connection_killers = connection_killers.clone(); let forbid_connections = forbid_connections.clone(); let client_name = client_name.clone(); + let connection_id_tx = connection_id_tx.clone(); cx.spawn(move |cx| async move { if forbid_connections.load(SeqCst) { Err(EstablishConnectionError::other(anyhow!( @@ -2303,7 +2339,12 @@ mod tests { let (client_conn, server_conn, kill_conn) = Connection::in_memory(); connection_killers.lock().insert(user_id, kill_conn); cx.background() - .spawn(server.handle_connection(server_conn, client_name, user_id)) + .spawn(server.handle_connection( + server_conn, + client_name, + user_id, + Some(connection_id_tx), + )) .detach(); Ok(client_conn) } @@ -2316,12 +2357,17 @@ mod tests { .await .unwrap(); + let peer_id = PeerId(connection_id_rx.recv().await.unwrap().0); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); let mut authed_user = user_store.read_with(cx, |user_store, _| user_store.watch_current_user()); while authed_user.recv().await.unwrap().is_none() {} - (client, user_store) + TestClient { + client, + peer_id, + user_store, + } } fn disconnect_client(&self, user_id: UserId) { @@ -2377,10 +2423,27 @@ mod tests { } } - fn current_user_id(user_store: &ModelHandle, cx: &TestAppContext) -> UserId { - UserId::from_proto( - user_store.read_with(cx, |user_store, _| user_store.current_user().unwrap().id), - ) + struct TestClient { + client: Arc, + pub peer_id: PeerId, + pub user_store: ModelHandle, + } + + impl Deref for TestClient { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.client + } + } + + impl TestClient { + pub fn current_user_id(&self, cx: &TestAppContext) -> UserId { + UserId::from_proto( + self.user_store + .read_with(cx, |user_store, _| user_store.current_user().unwrap().id), + ) + } } fn channel_messages(channel: &Channel) -> Vec<(String, String, bool)> { From 6ce76ca13e5d1c873c52a9465a663f3d1ca792ad Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 28 Nov 2021 12:28:10 -0700 Subject: [PATCH 10/13] Render active worktree collaborator avatars in the titlebar --- crates/workspace/src/lib.rs | 102 +++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index ea75277b9cbbf2af29511f1e3547366498b881a3..a9bcd8261992ebeefa0307ba545d105f8f7b41c8 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -5,7 +5,7 @@ pub mod sidebar; mod status_bar; use anyhow::{anyhow, Result}; -use client::{Authenticate, ChannelList, Client, UserStore}; +use client::{Authenticate, ChannelList, Client, User, UserStore}; use gpui::{ action, elements::*, json::to_string_pretty, keymap::Binding, platform::CursorStyle, AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle, MutableAppContext, @@ -27,6 +27,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; +use theme::Theme; action!(OpenNew, WorkspaceParams); action!(Save); @@ -952,14 +953,64 @@ impl Workspace { } } - fn render_avatar(&self, cx: &mut RenderContext) -> ElementBox { - let theme = &self.settings.borrow().theme; - if let Some(avatar) = self - .user_store - .read(cx) - .current_user() - .and_then(|user| user.avatar.clone()) - { + fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext) -> ElementBox { + ConstrainedBox::new( + Container::new( + Stack::new() + .with_child( + Align::new( + Label::new("zed".into(), theme.workspace.titlebar.title.clone()) + .boxed(), + ) + .boxed(), + ) + .with_child( + Align::new( + Flex::row() + .with_children(self.render_collaborators(theme, cx)) + .with_child(self.render_avatar( + self.user_store.read(cx).current_user().as_ref(), + theme, + cx, + )) + .with_children(self.render_connection_status()) + .boxed(), + ) + .right() + .boxed(), + ) + .boxed(), + ) + .with_style(theme.workspace.titlebar.container) + .boxed(), + ) + .with_height(32.) + .named("titlebar") + } + + fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext) -> Vec { + let mut elements = Vec::new(); + if let Some(active_worktree) = self.project.read(cx).active_worktree() { + let users = active_worktree + .read(cx) + .collaborators() + .values() + .map(|c| c.user.clone()) + .collect::>(); + for user in users { + elements.push(self.render_avatar(Some(&user), theme, cx)); + } + } + elements + } + + fn render_avatar( + &self, + user: Option<&Arc>, + theme: &Theme, + cx: &mut RenderContext, + ) -> ElementBox { + if let Some(avatar) = user.and_then(|user| user.avatar.clone()) { ConstrainedBox::new( Align::new( ConstrainedBox::new( @@ -1008,38 +1059,7 @@ impl View for Workspace { let theme = &settings.theme; Container::new( Flex::column() - .with_child( - ConstrainedBox::new( - Container::new( - Stack::new() - .with_child( - Align::new( - Label::new( - "zed".into(), - theme.workspace.titlebar.title.clone(), - ) - .boxed(), - ) - .boxed(), - ) - .with_child( - Align::new( - Flex::row() - .with_children(self.render_connection_status()) - .with_child(self.render_avatar(cx)) - .boxed(), - ) - .right() - .boxed(), - ) - .boxed(), - ) - .with_style(theme.workspace.titlebar.container) - .boxed(), - ) - .with_height(32.) - .named("titlebar"), - ) + .with_child(self.render_titlebar(&theme, cx)) .with_child( Expanded::new( 1.0, From a5039cad650403dbad34fdd629ba68bc0a77310c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 28 Nov 2021 12:41:46 -0700 Subject: [PATCH 11/13] Tweak avatar sizes in titlebar --- crates/theme/src/lib.rs | 1 + crates/workspace/src/lib.rs | 2 +- crates/zed/assets/themes/_base.toml | 9 +++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index 1a484c6db7720510e094fd33eb986251fd1b64a4..431937cc7e4ea3a01d63225a884e25b62c28c16f 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -42,6 +42,7 @@ pub struct Workspace { pub struct Titlebar { #[serde(flatten)] pub container: ContainerStyle, + pub height: f32, pub title: TextStyle, pub avatar_width: f32, pub offline_icon: OfflineIcon, diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index a9bcd8261992ebeefa0307ba545d105f8f7b41c8..0f636c23eb0648690d47d6badb05b4213e930d3e 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -984,7 +984,7 @@ impl Workspace { .with_style(theme.workspace.titlebar.container) .boxed(), ) - .with_height(32.) + .with_height(theme.workspace.titlebar.height) .named("titlebar") } diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index dca2e0602898f6aa2cafeff0deea0429b597345b..40868262fb489a1d95e3762c3aa99a6590c3b6cf 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -6,9 +6,10 @@ background = "$surface.0" pane_divider = { width = 1, color = "$border.0" } [workspace.titlebar] +height = 32 border = { width = 1, bottom = true, color = "$border.0" } title = "$text.0" -avatar_width = 20 +avatar_width = 18 icon_color = "$text.2.color" avatar = { corner_radius = 10, border = { width = 1, color = "#00000088" } } outdated_warning = { extends = "$text.2", size = 13 } @@ -46,7 +47,7 @@ background = "$surface.1" text = "$text.0" [workspace.sidebar] -width = 32 +width = 30 border = { right = true, width = 1, color = "$border.0" } [workspace.sidebar.resize_handle] @@ -149,7 +150,7 @@ color = "$text.1.color" [contacts_panel] extends = "$panel" host_row_height = 28 -host_avatar = { corner_radius = 10, width = 20 } +host_avatar = { corner_radius = 10, width = 18 } host_username = { extends = "$text.0", padding.left = 8 } tree_branch_width = 1 tree_branch_color = "$surface.2" @@ -157,7 +158,7 @@ tree_branch_color = "$surface.2" [contacts_panel.worktree] height = 24 padding = { left = 8 } -guest_avatar = { corner_radius = 8, width = 16 } +guest_avatar = { corner_radius = 8, width = 14 } guest_avatar_spacing = 4 [contacts_panel.worktree.name] From fbc307cd5ebddddb2d51cd27d532accd0fa8b117 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 28 Nov 2021 13:25:05 -0700 Subject: [PATCH 12/13] Associate collaborator avatars with "ribbons" corresponding to their cursor color --- crates/editor/src/element.rs | 7 +--- crates/gpui/src/elements/align.rs | 5 +++ crates/project/src/worktree.rs | 2 +- crates/theme/src/lib.rs | 18 +++++++++ crates/workspace/src/lib.rs | 59 +++++++++++++++++++++-------- crates/zed/assets/themes/_base.toml | 3 +- 6 files changed, 70 insertions(+), 24 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 03b04c8c6bf391bbed0378b3223609589f18a754..2f4923985f98061803e2af901e3fdd98c760bb96 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -371,12 +371,7 @@ impl EditorElement { let content_origin = bounds.origin() + layout.text_offset; for (replica_id, selections) in &layout.selections { - let style_ix = *replica_id as usize % (style.guest_selections.len() + 1); - let style = if style_ix == 0 { - &style.selection - } else { - &style.guest_selections[style_ix - 1] - }; + let style = style.replica_selection_style(*replica_id); for selection in selections { if selection.start != selection.end { diff --git a/crates/gpui/src/elements/align.rs b/crates/gpui/src/elements/align.rs index 7f065e2b5357856a96d515224679d1d9f4c46525..ce99437f3ba51ffc1d79a30a6b9c7812fb8fe904 100644 --- a/crates/gpui/src/elements/align.rs +++ b/crates/gpui/src/elements/align.rs @@ -25,6 +25,11 @@ impl Align { self } + pub fn bottom(mut self) -> Self { + self.alignment.set_y(1.0); + self + } + pub fn left(mut self) -> Self { self.alignment.set_x(-1.0); self diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 50f5b2d044dd3a4947596af94fb72b7317737e72..7187310be7419fd64db0ffec43b61b54b18f535c 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -63,7 +63,7 @@ pub enum Event { Closed, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Collaborator { pub user: Arc, pub peer_id: PeerId, diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index 431937cc7e4ea3a01d63225a884e25b62c28c16f..716ce4a46a42faa14409b3c18ba1e4be02cba5e6 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -45,6 +45,7 @@ pub struct Titlebar { pub height: f32, pub title: TextStyle, pub avatar_width: f32, + pub avatar_ribbon: AvatarRibbon, pub offline_icon: OfflineIcon, pub icon_color: Color, pub avatar: ImageStyle, @@ -53,6 +54,14 @@ pub struct Titlebar { pub outdated_warning: ContainedText, } +#[derive(Clone, Deserialize, Default)] +pub struct AvatarRibbon { + #[serde(flatten)] + pub container: ContainerStyle, + pub width: f32, + pub height: f32, +} + #[derive(Clone, Deserialize, Default)] pub struct OfflineIcon { #[serde(flatten)] @@ -276,6 +285,15 @@ impl EditorStyle { pub fn placeholder_text(&self) -> &TextStyle { self.placeholder_text.as_ref().unwrap_or(&self.text) } + + pub fn replica_selection_style(&self, replica_id: u16) -> &SelectionStyle { + let style_ix = replica_id as usize % (self.guest_selections.len() + 1); + if style_ix == 0 { + &self.selection + } else { + &self.guest_selections[style_ix - 1] + } + } } impl InputEditorStyle { diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 0f636c23eb0648690d47d6badb05b4213e930d3e..9c6d0d43c28b5fdc9d6f8004f5a16c3522b8faa1 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -968,11 +968,17 @@ impl Workspace { Align::new( Flex::row() .with_children(self.render_collaborators(theme, cx)) - .with_child(self.render_avatar( - self.user_store.read(cx).current_user().as_ref(), - theme, - cx, - )) + .with_child( + self.render_avatar( + self.user_store.read(cx).current_user().as_ref(), + self.project + .read(cx) + .active_worktree() + .map(|worktree| worktree.read(cx).replica_id()), + theme, + cx, + ), + ) .with_children(self.render_connection_status()) .boxed(), ) @@ -991,14 +997,19 @@ impl Workspace { fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext) -> Vec { let mut elements = Vec::new(); if let Some(active_worktree) = self.project.read(cx).active_worktree() { - let users = active_worktree + let collaborators = active_worktree .read(cx) .collaborators() .values() - .map(|c| c.user.clone()) + .cloned() .collect::>(); - for user in users { - elements.push(self.render_avatar(Some(&user), theme, cx)); + for collaborator in collaborators { + elements.push(self.render_avatar( + Some(&collaborator.user), + Some(collaborator.replica_id), + theme, + cx, + )); } } elements @@ -1007,21 +1018,37 @@ impl Workspace { fn render_avatar( &self, user: Option<&Arc>, + replica_id: Option, theme: &Theme, cx: &mut RenderContext, ) -> ElementBox { if let Some(avatar) = user.and_then(|user| user.avatar.clone()) { ConstrainedBox::new( - Align::new( - ConstrainedBox::new( - Image::new(avatar) - .with_style(theme.workspace.titlebar.avatar) + Stack::new() + .with_child( + ConstrainedBox::new( + Image::new(avatar) + .with_style(theme.workspace.titlebar.avatar) + .boxed(), + ) + .with_width(theme.workspace.titlebar.avatar_width) + .aligned() + .boxed(), + ) + .with_child( + Container::new(Empty::new().boxed()) + .with_style(theme.workspace.titlebar.avatar_ribbon.container) + .with_background_color(replica_id.map_or(Default::default(), |id| { + theme.editor.replica_selection_style(id).cursor + })) + .constrained() + .with_width(theme.workspace.titlebar.avatar_ribbon.width) + .with_height(theme.workspace.titlebar.avatar_ribbon.height) + .aligned() + .bottom() .boxed(), ) - .with_width(theme.workspace.titlebar.avatar_width) .boxed(), - ) - .boxed(), ) .with_width(theme.workspace.right_sidebar.width) .boxed() diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 40868262fb489a1d95e3762c3aa99a6590c3b6cf..7d25ee87fc2ab6c5341763138c244328e5d70ad5 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -10,8 +10,9 @@ height = 32 border = { width = 1, bottom = true, color = "$border.0" } title = "$text.0" avatar_width = 18 -icon_color = "$text.2.color" avatar = { corner_radius = 10, border = { width = 1, color = "#00000088" } } +avatar_ribbon = { background = "#ff0000", height = 3, width = 12 } +icon_color = "$text.2.color" outdated_warning = { extends = "$text.2", size = 13 } [workspace.titlebar.sign_in_prompt] From 815cc7ee91f363a3b1d61d3156f3248a51fef735 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 28 Nov 2021 14:04:31 -0700 Subject: [PATCH 13/13] Give avatar ribbons a rounded top --- crates/workspace/src/lib.rs | 96 ++++++++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 9c6d0d43c28b5fdc9d6f8004f5a16c3522b8faa1..1f11f116936717acc9f5c17eb11c791b97b0b7c8 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -7,7 +7,13 @@ mod status_bar; use anyhow::{anyhow, Result}; use client::{Authenticate, ChannelList, Client, User, UserStore}; use gpui::{ - action, elements::*, json::to_string_pretty, keymap::Binding, platform::CursorStyle, + action, + color::Color, + elements::*, + geometry::{vector::vec2f, PathBuilder}, + json::{self, to_string_pretty, ToJson}, + keymap::Binding, + platform::CursorStyle, AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, WeakModelHandle, }; @@ -1036,17 +1042,15 @@ impl Workspace { .boxed(), ) .with_child( - Container::new(Empty::new().boxed()) - .with_style(theme.workspace.titlebar.avatar_ribbon.container) - .with_background_color(replica_id.map_or(Default::default(), |id| { - theme.editor.replica_selection_style(id).cursor - })) - .constrained() - .with_width(theme.workspace.titlebar.avatar_ribbon.width) - .with_height(theme.workspace.titlebar.avatar_ribbon.height) - .aligned() - .bottom() - .boxed(), + AvatarRibbon::new(replica_id.map_or(Default::default(), |id| { + theme.editor.replica_selection_style(id).cursor + })) + .constrained() + .with_width(theme.workspace.titlebar.avatar_ribbon.width) + .with_height(theme.workspace.titlebar.avatar_ribbon.height) + .aligned() + .bottom() + .boxed(), ) .boxed(), ) @@ -1154,3 +1158,71 @@ impl WorkspaceHandle for ViewHandle { .collect::>() } } + +pub struct AvatarRibbon { + color: Color, +} + +impl AvatarRibbon { + pub fn new(color: Color) -> AvatarRibbon { + AvatarRibbon { color } + } +} + +impl Element for AvatarRibbon { + type LayoutState = (); + + type PaintState = (); + + fn layout( + &mut self, + constraint: gpui::SizeConstraint, + _: &mut gpui::LayoutContext, + ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { + (constraint.max, ()) + } + + fn paint( + &mut self, + bounds: gpui::geometry::rect::RectF, + _: gpui::geometry::rect::RectF, + _: &mut Self::LayoutState, + cx: &mut gpui::PaintContext, + ) -> Self::PaintState { + let mut path = PathBuilder::new(); + path.reset(bounds.lower_left()); + path.curve_to( + bounds.origin() + vec2f(bounds.height(), 0.), + bounds.origin(), + ); + path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.)); + path.curve_to(bounds.lower_right(), bounds.upper_right()); + path.line_to(bounds.lower_left()); + cx.scene.push_path(path.build(self.color, None)); + } + + fn dispatch_event( + &mut self, + _: &gpui::Event, + _: gpui::geometry::rect::RectF, + _: &mut Self::LayoutState, + _: &mut Self::PaintState, + _: &mut gpui::EventContext, + ) -> bool { + false + } + + fn debug( + &self, + bounds: gpui::geometry::rect::RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &gpui::DebugContext, + ) -> gpui::json::Value { + json::json!({ + "type": "AvatarRibbon", + "bounds": bounds.to_json(), + "color": self.color.to_json(), + }) + } +}