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/client/src/test.rs b/crates/client/src/test.rs index e471398b53178c9bbac94ebebabf21781278e422..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 { @@ -172,6 +190,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 47ea9ae55c087603137a5c0a573e65b883dff6b4..0a387487e1b064e3b387a634dfbbd62a13789e6f 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]>, - rpc: Arc, + contacts: Arc<[Contact]>, + client: Arc, http: Arc, - _maintain_collaborators: Task<()>, + _maintain_contacts: Task<()>, _maintain_current_user: Task<()>, } @@ -50,39 +50,43 @@ 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_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 = client.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([]), - rpc: rpc.clone(), + contacts: Arc::from([]), + client: client.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; } } }), _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() @@ -100,35 +104,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 +134,8 @@ impl UserStore { }) } - pub fn collaborators(&self) -> &Arc<[Collaborator]> { - &self.collaborators + pub fn contacts(&self) -> &Arc<[Contact]> { + &self.contacts } pub fn load_users( @@ -145,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 { @@ -212,19 +210,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/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 90% rename from crates/people_panel/src/lib.rs rename to crates/contacts_panel/src/lib.rs index 4a9d2927374225830b1867c91c6b322d15d6eb2a..8cf8b9191b6f478dd2d96fe4276af7208003fab4 100644 --- a/crates/people_panel/src/lib.rs +++ b/crates/contacts_panel/src/lib.rs @@ -1,4 +1,4 @@ -use client::{Collaborator, UserStore}; +use client::{Contact, UserStore}; use gpui::{ action, elements::*, @@ -17,28 +17,28 @@ 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 { - collaborators: ListState, +pub struct ContactsPanel { + contacts: ListState, user_store: ModelHandle, settings: watch::Receiver, - _maintain_collaborators: Subscription, + _maintain_contacts: Subscription, } -impl PeoplePanel { +impl ContactsPanel { pub fn new( user_store: ModelHandle, settings: watch::Receiver, 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,19 +103,19 @@ 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, ) -> 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,18 +294,18 @@ 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; - Container::new(List::new(self.collaborators.clone()).boxed()) + 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/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/lib.rs b/crates/project/src/lib.rs index 3e129c8fb8a1d67feb7b7abdebb1bc89c0c5c1fe..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}; @@ -19,9 +19,11 @@ pub use worktree::*; pub struct Project { worktrees: Vec>, + active_worktree: Option, active_entry: Option, languages: Arc, client: Arc, + user_store: ModelHandle, fs: Arc, } @@ -43,12 +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, } } @@ -70,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); }); @@ -89,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 => { @@ -109,10 +122,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 +151,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 +215,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 +228,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>( @@ -302,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; @@ -407,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 e6b444b5480b21c0deadfef5234356f6b9dd03df..7187310be7419fd64db0ffec43b61b54b18f535c 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; @@ -63,6 +63,33 @@ pub enum Event { Closed, } +#[derive(Clone, Debug)] +pub struct Collaborator { + pub user: Arc, + pub peer_id: PeerId, + 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; @@ -70,7 +97,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); @@ -118,14 +145,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(); @@ -142,18 +170,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> { @@ -163,7 +195,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() @@ -198,6 +229,20 @@ impl Worktree { }) .await; + 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(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| { cx.add_model(|cx: &mut ModelContext| { let snapshot = Snapshot { @@ -243,12 +288,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_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), + client.subscribe_to_entity(remote_id, cx, Self::handle_unshare), ]; Worktree::Remote(RemoteWorktree { @@ -257,14 +302,12 @@ impl Worktree { snapshot, snapshot_rx, updates_tx, - client: rpc.clone(), + client: client.clone(), open_buffers: Default::default(), - peers: peers - .into_iter() - .map(|p| (PeerId(p.peer_id), p.replica_id as ReplicaId)) - .collect(), + collaborators, queued_operations: Default::default(), languages, + user_store, _subscriptions, }) }) @@ -318,27 +361,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), } } @@ -390,10 +458,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, } } @@ -721,7 +789,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 { @@ -772,10 +840,11 @@ pub struct LocalWorktree { open_buffers: HashMap>, shared_buffers: HashMap>>, diagnostics: HashMap>, - peers: HashMap, + collaborators: HashMap, queued_operations: Vec<(u64, Operation)>, languages: Arc, - rpc: Arc, + client: Arc, + user_store: ModelHandle, fs: Arc, language_servers: HashMap>, } @@ -787,7 +856,8 @@ struct WorktreeConfig { impl LocalWorktree { async fn new( - rpc: Arc, + client: Arc, + user_store: ModelHandle, path: impl Into>, fs: Arc, languages: Arc, @@ -841,20 +911,20 @@ 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(); 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?; @@ -887,9 +957,10 @@ impl LocalWorktree { shared_buffers: Default::default(), diagnostics: Default::default(), queued_operations: Default::default(), - peers: Default::default(), + collaborators: Default::default(), languages, - rpc, + client, + user_store, fs, language_servers: Default::default(), }; @@ -1075,33 +1146,27 @@ 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"))?; - self.peers - .insert(PeerId(peer.peer_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); 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) { @@ -1243,7 +1308,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 @@ -1276,8 +1341,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), @@ -1297,7 +1362,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( @@ -1373,8 +1438,9 @@ pub struct RemoteWorktree { updates_tx: postage::mpsc::Sender, replica_id: ReplicaId, open_buffers: HashMap, - peers: HashMap, + collaborators: HashMap, languages: Arc, + user_store: ModelHandle, queued_operations: Vec<(u64, Operation)>, _subscriptions: Vec, } @@ -1491,32 +1557,27 @@ 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"))?; - self.peers - .insert(PeerId(peer.peer_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); 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)); @@ -1977,7 +2038,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 { @@ -2944,7 +3005,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}; @@ -2960,7 +3021,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", @@ -2974,8 +3035,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(), @@ -3006,8 +3072,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(), @@ -3036,8 +3108,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(), @@ -3082,8 +3159,10 @@ mod tests { let user_id = 5; let mut client = Client::new(); 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(), dir.path(), Arc::new(RealFs), Default::default(), @@ -3135,9 +3214,10 @@ mod tests { proto::JoinWorktreeResponse { worktree: share_request.await.unwrap().worktree, replica_id: 1, - peers: Vec::new(), + collaborators: Vec::new(), }, Client::new(), + user_store, Default::default(), &mut cx.to_async(), ) @@ -3230,7 +3310,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", @@ -3242,8 +3322,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(), @@ -3281,6 +3366,7 @@ mod tests { let user_id = 100; let mut client = Client::new(); let server = FakeServer::for_client(user_id, &mut client, &cx).await; + let user_store = server.build_user_store(client.clone(), &mut cx).await; let fs = Arc::new(FakeFs::new()); fs.insert_tree( @@ -3298,6 +3384,7 @@ mod tests { let worktree = Worktree::open_local( client.clone(), + user_store, "/path/to/the-dir".as_ref(), fs, Default::default(), @@ -3306,17 +3393,12 @@ 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, 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()], } ); @@ -3344,8 +3426,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(), @@ -3477,8 +3564,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(), @@ -3587,8 +3679,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/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 950178be340c9a0ff54e6722b9306e68f852a7fc..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; @@ -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; } } @@ -55,7 +55,7 @@ message Error { message OpenWorktree { string root_name = 1; - repeated string collaborator_logins = 2; + repeated string authorized_logins = 2; } message OpenWorktreeResponse { @@ -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; } @@ -190,15 +190,16 @@ message GetChannelMessagesResponse { bool done = 2; } -message UpdateCollaborators { - repeated Collaborator collaborators = 1; +message UpdateContacts { + repeated Contact contacts = 1; } // Entities -message Peer { +message Collaborator { uint32 peer_id = 1; uint32 replica_id = 2; + uint64 user_id = 3; } message User { @@ -357,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/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/rpc/src/proto.rs b/crates/rpc/src/proto.rs index e9de319a1c6abf8c23efcbfd3e47622d133908dc..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, @@ -131,7 +131,7 @@ messages!( GetChannelMessagesResponse, GetChannels, GetChannelsResponse, - UpdateCollaborators, + UpdateContacts, GetUsers, GetUsersResponse, JoinChannel, @@ -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 f35bbb00e6185efdc0589d93be11ed3410c31f7a..0e317028c70c54d84860be0c3273c2e813d8ce8c 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -112,14 +112,20 @@ 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_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(); @@ -173,7 +179,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 }) @@ -187,7 +193,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, }, @@ -196,7 +202,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 +220,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); - for github_login in request.payload.collaborator_logins { + let mut contact_user_ids = HashSet::new(); + contact_user_ids.insert(host_user_id); + for github_login in request.payload.authorized_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,10 +237,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, - collaborator_user_ids: collaborator_user_ids.clone(), + host_user_id, + authorized_user_ids: contact_user_ids.clone(), root_name: request.payload.root_name, share: None, }); @@ -242,8 +249,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(()) } @@ -260,7 +266,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 }) @@ -268,7 +274,7 @@ impl Server { ) .await?; } - self.update_collaborators_for_users(&worktree.collaborator_user_ids) + self.update_contacts_for_users(&worktree.authorized_user_ids) .await?; Ok(()) } @@ -287,15 +293,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( @@ -323,7 +328,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.authorized_user_ids) .await?; Ok(()) @@ -341,17 +346,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 mut peers = Vec::with_capacity(peer_count); - peers.push(proto::Peer { + let peer_count = share.guests.len(); + 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) 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 { + collaborators.push(proto::Collaborator { peer_id: peer_conn_id.0, replica_id: *peer_replica_id as u32, + user_id: peer_user_id.to_proto(), }); } } @@ -362,31 +369,31 @@ 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 collaborator_user_ids = joined.worktree.collaborator_user_ids.clone(); - Ok((response, connection_ids, collaborator_user_ids)) + let contact_user_ids = joined.worktree.authorized_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, - 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(), }), }, ) }) .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 @@ -414,14 +421,14 @@ 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, }, ) }) .await?; - self.update_collaborators_for_users(&worktree.collaborator_ids) + self.update_contacts_for_users(&worktree.authorized_user_ids) .await?; } Ok(()) @@ -591,7 +598,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<()> { @@ -600,12 +607,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(), }, )); } @@ -886,6 +893,7 @@ pub fn add_routes(app: &mut tide::Server>, rpc: &Arc) { ), addr, user_id, + None, ) .await; } @@ -924,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}, @@ -939,6 +949,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::{ @@ -946,7 +957,6 @@ mod tests { LanguageServerConfig, Point, }, lsp, - people_panel::JoinWorktree, project::{ProjectPath, Worktree}, test::test_app_state, workspace::Workspace, @@ -959,8 +969,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, _) = 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(); @@ -977,6 +987,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), + client_a.user_store.clone(), "/a".as_ref(), fs, lang_registry.clone(), @@ -997,16 +1008,31 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + 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.peers() - .values() - .any(|replica_id| *replica_id == replica_id_b) + tree.collaborators() + .get(&client_b.peer_id) + .map_or(false, |collaborator| { + collaborator.replica_id == replica_id_b + && collaborator.user.github_login == "user_b" + }) }) .await; @@ -1050,27 +1076,27 @@ 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.peers().is_empty()) + .condition(&cx_a, |tree, _| tree.collaborators().is_empty()) .await; } #[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); // 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(); @@ -1087,6 +1113,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(), @@ -1161,9 +1188,9 @@ 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_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()); @@ -1180,6 +1207,7 @@ mod tests { let worktree_a = Worktree::open_local( client_a.clone(), + client_a.user_store.clone(), "/a".as_ref(), fs.clone(), lang_registry.clone(), @@ -1200,6 +1228,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + client_b.user_store.clone(), &mut cx_b.to_async(), ) .await @@ -1208,6 +1237,7 @@ mod tests { client_c.clone(), worktree_id, lang_registry.clone(), + client_c.user_store.clone(), &mut cx_c.to_async(), ) .await @@ -1300,8 +1330,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, _) = 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()); @@ -1316,6 +1346,7 @@ mod tests { let worktree_a = Worktree::open_local( client_a.clone(), + client_a.user_store.clone(), "/dir".as_ref(), fs, lang_registry.clone(), @@ -1336,6 +1367,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + client_b.user_store.clone(), &mut cx_b.to_async(), ) .await @@ -1385,8 +1417,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, _) = 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()); @@ -1400,6 +1432,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), + client_a.user_store.clone(), "/dir".as_ref(), fs, lang_registry.clone(), @@ -1420,6 +1453,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + client_b.user_store.clone(), &mut cx_b.to_async(), ) .await @@ -1451,8 +1485,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, _) = 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()); @@ -1466,6 +1500,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), + client_a.user_store.clone(), "/dir".as_ref(), fs, lang_registry.clone(), @@ -1486,12 +1521,13 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + client_b.user_store.clone(), &mut cx_b.to_async(), ) .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 @@ -1500,19 +1536,19 @@ 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; } #[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, _) = server.create_client(&mut cx_a, "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()); @@ -1527,6 +1563,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), + client_a.user_store.clone(), "/a".as_ref(), fs, lang_registry.clone(), @@ -1547,18 +1584,19 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + client_b.user_store.clone(), &mut cx_b.to_async(), ) .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; } @@ -1585,8 +1623,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, _) = server.create_client(&mut cx_a, "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()); @@ -1601,6 +1639,7 @@ mod tests { .await; let worktree_a = Worktree::open_local( client_a.clone(), + client_a.user_store.clone(), "/a".as_ref(), fs, lang_registry.clone(), @@ -1655,6 +1694,7 @@ mod tests { client_b.clone(), worktree_id, lang_registry.clone(), + client_b.user_store.clone(), &mut cx_b.to_async(), ) .await @@ -1702,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, @@ -1733,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; @@ -1757,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; @@ -1839,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; @@ -1899,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, @@ -1931,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; @@ -1956,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; @@ -1983,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 { .. }) @@ -2104,7 +2149,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, @@ -2114,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()); @@ -2131,6 +2176,7 @@ mod tests { let worktree_a = Worktree::open_local( client_a.clone(), + client_a.user_store.clone(), "/a".as_ref(), fs.clone(), lang_registry.clone(), @@ -2139,19 +2185,22 @@ mod tests { .await .unwrap(); - user_store_a + client_a + .user_store .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 + client_b + .user_store .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 + client_c + .user_store .condition(&cx_c, |user_store, _| { - collaborators(user_store) == vec![("user_a", vec![("a", vec![])])] + contacts(user_store) == vec![("user_a", vec![("a", vec![])])] }) .await; @@ -2164,44 +2213,51 @@ mod tests { client_b.clone(), worktree_id, lang_registry.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, _| { - 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 + client_b + .user_store .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 + client_c + .user_store .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![]) + client_a + .user_store + .condition(&cx_a, |user_store, _| contacts(user_store) == vec![]) .await; - user_store_b - .condition(&cx_b, |user_store, _| collaborators(user_store) == vec![]) + client_b + .user_store + .condition(&cx_b, |user_store, _| contacts(user_store) == vec![]) .await; - user_store_c - .condition(&cx_c, |user_store, _| collaborators(user_store) == vec![]) + client_c + .user_store + .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| { @@ -2211,7 +2267,7 @@ mod tests { ) }) .collect(); - (collaborator.user.github_login.as_str(), worktrees) + (contact.user.github_login.as_str(), worktrees) }) .collect() } @@ -2245,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| { @@ -2275,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!( @@ -2284,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) } @@ -2297,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) { @@ -2358,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)> { diff --git a/crates/server/src/rpc/store.rs b/crates/server/src/rpc/store.rs index ac9c069146745d48b3820cb4275895d973549e2c..77bb01702fa3988d3ecdc1bd8f612c9ea673a915 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 collaborator_user_ids: Vec, + pub host_user_id: UserId, + pub authorized_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, } @@ -43,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> { @@ -53,12 +54,12 @@ pub struct JoinedWorktree<'a> { pub struct UnsharedWorktree { pub connection_ids: Vec, - pub collaborator_ids: Vec, + pub authorized_user_ids: Vec, } pub struct LeftWorktree { pub connection_ids: Vec, - pub collaborator_ids: Vec, + pub authorized_user_ids: Vec, } impl Store { @@ -106,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.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.collaborator_ids.extend(worktree.collaborator_ids); + result.contact_ids.extend(worktree.authorized_user_ids); } } @@ -170,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) @@ -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()); } @@ -189,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(), }) @@ -205,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 authorized_user_id in &worktree.authorized_user_ids { self.visible_worktrees_by_user_id - .entry(*collaborator_user_id) + .entry(*authorized_user_id) .or_default() .insert(worktree_id); } @@ -247,17 +248,17 @@ 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); } } } - for collaborator_user_id in &worktree.collaborator_user_ids { + for authorized_user_id in &worktree.authorized_user_ids { if let Some(visible_worktrees) = self .visible_worktrees_by_user_id - .get_mut(&collaborator_user_id) + .get_mut(&authorized_user_id) { visible_worktrees.remove(&worktree_id); } @@ -278,11 +279,11 @@ 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, }); - return Some(worktree.collaborator_user_ids.clone()); + return Some(worktree.authorized_user_ids.clone()); } } None @@ -304,9 +305,9 @@ impl Store { } let connection_ids = worktree.connection_ids(); - let collaborator_ids = worktree.collaborator_user_ids.clone(); + let authorized_user_ids = worktree.authorized_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); } @@ -317,7 +318,7 @@ impl Store { Ok(UnsharedWorktree { connection_ids, - collaborator_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.collaborator_user_ids.contains(&user_id) { + if worktree.authorized_user_ids.contains(&user_id) { Some(worktree) } else { None @@ -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) { @@ -380,14 +381,14 @@ impl Store { } let connection_ids = worktree.connection_ids(); - let collaborator_ids = worktree.collaborator_user_ids.clone(); + let authorized_user_ids = worktree.authorized_user_ids.clone(); #[cfg(test)] self.check_invariants(); Some(LeftWorktree { connection_ids, - collaborator_ids, + authorized_user_ids, }) } @@ -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 { @@ -535,29 +530,26 @@ 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 { + for authorized_user_ids in &worktree.authorized_user_ids { let visible_worktree_ids = self .visible_worktrees_by_user_id - .get(collaborator_id) + .get(authorized_user_ids) .unwrap(); assert!(visible_worktree_ids.contains(worktree_id)); } 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::>(), ); } @@ -566,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.collaborator_user_ids.contains(user_id)); + assert!(worktree.authorized_user_ids.contains(user_id)); } } @@ -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)) diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index e280b224cc9fc968dc0d49e5a976404c0fa04067..716ce4a46a42faa14409b3c18ba1e4be02cba5e6 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, @@ -42,8 +42,10 @@ pub struct Workspace { pub struct Titlebar { #[serde(flatten)] pub container: ContainerStyle, + 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, @@ -52,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)] @@ -137,7 +147,7 @@ pub struct ProjectPanelEntry { } #[derive(Deserialize, Default)] -pub struct PeoplePanel { +pub struct ContactsPanel { #[serde(flatten)] pub container: ContainerStyle, pub host_row_height: f32, @@ -275,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 5901d694d30d4d5862ddf73b67f00b4aec7c3f82..1f11f116936717acc9f5c17eb11c791b97b0b7c8 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -5,9 +5,15 @@ 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, + 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, }; @@ -27,6 +33,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; +use theme::Theme; action!(OpenNew, WorkspaceParams); action!(Save); @@ -348,6 +355,7 @@ impl Workspace { Project::new( params.languages.clone(), params.client.clone(), + params.user_store.clone(), params.fs.clone(), ) }); @@ -951,25 +959,100 @@ 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(), + self.project + .read(cx) + .active_worktree() + .map(|worktree| worktree.read(cx).replica_id()), + theme, + cx, + ), + ) + .with_children(self.render_connection_status()) + .boxed(), + ) + .right() + .boxed(), + ) + .boxed(), + ) + .with_style(theme.workspace.titlebar.container) + .boxed(), + ) + .with_height(theme.workspace.titlebar.height) + .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 collaborators = active_worktree + .read(cx) + .collaborators() + .values() + .cloned() + .collect::>(); + for collaborator in collaborators { + elements.push(self.render_avatar( + Some(&collaborator.user), + Some(collaborator.replica_id), + theme, + cx, + )); + } + } + elements + } + + 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) - .boxed(), + 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( + 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(), ) - .with_width(theme.workspace.titlebar.avatar_width) .boxed(), - ) - .boxed(), ) .with_width(theme.workspace.right_sidebar.width) .boxed() @@ -1007,38 +1090,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, @@ -1106,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(), + }) + } +} 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..7d25ee87fc2ab6c5341763138c244328e5d70ad5 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -6,11 +6,13 @@ 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 -icon_color = "$text.2.color" +avatar_width = 18 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] @@ -46,7 +48,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] @@ -146,38 +148,38 @@ 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 } +host_avatar = { corner_radius = 10, width = 18 } 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 = { corner_radius = 8, width = 14 } 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);