Remove projects from contact updates

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/client/src/user.rs                   |  34 -
crates/collab/src/integration_tests.rs      | 449 ---------------
crates/collab/src/rpc/store.rs              |  36 -
crates/contacts_panel/src/contacts_panel.rs | 617 ----------------------
crates/rpc/proto/zed.proto                  |  11 
crates/workspace/src/waiting_room.rs        | 185 ------
crates/workspace/src/workspace.rs           |  32 -
7 files changed, 35 insertions(+), 1,329 deletions(-)

Detailed changes

crates/client/src/user.rs 🔗

@@ -1,7 +1,7 @@
 use super::{http::HttpClient, proto, Client, Status, TypedEnvelope};
 use crate::incoming_call::IncomingCall;
 use anyhow::{anyhow, Context, Result};
-use collections::{hash_map::Entry, BTreeSet, HashMap, HashSet};
+use collections::{hash_map::Entry, HashMap, HashSet};
 use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
 use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task};
 use postage::{sink::Sink, watch};
@@ -40,14 +40,6 @@ impl Eq for User {}
 pub struct Contact {
     pub user: Arc<User>,
     pub online: bool,
-    pub projects: Vec<ProjectMetadata>,
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct ProjectMetadata {
-    pub id: u64,
-    pub visible_worktree_root_names: Vec<String>,
-    pub guests: BTreeSet<Arc<User>>,
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -290,7 +282,6 @@ impl UserStore {
                 let mut user_ids = HashSet::default();
                 for contact in &message.contacts {
                     user_ids.insert(contact.user_id);
-                    user_ids.extend(contact.projects.iter().flat_map(|w| &w.guests).copied());
                 }
                 user_ids.extend(message.incoming_requests.iter().map(|req| req.requester_id));
                 user_ids.extend(message.outgoing_requests.iter());
@@ -688,34 +679,11 @@ impl Contact {
                 user_store.get_user(contact.user_id, cx)
             })
             .await?;
-        let mut projects = Vec::new();
-        for project in contact.projects {
-            let mut guests = BTreeSet::new();
-            for participant_id in project.guests {
-                guests.insert(
-                    user_store
-                        .update(cx, |user_store, cx| user_store.get_user(participant_id, cx))
-                        .await?,
-                );
-            }
-            projects.push(ProjectMetadata {
-                id: project.id,
-                visible_worktree_root_names: project.visible_worktree_root_names.clone(),
-                guests,
-            });
-        }
         Ok(Self {
             user,
             online: contact.online,
-            projects,
         })
     }
-
-    pub fn non_empty_projects(&self) -> impl Iterator<Item = &ProjectMetadata> {
-        self.projects
-            .iter()
-            .filter(|project| !project.visible_worktree_root_names.is_empty())
-    }
 }
 
 async fn fetch_avatar(http: &dyn HttpClient, url: &str) -> Result<Arc<ImageData>> {

crates/collab/src/integration_tests.rs 🔗

@@ -8,7 +8,7 @@ use anyhow::anyhow;
 use call::Room;
 use client::{
     self, proto, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection,
-    Credentials, EstablishConnectionError, ProjectMetadata, UserStore, RECEIVE_TIMEOUT,
+    Credentials, EstablishConnectionError, UserStore, RECEIVE_TIMEOUT,
 };
 use collections::{BTreeMap, HashMap, HashSet};
 use editor::{
@@ -731,294 +731,6 @@ async fn test_cancel_join_request(
     );
 }
 
-#[gpui::test(iterations = 10)]
-async fn test_offline_projects(
-    deterministic: Arc<Deterministic>,
-    cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
-) {
-    cx_a.foreground().forbid_parking();
-    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
-    let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
-    let user_a = UserId::from_proto(client_a.user_id().unwrap());
-    server
-        .make_contacts(vec![
-            (&client_a, cx_a),
-            (&client_b, cx_b),
-            (&client_c, cx_c),
-        ])
-        .await;
-
-    // Set up observers of the project and user stores. Any time either of
-    // these models update, they should be in a consistent state with each
-    // other. There should not be an observable moment where the current
-    // user's contact entry contains a project that does not match one of
-    // the current open projects. That would cause a duplicate entry to be
-    // shown in the contacts panel.
-    let mut subscriptions = vec![];
-    let (window_id, view) = cx_a.add_window(|cx| {
-        subscriptions.push(cx.observe(&client_a.user_store, {
-            let project_store = client_a.project_store.clone();
-            let user_store = client_a.user_store.clone();
-            move |_, _, cx| check_project_list(project_store.clone(), user_store.clone(), cx)
-        }));
-
-        subscriptions.push(cx.observe(&client_a.project_store, {
-            let project_store = client_a.project_store.clone();
-            let user_store = client_a.user_store.clone();
-            move |_, _, cx| check_project_list(project_store.clone(), user_store.clone(), cx)
-        }));
-
-        fn check_project_list(
-            project_store: ModelHandle<ProjectStore>,
-            user_store: ModelHandle<UserStore>,
-            cx: &mut gpui::MutableAppContext,
-        ) {
-            let user_store = user_store.read(cx);
-            for contact in user_store.contacts() {
-                if contact.user.id == user_store.current_user().unwrap().id {
-                    for project in &contact.projects {
-                        let store_contains_project = project_store
-                            .read(cx)
-                            .projects(cx)
-                            .filter_map(|project| project.read(cx).remote_id())
-                            .any(|x| x == project.id);
-
-                        if !store_contains_project {
-                            panic!(
-                                concat!(
-                                    "current user's contact data has a project",
-                                    "that doesn't match any open project {:?}",
-                                ),
-                                project
-                            );
-                        }
-                    }
-                }
-            }
-        }
-
-        EmptyView
-    });
-
-    // Build an offline project with two worktrees.
-    client_a
-        .fs
-        .insert_tree(
-            "/code",
-            json!({
-                "crate1": { "a.rs": "" },
-                "crate2": { "b.rs": "" },
-            }),
-        )
-        .await;
-    let project = cx_a.update(|cx| {
-        Project::local(
-            false,
-            client_a.client.clone(),
-            client_a.user_store.clone(),
-            client_a.project_store.clone(),
-            client_a.language_registry.clone(),
-            client_a.fs.clone(),
-            cx,
-        )
-    });
-    project
-        .update(cx_a, |p, cx| {
-            p.find_or_create_local_worktree("/code/crate1", true, cx)
-        })
-        .await
-        .unwrap();
-    project
-        .update(cx_a, |p, cx| {
-            p.find_or_create_local_worktree("/code/crate2", true, cx)
-        })
-        .await
-        .unwrap();
-    project
-        .update(cx_a, |p, cx| p.restore_state(cx))
-        .await
-        .unwrap();
-
-    // When a project is offline, we still create it on the server but is invisible
-    // to other users.
-    deterministic.run_until_parked();
-    assert!(server
-        .store
-        .lock()
-        .await
-        .project_metadata_for_user(user_a)
-        .is_empty());
-    project.read_with(cx_a, |project, _| {
-        assert!(project.remote_id().is_some());
-        assert!(!project.is_online());
-    });
-    assert!(client_b
-        .user_store
-        .read_with(cx_b, |store, _| { store.contacts()[0].projects.is_empty() }));
-
-    // When the project is taken online, its metadata is sent to the server
-    // and broadcasted to other users.
-    project.update(cx_a, |p, cx| p.set_online(true, cx));
-    deterministic.run_until_parked();
-    let project_id = project.read_with(cx_a, |p, _| p.remote_id()).unwrap();
-    client_b.user_store.read_with(cx_b, |store, _| {
-        assert_eq!(
-            store.contacts()[0].projects,
-            &[ProjectMetadata {
-                id: project_id,
-                visible_worktree_root_names: vec!["crate1".into(), "crate2".into()],
-                guests: Default::default(),
-            }]
-        );
-    });
-
-    // The project is registered again when the host loses and regains connection.
-    server.disconnect_client(user_a);
-    server.forbid_connections();
-    cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
-    assert!(server
-        .store
-        .lock()
-        .await
-        .project_metadata_for_user(user_a)
-        .is_empty());
-    assert!(project.read_with(cx_a, |p, _| p.remote_id().is_none()));
-    assert!(client_b
-        .user_store
-        .read_with(cx_b, |store, _| { store.contacts()[0].projects.is_empty() }));
-
-    server.allow_connections();
-    cx_b.foreground().advance_clock(Duration::from_secs(10));
-    let project_id = project.read_with(cx_a, |p, _| p.remote_id()).unwrap();
-    client_b.user_store.read_with(cx_b, |store, _| {
-        assert_eq!(
-            store.contacts()[0].projects,
-            &[ProjectMetadata {
-                id: project_id,
-                visible_worktree_root_names: vec!["crate1".into(), "crate2".into()],
-                guests: Default::default(),
-            }]
-        );
-    });
-
-    project
-        .update(cx_a, |p, cx| {
-            p.find_or_create_local_worktree("/code/crate3", true, cx)
-        })
-        .await
-        .unwrap();
-    deterministic.run_until_parked();
-    client_b.user_store.read_with(cx_b, |store, _| {
-        assert_eq!(
-            store.contacts()[0].projects,
-            &[ProjectMetadata {
-                id: project_id,
-                visible_worktree_root_names: vec![
-                    "crate1".into(),
-                    "crate2".into(),
-                    "crate3".into()
-                ],
-                guests: Default::default(),
-            }]
-        );
-    });
-
-    // Build another project using a directory which was previously part of
-    // an online project. Restore the project's state from the host's database.
-    let project2_a = cx_a.update(|cx| {
-        Project::local(
-            false,
-            client_a.client.clone(),
-            client_a.user_store.clone(),
-            client_a.project_store.clone(),
-            client_a.language_registry.clone(),
-            client_a.fs.clone(),
-            cx,
-        )
-    });
-    project2_a
-        .update(cx_a, |p, cx| {
-            p.find_or_create_local_worktree("/code/crate3", true, cx)
-        })
-        .await
-        .unwrap();
-    project2_a
-        .update(cx_a, |project, cx| project.restore_state(cx))
-        .await
-        .unwrap();
-
-    // This project is now online, because its directory was previously online.
-    project2_a.read_with(cx_a, |project, _| assert!(project.is_online()));
-    deterministic.run_until_parked();
-    let project2_id = project2_a.read_with(cx_a, |p, _| p.remote_id()).unwrap();
-    client_b.user_store.read_with(cx_b, |store, _| {
-        assert_eq!(
-            store.contacts()[0].projects,
-            &[
-                ProjectMetadata {
-                    id: project_id,
-                    visible_worktree_root_names: vec![
-                        "crate1".into(),
-                        "crate2".into(),
-                        "crate3".into()
-                    ],
-                    guests: Default::default(),
-                },
-                ProjectMetadata {
-                    id: project2_id,
-                    visible_worktree_root_names: vec!["crate3".into()],
-                    guests: Default::default(),
-                }
-            ]
-        );
-    });
-
-    let project2_b = client_b.build_remote_project(&project2_a, cx_a, cx_b).await;
-    let project2_c = cx_c.foreground().spawn(Project::remote(
-        project2_id,
-        client_c.client.clone(),
-        client_c.user_store.clone(),
-        client_c.project_store.clone(),
-        client_c.language_registry.clone(),
-        FakeFs::new(cx_c.background()),
-        cx_c.to_async(),
-    ));
-    deterministic.run_until_parked();
-
-    // Taking a project offline unshares the project, rejects any pending join request and
-    // disconnects existing guests.
-    project2_a.update(cx_a, |project, cx| project.set_online(false, cx));
-    deterministic.run_until_parked();
-    project2_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
-    project2_b.read_with(cx_b, |project, _| assert!(project.is_read_only()));
-    project2_c.await.unwrap_err();
-
-    client_b.user_store.read_with(cx_b, |store, _| {
-        assert_eq!(
-            store.contacts()[0].projects,
-            &[ProjectMetadata {
-                id: project_id,
-                visible_worktree_root_names: vec![
-                    "crate1".into(),
-                    "crate2".into(),
-                    "crate3".into()
-                ],
-                guests: Default::default(),
-            },]
-        );
-    });
-
-    cx_a.update(|cx| {
-        drop(subscriptions);
-        drop(view);
-        cx.remove_window(window_id);
-    });
-}
-
 #[gpui::test(iterations = 10)]
 async fn test_propagate_saves_and_fs_changes(
     cx_a: &mut TestAppContext,
@@ -3911,24 +3623,15 @@ async fn test_contacts(
     deterministic.run_until_parked();
     assert_eq!(
         contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), true, vec![]),
-            ("user_c".to_string(), true, vec![])
-        ]
+        [("user_b".to_string(), true), ("user_c".to_string(), true)]
     );
     assert_eq!(
         contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), true, vec![]),
-            ("user_c".to_string(), true, vec![])
-        ]
+        [("user_a".to_string(), true), ("user_c".to_string(), true)]
     );
     assert_eq!(
         contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), true, vec![]),
-            ("user_b".to_string(), true, vec![])
-        ]
+        [("user_a".to_string(), true), ("user_b".to_string(), true)]
     );
 
     // Share a project as client A.
@@ -3938,24 +3641,15 @@ async fn test_contacts(
     deterministic.run_until_parked();
     assert_eq!(
         contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), true, vec![]),
-            ("user_c".to_string(), true, vec![])
-        ]
+        [("user_b".to_string(), true), ("user_c".to_string(), true)]
     );
     assert_eq!(
         contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), true, vec![("a".to_string(), vec![])]),
-            ("user_c".to_string(), true, vec![])
-        ]
+        [("user_a".to_string(), true), ("user_c".to_string(), true)]
     );
     assert_eq!(
         contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), true, vec![("a".to_string(), vec![])]),
-            ("user_b".to_string(), true, vec![])
-        ]
+        [("user_a".to_string(), true), ("user_b".to_string(), true)]
     );
 
     let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
@@ -3963,32 +3657,15 @@ async fn test_contacts(
     deterministic.run_until_parked();
     assert_eq!(
         contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), true, vec![]),
-            ("user_c".to_string(), true, vec![])
-        ]
+        [("user_b".to_string(), true), ("user_c".to_string(), true)]
     );
     assert_eq!(
         contacts(&client_b, cx_b),
-        [
-            (
-                "user_a".to_string(),
-                true,
-                vec![("a".to_string(), vec!["user_b".to_string()])]
-            ),
-            ("user_c".to_string(), true, vec![])
-        ]
+        [("user_a".to_string(), true,), ("user_c".to_string(), true)]
     );
     assert_eq!(
         contacts(&client_c, cx_c),
-        [
-            (
-                "user_a".to_string(),
-                true,
-                vec![("a".to_string(), vec!["user_b".to_string()])]
-            ),
-            ("user_b".to_string(), true, vec![])
-        ]
+        [("user_a".to_string(), true,), ("user_b".to_string(), true)]
     );
 
     // Add a local project as client B
@@ -3998,32 +3675,15 @@ async fn test_contacts(
     deterministic.run_until_parked();
     assert_eq!(
         contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), true, vec![("b".to_string(), vec![])]),
-            ("user_c".to_string(), true, vec![])
-        ]
+        [("user_b".to_string(), true), ("user_c".to_string(), true)]
     );
     assert_eq!(
         contacts(&client_b, cx_b),
-        [
-            (
-                "user_a".to_string(),
-                true,
-                vec![("a".to_string(), vec!["user_b".to_string()])]
-            ),
-            ("user_c".to_string(), true, vec![])
-        ]
+        [("user_a".to_string(), true), ("user_c".to_string(), true)]
     );
     assert_eq!(
         contacts(&client_c, cx_c),
-        [
-            (
-                "user_a".to_string(),
-                true,
-                vec![("a".to_string(), vec!["user_b".to_string()])]
-            ),
-            ("user_b".to_string(), true, vec![("b".to_string(), vec![])])
-        ]
+        [("user_a".to_string(), true,), ("user_b".to_string(), true)]
     );
 
     project_a
@@ -4036,24 +3696,15 @@ async fn test_contacts(
     deterministic.run_until_parked();
     assert_eq!(
         contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), true, vec![("b".to_string(), vec![])]),
-            ("user_c".to_string(), true, vec![])
-        ]
+        [("user_b".to_string(), true), ("user_c".to_string(), true)]
     );
     assert_eq!(
         contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), true, vec![]),
-            ("user_c".to_string(), true, vec![])
-        ]
+        [("user_a".to_string(), true), ("user_c".to_string(), true)]
     );
     assert_eq!(
         contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), true, vec![]),
-            ("user_b".to_string(), true, vec![("b".to_string(), vec![])])
-        ]
+        [("user_a".to_string(), true), ("user_b".to_string(), true)]
     );
 
     server.disconnect_client(client_c.current_user_id(cx_c));
@@ -4061,17 +3712,11 @@ async fn test_contacts(
     deterministic.advance_clock(rpc::RECEIVE_TIMEOUT);
     assert_eq!(
         contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), true, vec![("b".to_string(), vec![])]),
-            ("user_c".to_string(), false, vec![])
-        ]
+        [("user_b".to_string(), true), ("user_c".to_string(), false)]
     );
     assert_eq!(
         contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), true, vec![]),
-            ("user_c".to_string(), false, vec![])
-        ]
+        [("user_a".to_string(), true), ("user_c".to_string(), false)]
     );
     assert_eq!(contacts(&client_c, cx_c), []);
 
@@ -4084,48 +3729,24 @@ async fn test_contacts(
     deterministic.run_until_parked();
     assert_eq!(
         contacts(&client_a, cx_a),
-        [
-            ("user_b".to_string(), true, vec![("b".to_string(), vec![])]),
-            ("user_c".to_string(), true, vec![])
-        ]
+        [("user_b".to_string(), true), ("user_c".to_string(), true)]
     );
     assert_eq!(
         contacts(&client_b, cx_b),
-        [
-            ("user_a".to_string(), true, vec![]),
-            ("user_c".to_string(), true, vec![])
-        ]
+        [("user_a".to_string(), true), ("user_c".to_string(), true)]
     );
     assert_eq!(
         contacts(&client_c, cx_c),
-        [
-            ("user_a".to_string(), true, vec![]),
-            ("user_b".to_string(), true, vec![("b".to_string(), vec![])])
-        ]
+        [("user_a".to_string(), true), ("user_b".to_string(), true)]
     );
 
     #[allow(clippy::type_complexity)]
-    fn contacts(
-        client: &TestClient,
-        cx: &TestAppContext,
-    ) -> Vec<(String, bool, Vec<(String, Vec<String>)>)> {
+    fn contacts(client: &TestClient, cx: &TestAppContext) -> Vec<(String, bool)> {
         client.user_store.read_with(cx, |store, _| {
             store
                 .contacts()
                 .iter()
-                .map(|contact| {
-                    let projects = contact
-                        .projects
-                        .iter()
-                        .map(|p| {
-                            (
-                                p.visible_worktree_root_names[0].clone(),
-                                p.guests.iter().map(|p| p.github_login.clone()).collect(),
-                            )
-                        })
-                        .collect();
-                    (contact.user.github_login.clone(), contact.online, projects)
-                })
+                .map(|contact| (contact.user.github_login.clone(), contact.online))
                 .collect()
         })
     }
@@ -5155,22 +4776,6 @@ async fn test_random_collaboration(
                     log::error!("{} error - {:?}", guest.username, guest_err);
                 }
 
-                let contacts = server
-                    .app_state
-                    .db
-                    .get_contacts(guest.current_user_id(&guest_cx))
-                    .await
-                    .unwrap();
-                let contacts = server
-                    .store
-                    .lock()
-                    .await
-                    .build_initial_contacts_update(contacts)
-                    .contacts;
-                assert!(!contacts
-                    .iter()
-                    .flat_map(|contact| &contact.projects)
-                    .any(|project| project.id == host_project_id));
                 guest_project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
                 guest_cx.update(|_| drop((guest, guest_project)));
             }
@@ -5259,14 +4864,6 @@ async fn test_random_collaboration(
                                 "removed guest is still a contact of another peer"
                             );
                         }
-                        for project in contact.projects {
-                            for project_guest_id in project.guests {
-                                assert_ne!(
-                                    project_guest_id, removed_guest_id.0 as u64,
-                                    "removed guest appears as still participating on a project"
-                                );
-                            }
-                        }
                     }
                 }
 

crates/collab/src/rpc/store.rs 🔗

@@ -345,47 +345,11 @@ impl Store {
     pub fn contact_for_user(&self, user_id: UserId, should_notify: bool) -> proto::Contact {
         proto::Contact {
             user_id: user_id.to_proto(),
-            projects: self.project_metadata_for_user(user_id),
             online: self.is_user_online(user_id),
             should_notify,
         }
     }
 
-    pub fn project_metadata_for_user(&self, user_id: UserId) -> Vec<proto::ProjectMetadata> {
-        let user_connection_state = self.connected_users.get(&user_id);
-        let project_ids = user_connection_state.iter().flat_map(|state| {
-            state
-                .connection_ids
-                .iter()
-                .filter_map(|connection_id| self.connections.get(connection_id))
-                .flat_map(|connection| connection.projects.iter().copied())
-        });
-
-        let mut metadata = Vec::new();
-        for project_id in project_ids {
-            if let Some(project) = self.projects.get(&project_id) {
-                if project.host.user_id == user_id && project.online {
-                    metadata.push(proto::ProjectMetadata {
-                        id: project_id.to_proto(),
-                        visible_worktree_root_names: project
-                            .worktrees
-                            .values()
-                            .filter(|worktree| worktree.visible)
-                            .map(|worktree| worktree.root_name.clone())
-                            .collect(),
-                        guests: project
-                            .guests
-                            .values()
-                            .map(|guest| guest.user_id.to_proto())
-                            .collect(),
-                    });
-                }
-            }
-        }
-
-        metadata
-    }
-
     pub fn create_room(&mut self, creator_connection_id: ConnectionId) -> Result<RoomId> {
         let connection = self
             .connections

crates/contacts_panel/src/contacts_panel.rs 🔗

@@ -8,23 +8,19 @@ use contact_notification::ContactNotification;
 use editor::{Cancel, Editor};
 use fuzzy::{match_strings, StringMatchCandidate};
 use gpui::{
-    actions,
-    elements::*,
-    geometry::{rect::RectF, vector::vec2f},
-    impl_actions, impl_internal_actions,
-    platform::CursorStyle,
+    actions, elements::*, impl_actions, impl_internal_actions, platform::CursorStyle,
     AnyViewHandle, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
     MouseButton, MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle,
-    WeakModelHandle, WeakViewHandle,
+    WeakViewHandle,
 };
 use join_project_notification::JoinProjectNotification;
 use menu::{Confirm, SelectNext, SelectPrev};
-use project::{Project, ProjectStore};
+use project::ProjectStore;
 use serde::Deserialize;
 use settings::Settings;
-use std::{ops::DerefMut, sync::Arc};
+use std::sync::Arc;
 use theme::IconButton;
-use workspace::{sidebar::SidebarItem, JoinProject, ToggleProjectOnline, Workspace};
+use workspace::{sidebar::SidebarItem, Workspace};
 
 actions!(contacts_panel, [ToggleFocus]);
 
@@ -48,8 +44,6 @@ enum ContactEntry {
     IncomingRequest(Arc<User>),
     OutgoingRequest(Arc<User>),
     Contact(Arc<Contact>),
-    ContactProject(Arc<Contact>, usize, Option<WeakModelHandle<Project>>),
-    OfflineProject(WeakModelHandle<Project>),
 }
 
 #[derive(Clone, PartialEq)]
@@ -181,7 +175,6 @@ impl ContactsPanel {
 
         let list_state = ListState::new(0, Orientation::Top, 1000., cx, move |this, ix, cx| {
             let theme = cx.global::<Settings>().theme.clone();
-            let current_user_id = this.user_store.read(cx).current_user().map(|user| user.id);
             let is_selected = this.selection == Some(ix);
 
             match &this.entries[ix] {
@@ -214,34 +207,6 @@ impl ContactsPanel {
                 ContactEntry::Contact(contact) => {
                     Self::render_contact(&contact.user, &theme.contacts_panel, is_selected)
                 }
-                ContactEntry::ContactProject(contact, project_ix, open_project) => {
-                    let is_last_project_for_contact =
-                        this.entries.get(ix + 1).map_or(true, |next| {
-                            if let ContactEntry::ContactProject(next_contact, _, _) = next {
-                                next_contact.user.id != contact.user.id
-                            } else {
-                                true
-                            }
-                        });
-                    Self::render_project(
-                        contact.clone(),
-                        current_user_id,
-                        *project_ix,
-                        *open_project,
-                        &theme.contacts_panel,
-                        &theme.tooltip,
-                        is_last_project_for_contact,
-                        is_selected,
-                        cx,
-                    )
-                }
-                ContactEntry::OfflineProject(project) => Self::render_offline_project(
-                    *project,
-                    &theme.contacts_panel,
-                    &theme.tooltip,
-                    is_selected,
-                    cx,
-                ),
             }
         });
 
@@ -343,260 +308,6 @@ impl ContactsPanel {
             .boxed()
     }
 
-    #[allow(clippy::too_many_arguments)]
-    fn render_project(
-        contact: Arc<Contact>,
-        current_user_id: Option<u64>,
-        project_index: usize,
-        open_project: Option<WeakModelHandle<Project>>,
-        theme: &theme::ContactsPanel,
-        tooltip_style: &TooltipStyle,
-        is_last_project: bool,
-        is_selected: bool,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
-        enum ToggleOnline {}
-
-        let project = &contact.projects[project_index];
-        let project_id = project.id;
-        let is_host = Some(contact.user.id) == current_user_id;
-        let open_project = open_project.and_then(|p| p.upgrade(cx.deref_mut()));
-
-        let font_cache = cx.font_cache();
-        let host_avatar_height = theme
-            .contact_avatar
-            .width
-            .or(theme.contact_avatar.height)
-            .unwrap_or(0.);
-        let row = &theme.project_row.default;
-        let tree_branch = theme.tree_branch;
-        let line_height = row.name.text.line_height(font_cache);
-        let cap_height = row.name.text.cap_height(font_cache);
-        let baseline_offset =
-            row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
-
-        MouseEventHandler::<JoinProject>::new(project_id as usize, cx, |mouse_state, cx| {
-            let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
-            let row = theme.project_row.style_for(mouse_state, is_selected);
-
-            Flex::row()
-                .with_child(
-                    Stack::new()
-                        .with_child(
-                            Canvas::new(move |bounds, _, cx| {
-                                let start_x = bounds.min_x() + (bounds.width() / 2.)
-                                    - (tree_branch.width / 2.);
-                                let end_x = bounds.max_x();
-                                let start_y = bounds.min_y();
-                                let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
-
-                                cx.scene.push_quad(gpui::Quad {
-                                    bounds: RectF::from_points(
-                                        vec2f(start_x, start_y),
-                                        vec2f(
-                                            start_x + tree_branch.width,
-                                            if is_last_project {
-                                                end_y
-                                            } else {
-                                                bounds.max_y()
-                                            },
-                                        ),
-                                    ),
-                                    background: Some(tree_branch.color),
-                                    border: gpui::Border::default(),
-                                    corner_radius: 0.,
-                                });
-                                cx.scene.push_quad(gpui::Quad {
-                                    bounds: RectF::from_points(
-                                        vec2f(start_x, end_y),
-                                        vec2f(end_x, end_y + tree_branch.width),
-                                    ),
-                                    background: Some(tree_branch.color),
-                                    border: gpui::Border::default(),
-                                    corner_radius: 0.,
-                                });
-                            })
-                            .boxed(),
-                        )
-                        .with_children(open_project.and_then(|open_project| {
-                            let is_going_offline = !open_project.read(cx).is_online();
-                            if !mouse_state.hovered && !is_going_offline {
-                                return None;
-                            }
-
-                            let button = MouseEventHandler::<ToggleProjectOnline>::new(
-                                project_id as usize,
-                                cx,
-                                |state, _| {
-                                    let mut icon_style =
-                                        *theme.private_button.style_for(state, false);
-                                    icon_style.container.background_color =
-                                        row.container.background_color;
-                                    if is_going_offline {
-                                        icon_style.color = theme.disabled_button.color;
-                                    }
-                                    render_icon_button(&icon_style, "icons/lock_8.svg")
-                                        .aligned()
-                                        .boxed()
-                                },
-                            );
-
-                            if is_going_offline {
-                                Some(button.boxed())
-                            } else {
-                                Some(
-                                    button
-                                        .with_cursor_style(CursorStyle::PointingHand)
-                                        .on_click(MouseButton::Left, move |_, cx| {
-                                            cx.dispatch_action(ToggleProjectOnline {
-                                                project: Some(open_project.clone()),
-                                            })
-                                        })
-                                        .with_tooltip::<ToggleOnline, _>(
-                                            project_id as usize,
-                                            "Take project offline".to_string(),
-                                            None,
-                                            tooltip_style.clone(),
-                                            cx,
-                                        )
-                                        .boxed(),
-                                )
-                            }
-                        }))
-                        .constrained()
-                        .with_width(host_avatar_height)
-                        .boxed(),
-                )
-                .with_child(
-                    Label::new(
-                        project.visible_worktree_root_names.join(", "),
-                        row.name.text.clone(),
-                    )
-                    .aligned()
-                    .left()
-                    .contained()
-                    .with_style(row.name.container)
-                    .flex(1., false)
-                    .boxed(),
-                )
-                .with_children(project.guests.iter().filter_map(|participant| {
-                    participant.avatar.clone().map(|avatar| {
-                        Image::new(avatar)
-                            .with_style(row.guest_avatar)
-                            .aligned()
-                            .left()
-                            .contained()
-                            .with_margin_right(row.guest_avatar_spacing)
-                            .boxed()
-                    })
-                }))
-                .constrained()
-                .with_height(theme.row_height)
-                .contained()
-                .with_style(row.container)
-                .boxed()
-        })
-        .with_cursor_style(if !is_host {
-            CursorStyle::PointingHand
-        } else {
-            CursorStyle::Arrow
-        })
-        .on_click(MouseButton::Left, move |_, cx| {
-            if !is_host {
-                cx.dispatch_global_action(JoinProject {
-                    contact: contact.clone(),
-                    project_index,
-                });
-            }
-        })
-        .boxed()
-    }
-
-    fn render_offline_project(
-        project_handle: WeakModelHandle<Project>,
-        theme: &theme::ContactsPanel,
-        tooltip_style: &TooltipStyle,
-        is_selected: bool,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
-        let host_avatar_height = theme
-            .contact_avatar
-            .width
-            .or(theme.contact_avatar.height)
-            .unwrap_or(0.);
-
-        enum LocalProject {}
-        enum ToggleOnline {}
-
-        let project_id = project_handle.id();
-        MouseEventHandler::<LocalProject>::new(project_id, cx, |state, cx| {
-            let row = theme.project_row.style_for(state, is_selected);
-            let mut worktree_root_names = String::new();
-            let project = if let Some(project) = project_handle.upgrade(cx.deref_mut()) {
-                project.read(cx)
-            } else {
-                return Empty::new().boxed();
-            };
-            let is_going_online = project.is_online();
-            for tree in project.visible_worktrees(cx) {
-                if !worktree_root_names.is_empty() {
-                    worktree_root_names.push_str(", ");
-                }
-                worktree_root_names.push_str(tree.read(cx).root_name());
-            }
-
-            Flex::row()
-                .with_child({
-                    let button =
-                        MouseEventHandler::<ToggleOnline>::new(project_id, cx, |state, _| {
-                            let mut style = *theme.private_button.style_for(state, false);
-                            if is_going_online {
-                                style.color = theme.disabled_button.color;
-                            }
-                            render_icon_button(&style, "icons/lock_8.svg")
-                                .aligned()
-                                .constrained()
-                                .with_width(host_avatar_height)
-                                .boxed()
-                        });
-
-                    if is_going_online {
-                        button.boxed()
-                    } else {
-                        button
-                            .with_cursor_style(CursorStyle::PointingHand)
-                            .on_click(MouseButton::Left, move |_, cx| {
-                                let project = project_handle.upgrade(cx.app);
-                                cx.dispatch_action(ToggleProjectOnline { project })
-                            })
-                            .with_tooltip::<ToggleOnline, _>(
-                                project_id,
-                                "Take project online".to_string(),
-                                None,
-                                tooltip_style.clone(),
-                                cx,
-                            )
-                            .boxed()
-                    }
-                })
-                .with_child(
-                    Label::new(worktree_root_names, row.name.text.clone())
-                        .aligned()
-                        .left()
-                        .contained()
-                        .with_style(row.name.container)
-                        .flex(1., false)
-                        .boxed(),
-                )
-                .constrained()
-                .with_height(theme.row_height)
-                .contained()
-                .with_style(row.container)
-                .boxed()
-        })
-        .boxed()
-    }
-
     fn render_contact_request(
         user: Arc<User>,
         user_store: ModelHandle<UserStore>,
@@ -710,7 +421,6 @@ impl ContactsPanel {
 
     fn update_entries(&mut self, cx: &mut ViewContext<Self>) {
         let user_store = self.user_store.read(cx);
-        let project_store = self.project_store.read(cx);
         let query = self.filter_editor.read(cx).text(cx);
         let executor = cx.background().clone();
 
@@ -837,60 +547,6 @@ impl ContactsPanel {
                         for mat in matches {
                             let contact = &contacts[mat.candidate_id];
                             self.entries.push(ContactEntry::Contact(contact.clone()));
-
-                            let is_current_user = current_user
-                                .as_ref()
-                                .map_or(false, |user| user.id == contact.user.id);
-                            if is_current_user {
-                                let mut open_projects =
-                                    project_store.projects(cx).collect::<Vec<_>>();
-                                self.entries.extend(
-                                    contact.projects.iter().enumerate().filter_map(
-                                        |(ix, project)| {
-                                            let open_project = open_projects
-                                                .iter()
-                                                .position(|p| {
-                                                    p.read(cx).remote_id() == Some(project.id)
-                                                })
-                                                .map(|ix| open_projects.remove(ix).downgrade());
-                                            if project.visible_worktree_root_names.is_empty() {
-                                                None
-                                            } else {
-                                                Some(ContactEntry::ContactProject(
-                                                    contact.clone(),
-                                                    ix,
-                                                    open_project,
-                                                ))
-                                            }
-                                        },
-                                    ),
-                                );
-                                self.entries.extend(open_projects.into_iter().filter_map(
-                                    |project| {
-                                        if project.read(cx).visible_worktrees(cx).next().is_none() {
-                                            None
-                                        } else {
-                                            Some(ContactEntry::OfflineProject(project.downgrade()))
-                                        }
-                                    },
-                                ));
-                            } else {
-                                self.entries.extend(
-                                    contact.projects.iter().enumerate().filter_map(
-                                        |(ix, project)| {
-                                            if project.visible_worktree_root_names.is_empty() {
-                                                None
-                                            } else {
-                                                Some(ContactEntry::ContactProject(
-                                                    contact.clone(),
-                                                    ix,
-                                                    None,
-                                                ))
-                                            }
-                                        },
-                                    ),
-                                );
-                            }
                         }
                     }
                 }
@@ -981,18 +637,6 @@ impl ContactsPanel {
                         let section = *section;
                         self.toggle_expanded(&ToggleExpanded(section), cx);
                     }
-                    ContactEntry::ContactProject(contact, project_index, open_project) => {
-                        if let Some(open_project) = open_project {
-                            workspace::activate_workspace_for_project(cx, |_, cx| {
-                                cx.model_id() == open_project.id()
-                            });
-                        } else {
-                            cx.dispatch_global_action(JoinProject {
-                                contact: contact.clone(),
-                                project_index: *project_index,
-                            })
-                        }
-                    }
                     _ => {}
                 }
             }
@@ -1181,16 +825,6 @@ impl PartialEq for ContactEntry {
                     return contact_1.user.id == contact_2.user.id;
                 }
             }
-            ContactEntry::ContactProject(contact_1, ix_1, _) => {
-                if let ContactEntry::ContactProject(contact_2, ix_2, _) = other {
-                    return contact_1.user.id == contact_2.user.id && ix_1 == ix_2;
-                }
-            }
-            ContactEntry::OfflineProject(project_1) => {
-                if let ContactEntry::OfflineProject(project_2) = other {
-                    return project_1.id() == project_2.id();
-                }
-            }
         }
         false
     }
@@ -1205,7 +839,7 @@ mod tests {
         Client,
     };
     use collections::HashSet;
-    use gpui::{serde_json::json, TestAppContext};
+    use gpui::TestAppContext;
     use language::LanguageRegistry;
     use project::{FakeFs, Project};
 
@@ -1221,8 +855,6 @@ mod tests {
         let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
         let server = FakeServer::for_client(current_user_id, &client, cx).await;
         let fs = FakeFs::new(cx.background());
-        fs.insert_tree("/private_dir", json!({ "one.rs": "" }))
-            .await;
         let project = cx.update(|cx| {
             Project::local(
                 false,
@@ -1234,14 +866,6 @@ mod tests {
                 cx,
             )
         });
-        let worktree_id = project
-            .update(cx, |project, cx| {
-                project.find_or_create_local_worktree("/private_dir", true, cx)
-            })
-            .await
-            .unwrap()
-            .0
-            .read_with(cx, |worktree, _| worktree.id().to_proto());
 
         let (_, workspace) =
             cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx));
@@ -1315,211 +939,26 @@ mod tests {
                     user_id: 3,
                     online: true,
                     should_notify: false,
-                    projects: vec![proto::ProjectMetadata {
-                        id: 101,
-                        visible_worktree_root_names: vec!["dir1".to_string()],
-                        guests: vec![2],
-                    }],
                 },
                 proto::Contact {
                     user_id: 4,
                     online: true,
                     should_notify: false,
-                    projects: vec![proto::ProjectMetadata {
-                        id: 102,
-                        visible_worktree_root_names: vec!["dir2".to_string()],
-                        guests: vec![2],
-                    }],
                 },
                 proto::Contact {
                     user_id: 5,
                     online: false,
                     should_notify: false,
-                    projects: vec![],
                 },
                 proto::Contact {
                     user_id: current_user_id,
                     online: true,
                     should_notify: false,
-                    projects: vec![proto::ProjectMetadata {
-                        id: 103,
-                        visible_worktree_root_names: vec!["dir3".to_string()],
-                        guests: vec![3],
-                    }],
                 },
             ],
             ..Default::default()
         });
 
-        assert_eq!(
-            server
-                .receive::<proto::UpdateProject>()
-                .await
-                .unwrap()
-                .payload,
-            proto::UpdateProject {
-                project_id: 200,
-                online: false,
-                worktrees: vec![]
-            },
-        );
-
-        cx.foreground().run_until_parked();
-        assert_eq!(
-            cx.read(|cx| render_to_strings(&panel, cx)),
-            &[
-                "v Requests",
-                "  incoming user_one",
-                "  outgoing user_two",
-                "v Online",
-                "  the_current_user",
-                "    dir3",
-                "    🔒 private_dir",
-                "  user_four",
-                "    dir2",
-                "  user_three",
-                "    dir1",
-                "v Offline",
-                "  user_five",
-            ]
-        );
-
-        // Take a project online. It appears as loading, since the project
-        // isn't yet visible to other contacts.
-        project.update(cx, |project, cx| project.set_online(true, cx));
-        cx.foreground().run_until_parked();
-        assert_eq!(
-            cx.read(|cx| render_to_strings(&panel, cx)),
-            &[
-                "v Requests",
-                "  incoming user_one",
-                "  outgoing user_two",
-                "v Online",
-                "  the_current_user",
-                "    dir3",
-                "    🔒 private_dir (going online...)",
-                "  user_four",
-                "    dir2",
-                "  user_three",
-                "    dir1",
-                "v Offline",
-                "  user_five",
-            ]
-        );
-
-        // The server receives the project's metadata and updates the contact metadata
-        // for the current user. Now the project appears as online.
-        assert_eq!(
-            server
-                .receive::<proto::UpdateProject>()
-                .await
-                .unwrap()
-                .payload,
-            proto::UpdateProject {
-                project_id: 200,
-                online: true,
-                worktrees: vec![proto::WorktreeMetadata {
-                    id: worktree_id,
-                    root_name: "private_dir".to_string(),
-                    visible: true,
-                }]
-            },
-        );
-        server
-            .receive::<proto::UpdateWorktreeExtensions>()
-            .await
-            .unwrap();
-
-        server.send(proto::UpdateContacts {
-            contacts: vec![proto::Contact {
-                user_id: current_user_id,
-                online: true,
-                should_notify: false,
-                projects: vec![
-                    proto::ProjectMetadata {
-                        id: 103,
-                        visible_worktree_root_names: vec!["dir3".to_string()],
-                        guests: vec![3],
-                    },
-                    proto::ProjectMetadata {
-                        id: 200,
-                        visible_worktree_root_names: vec!["private_dir".to_string()],
-                        guests: vec![3],
-                    },
-                ],
-            }],
-            ..Default::default()
-        });
-        cx.foreground().run_until_parked();
-        assert_eq!(
-            cx.read(|cx| render_to_strings(&panel, cx)),
-            &[
-                "v Requests",
-                "  incoming user_one",
-                "  outgoing user_two",
-                "v Online",
-                "  the_current_user",
-                "    dir3",
-                "    private_dir",
-                "  user_four",
-                "    dir2",
-                "  user_three",
-                "    dir1",
-                "v Offline",
-                "  user_five",
-            ]
-        );
-
-        // Take the project offline. It appears as loading.
-        project.update(cx, |project, cx| project.set_online(false, cx));
-        cx.foreground().run_until_parked();
-        assert_eq!(
-            cx.read(|cx| render_to_strings(&panel, cx)),
-            &[
-                "v Requests",
-                "  incoming user_one",
-                "  outgoing user_two",
-                "v Online",
-                "  the_current_user",
-                "    dir3",
-                "    private_dir (going offline...)",
-                "  user_four",
-                "    dir2",
-                "  user_three",
-                "    dir1",
-                "v Offline",
-                "  user_five",
-            ]
-        );
-
-        // The server receives the unregister request and updates the contact
-        // metadata for the current user. The project is now offline.
-        assert_eq!(
-            server
-                .receive::<proto::UpdateProject>()
-                .await
-                .unwrap()
-                .payload,
-            proto::UpdateProject {
-                project_id: 200,
-                online: false,
-                worktrees: vec![]
-            },
-        );
-
-        server.send(proto::UpdateContacts {
-            contacts: vec![proto::Contact {
-                user_id: current_user_id,
-                online: true,
-                should_notify: false,
-                projects: vec![proto::ProjectMetadata {
-                    id: 103,
-                    visible_worktree_root_names: vec!["dir3".to_string()],
-                    guests: vec![3],
-                }],
-            }],
-            ..Default::default()
-        });
         cx.foreground().run_until_parked();
         assert_eq!(
             cx.read(|cx| render_to_strings(&panel, cx)),
@@ -1529,12 +968,8 @@ mod tests {
                 "  outgoing user_two",
                 "v Online",
                 "  the_current_user",
-                "    dir3",
-                "    🔒 private_dir",
                 "  user_four",
-                "    dir2",
                 "  user_three",
-                "    dir1",
                 "v Offline",
                 "  user_five",
             ]
@@ -1551,7 +986,6 @@ mod tests {
             &[
                 "v Online",
                 "  user_four  <=== selected",
-                "    dir2",
                 "v Offline",
                 "  user_five",
             ]
@@ -1565,8 +999,7 @@ mod tests {
             &[
                 "v Online",
                 "  user_four",
-                "    dir2  <=== selected",
-                "v Offline",
+                "v Offline  <=== selected",
                 "  user_five",
             ]
         );
@@ -1579,9 +1012,8 @@ mod tests {
             &[
                 "v Online",
                 "  user_four",
-                "    dir2",
-                "v Offline  <=== selected",
-                "  user_five",
+                "v Offline",
+                "  user_five  <=== selected",
             ]
         );
     }
@@ -1608,37 +1040,6 @@ mod tests {
                 ContactEntry::Contact(contact) => {
                     format!("  {}", contact.user.github_login)
                 }
-                ContactEntry::ContactProject(contact, project_ix, project) => {
-                    let project = project
-                        .and_then(|p| p.upgrade(cx))
-                        .map(|project| project.read(cx));
-                    format!(
-                        "    {}{}",
-                        contact.projects[*project_ix]
-                            .visible_worktree_root_names
-                            .join(", "),
-                        if project.map_or(true, |project| project.is_online()) {
-                            ""
-                        } else {
-                            " (going offline...)"
-                        },
-                    )
-                }
-                ContactEntry::OfflineProject(project) => {
-                    let project = project.upgrade(cx).unwrap().read(cx);
-                    format!(
-                        "    🔒 {}{}",
-                        project
-                            .worktree_root_names(cx)
-                            .collect::<Vec<_>>()
-                            .join(", "),
-                        if project.is_online() {
-                            " (going online...)"
-                        } else {
-                            ""
-                        },
-                    )
-                }
             };
 
             if panel.selection == Some(ix) {

crates/rpc/proto/zed.proto 🔗

@@ -1048,15 +1048,8 @@ message ChannelMessage {
 
 message Contact {
     uint64 user_id = 1;
-    repeated ProjectMetadata projects = 2;
-    bool online = 3;
-    bool should_notify = 4;
-}
-
-message ProjectMetadata {
-    uint64 id = 1;
-    repeated string visible_worktree_root_names = 3;
-    repeated uint64 guests = 4;
+    bool online = 2;
+    bool should_notify = 3;
 }
 
 message WorktreeMetadata {

crates/workspace/src/waiting_room.rs 🔗

@@ -1,185 +0,0 @@
-use crate::{sidebar::SidebarSide, AppState, ToggleFollow, Workspace};
-use anyhow::Result;
-use client::{proto, Client, Contact};
-use gpui::{
-    elements::*, ElementBox, Entity, ImageData, MutableAppContext, RenderContext, Task, View,
-    ViewContext,
-};
-use project::Project;
-use settings::Settings;
-use std::sync::Arc;
-use util::ResultExt;
-
-pub struct WaitingRoom {
-    project_id: u64,
-    avatar: Option<Arc<ImageData>>,
-    message: String,
-    waiting: bool,
-    client: Arc<Client>,
-    _join_task: Task<Result<()>>,
-}
-
-impl Entity for WaitingRoom {
-    type Event = ();
-
-    fn release(&mut self, _: &mut MutableAppContext) {
-        if self.waiting {
-            self.client
-                .send(proto::LeaveProject {
-                    project_id: self.project_id,
-                })
-                .log_err();
-        }
-    }
-}
-
-impl View for WaitingRoom {
-    fn ui_name() -> &'static str {
-        "WaitingRoom"
-    }
-
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        let theme = &cx.global::<Settings>().theme.workspace;
-
-        Flex::column()
-            .with_children(self.avatar.clone().map(|avatar| {
-                Image::new(avatar)
-                    .with_style(theme.joining_project_avatar)
-                    .aligned()
-                    .boxed()
-            }))
-            .with_child(
-                Text::new(
-                    self.message.clone(),
-                    theme.joining_project_message.text.clone(),
-                )
-                .contained()
-                .with_style(theme.joining_project_message.container)
-                .aligned()
-                .boxed(),
-            )
-            .aligned()
-            .contained()
-            .with_background_color(theme.background)
-            .boxed()
-    }
-}
-
-impl WaitingRoom {
-    pub fn new(
-        contact: Arc<Contact>,
-        project_index: usize,
-        app_state: Arc<AppState>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let project_id = contact.projects[project_index].id;
-        let client = app_state.client.clone();
-        let _join_task = cx.spawn_weak({
-            let contact = contact.clone();
-            |this, mut cx| async move {
-                let project = Project::remote(
-                    project_id,
-                    app_state.client.clone(),
-                    app_state.user_store.clone(),
-                    app_state.project_store.clone(),
-                    app_state.languages.clone(),
-                    app_state.fs.clone(),
-                    cx.clone(),
-                )
-                .await;
-
-                if let Some(this) = this.upgrade(&cx) {
-                    this.update(&mut cx, |this, cx| {
-                        this.waiting = false;
-                        match project {
-                            Ok(project) => {
-                                cx.replace_root_view(|cx| {
-                                    let mut workspace =
-                                        Workspace::new(project, app_state.default_item_factory, cx);
-                                    (app_state.initialize_workspace)(
-                                        &mut workspace,
-                                        &app_state,
-                                        cx,
-                                    );
-                                    workspace.toggle_sidebar(SidebarSide::Left, cx);
-                                    if let Some((host_peer_id, _)) = workspace
-                                        .project
-                                        .read(cx)
-                                        .collaborators()
-                                        .iter()
-                                        .find(|(_, collaborator)| collaborator.replica_id == 0)
-                                    {
-                                        if let Some(follow) = workspace
-                                            .toggle_follow(&ToggleFollow(*host_peer_id), cx)
-                                        {
-                                            follow.detach_and_log_err(cx);
-                                        }
-                                    }
-                                    workspace
-                                });
-                            }
-                            Err(error) => {
-                                let login = &contact.user.github_login;
-                                let message = match error {
-                                    project::JoinProjectError::HostDeclined => {
-                                        format!("@{} declined your request.", login)
-                                    }
-                                    project::JoinProjectError::HostClosedProject => {
-                                        format!(
-                                            "@{} closed their copy of {}.",
-                                            login,
-                                            humanize_list(
-                                                &contact.projects[project_index]
-                                                    .visible_worktree_root_names
-                                            )
-                                        )
-                                    }
-                                    project::JoinProjectError::HostWentOffline => {
-                                        format!("@{} went offline.", login)
-                                    }
-                                    project::JoinProjectError::Other(error) => {
-                                        log::error!("error joining project: {}", error);
-                                        "An error occurred.".to_string()
-                                    }
-                                };
-                                this.message = message;
-                                cx.notify();
-                            }
-                        }
-                    })
-                }
-
-                Ok(())
-            }
-        });
-
-        Self {
-            project_id,
-            avatar: contact.user.avatar.clone(),
-            message: format!(
-                "Asking to join @{}'s copy of {}...",
-                contact.user.github_login,
-                humanize_list(&contact.projects[project_index].visible_worktree_root_names)
-            ),
-            waiting: true,
-            client,
-            _join_task,
-        }
-    }
-}
-
-fn humanize_list<'a>(items: impl IntoIterator<Item = &'a String>) -> String {
-    let mut list = String::new();
-    let mut items = items.into_iter().enumerate().peekable();
-    while let Some((ix, item)) = items.next() {
-        if ix > 0 {
-            list.push_str(", ");
-            if items.peek().is_none() {
-                list.push_str("and ");
-            }
-        }
-
-        list.push_str(item);
-    }
-    list
-}

crates/workspace/src/workspace.rs 🔗

@@ -10,7 +10,6 @@ pub mod searchable;
 pub mod sidebar;
 mod status_bar;
 mod toolbar;
-mod waiting_room;
 
 use anyhow::{anyhow, Context, Result};
 use client::{proto, Client, Contact, PeerId, Subscription, TypedEnvelope, UserStore};
@@ -58,7 +57,6 @@ use std::{
 use theme::{Theme, ThemeRegistry};
 pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
 use util::ResultExt;
-use waiting_room::WaitingRoom;
 
 type ProjectItemBuilders = HashMap<
     TypeId,
@@ -167,14 +165,6 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
             }
         }
     });
-    cx.add_global_action({
-        let app_state = Arc::downgrade(&app_state);
-        move |action: &JoinProject, cx: &mut MutableAppContext| {
-            if let Some(app_state) = app_state.upgrade() {
-                join_project(action.contact.clone(), action.project_index, &app_state, cx);
-            }
-        }
-    });
 
     cx.add_async_action(Workspace::toggle_follow);
     cx.add_async_action(Workspace::follow_next_collaborator);
@@ -2663,28 +2653,6 @@ pub fn open_paths(
     })
 }
 
-pub fn join_project(
-    contact: Arc<Contact>,
-    project_index: usize,
-    app_state: &Arc<AppState>,
-    cx: &mut MutableAppContext,
-) {
-    let project_id = contact.projects[project_index].id;
-
-    for window_id in cx.window_ids().collect::<Vec<_>>() {
-        if let Some(workspace) = cx.root_view::<Workspace>(window_id) {
-            if workspace.read(cx).project().read(cx).remote_id() == Some(project_id) {
-                cx.activate_window(window_id);
-                return;
-            }
-        }
-    }
-
-    cx.add_window((app_state.build_window_options)(), |cx| {
-        WaitingRoom::new(contact, project_index, app_state.clone(), cx)
-    });
-}
-
 fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
     let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
         let mut workspace = Workspace::new(