diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index ca8d5ea95fb0e4d3b9fa49a825bd6a56a8218453..f371384a52ea43f02cd38194ffba7dbb51507e2e 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -24,6 +24,10 @@ pub struct Room { impl Entity for Room { type Event = Event; + + fn release(&mut self, _: &mut MutableAppContext) { + self.client.send(proto::LeaveRoom { id: self.id }).log_err(); + } } impl Room { @@ -99,6 +103,14 @@ impl Room { Ok(()) } + pub fn id(&self) -> u64 { + self.id + } + + pub fn status(&self) -> RoomStatus { + self.status + } + pub fn remote_participants(&self) -> &HashMap { &self.remote_participants } @@ -183,7 +195,7 @@ pub enum RoomStatus { } impl RoomStatus { - fn is_offline(&self) -> bool { + pub fn is_offline(&self) -> bool { matches!(self, RoomStatus::Offline) } } diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 8753ddee359c9b7f4a5047bcf78ae54e8242ee62..3bb63c0229d0b0a48d8ad00cc6d8a5ac302a14d4 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -74,11 +74,7 @@ async fn test_basic_calls( let client_b = server.create_client(cx_b, "user_b").await; let client_c = server.create_client(cx_c, "user_c").await; server - .make_contacts(vec![ - (&client_a, cx_a), - (&client_b, cx_b), - (&client_c, cx_c), - ]) + .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) .await; let room_a = cx_a @@ -224,7 +220,7 @@ async fn test_leaving_room_on_disconnection( let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; let room_a = cx_a @@ -286,15 +282,14 @@ async fn test_share_project( deterministic: Arc, cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, - cx_b2: &mut TestAppContext, ) { cx_a.foreground().forbid_parking(); let (_, window_b) = cx_b.add_window(|_| EmptyView); 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; client_a @@ -315,7 +310,7 @@ async fn test_share_project( let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); @@ -390,30 +385,6 @@ async fn test_share_project( // buffer_a // .condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 0) // .await; - - // Client B can join again on a different window because they are already a participant. - let client_b2 = server.create_client(cx_b2, "user_b").await; - let project_b2 = client_b2.build_remote_project(project_id, cx_b2).await; - deterministic.run_until_parked(); - project_a.read_with(cx_a, |project, _| { - assert_eq!(project.collaborators().len(), 2); - }); - project_b.read_with(cx_b, |project, _| { - assert_eq!(project.collaborators().len(), 2); - }); - project_b2.read_with(cx_b2, |project, _| { - assert_eq!(project.collaborators().len(), 2); - }); - - // Dropping client B's first project removes only that from client A's collaborators. - cx_b.update(move |_| drop(project_b)); - deterministic.run_until_parked(); - project_a.read_with(cx_a, |project, _| { - assert_eq!(project.collaborators().len(), 1); - }); - project_b2.read_with(cx_b2, |project, _| { - assert_eq!(project.collaborators().len(), 1); - }); } #[gpui::test(iterations = 10)] @@ -421,13 +392,15 @@ async fn test_unshare_project( deterministic: Arc, cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, + cx_c: &mut TestAppContext, ) { - cx_a.foreground().forbid_parking(); + deterministic.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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let client_c = server.create_client(cx_c, "user_c").await; + let (room_id, mut rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) .await; client_a @@ -443,7 +416,7 @@ async fn test_unshare_project( let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap()); @@ -455,30 +428,39 @@ async fn test_unshare_project( .await .unwrap(); - // When client A unshares the project, client B's project becomes read-only. + // When client B leaves the room, the project becomes read-only. + cx_b.update(|_| drop(rooms.remove(1))); + deterministic.run_until_parked(); + assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); + + // Client C opens the project. + let project_c = client_c.build_remote_project(project_id, cx_c).await; + + // When client A unshares the project, client C's project becomes read-only. project_a .update(cx_a, |project, cx| project.unshare(cx)) .unwrap(); deterministic.run_until_parked(); assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared())); - assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); + assert!(project_c.read_with(cx_c, |project, _| project.is_read_only())); - // Client B can join again after client A re-shares. + // Client C can open the project again after client A re-shares. let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); - let project_b2 = client_b.build_remote_project(project_id, cx_b).await; + let project_c2 = client_c.build_remote_project(project_id, cx_c).await; assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); - project_b2 - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + project_c2 + .update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) .await .unwrap(); - // When client A (the host) leaves, the project gets unshared and guests are notified. - cx_a.update(|_| drop(project_a)); + // When client A (the host) leaves the room, the project gets unshared and guests are notified. + cx_a.update(|_| drop(rooms.remove(0))); deterministic.run_until_parked(); - project_b2.read_with(cx_b, |project, _| { + project_a.read_with(cx_a, |project, _| assert!(!project.is_shared())); + project_c2.read_with(cx_c, |project, _| { assert!(project.is_read_only()); assert!(project.collaborators().is_empty()); }); @@ -497,12 +479,8 @@ async fn test_host_disconnect( 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; - server - .make_contacts(vec![ - (&client_a, cx_a), - (&client_b, cx_b), - (&client_c, cx_c), - ]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) .await; client_a @@ -519,7 +497,7 @@ async fn test_host_disconnect( let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap()); let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); @@ -576,18 +554,6 @@ async fn test_host_disconnect( drop(workspace_b); drop(project_b); }); - - // Ensure guests can still join. - let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) - .await - .unwrap(); - let project_b2 = client_b.build_remote_project(project_id, cx_b).await; - assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); - project_b2 - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) - .await - .unwrap(); } #[gpui::test(iterations = 10)] @@ -601,12 +567,8 @@ async fn test_propagate_saves_and_fs_changes( 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; - server - .make_contacts(vec![ - (&client_a, cx_a), - (&client_b, cx_b), - (&client_c, cx_c), - ]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) .await; client_a @@ -622,7 +584,7 @@ async fn test_propagate_saves_and_fs_changes( let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; let worktree_a = project_a.read_with(cx_a, |p, cx| p.worktrees(cx).next().unwrap()); let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); @@ -753,8 +715,8 @@ async fn test_fs_operations( 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; client_a @@ -769,7 +731,7 @@ async fn test_fs_operations( .await; let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -1018,8 +980,8 @@ async fn test_buffer_conflict_after_save(cx_a: &mut TestAppContext, cx_b: &mut T 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; client_a @@ -1033,7 +995,7 @@ async fn test_buffer_conflict_after_save(cx_a: &mut TestAppContext, cx_b: &mut T .await; let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -1071,8 +1033,8 @@ async fn test_buffer_reloading(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; client_a @@ -1086,7 +1048,7 @@ async fn test_buffer_reloading(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont .await; let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -1129,8 +1091,8 @@ async fn test_editing_while_guest_opens_buffer( 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; client_a @@ -1139,7 +1101,7 @@ async fn test_editing_while_guest_opens_buffer( .await; let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -1175,8 +1137,8 @@ async fn test_leaving_worktree_while_opening_buffer( 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; client_a @@ -1185,7 +1147,7 @@ async fn test_leaving_worktree_while_opening_buffer( .await; let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -1219,8 +1181,8 @@ async fn test_canceling_buffer_opening( 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; client_a @@ -1234,7 +1196,7 @@ async fn test_canceling_buffer_opening( .await; let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -1259,13 +1221,19 @@ async fn test_canceling_buffer_opening( } #[gpui::test(iterations = 10)] -async fn test_leaving_project(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { - cx_a.foreground().forbid_parking(); +async fn test_leaving_project( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, + cx_c: &mut TestAppContext, +) { + deterministic.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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let client_c = server.create_client(cx_c, "user_c").await; + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) .await; client_a @@ -1280,37 +1248,66 @@ async fn test_leaving_project(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte .await; let (project_a, _) = client_a.build_local_project("/a", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); - let _project_b = client_b.build_remote_project(project_id, cx_b).await; + let project_b = client_b.build_remote_project(project_id, cx_b).await; + let project_c = client_c.build_remote_project(project_id, cx_c).await; // Client A sees that a guest has joined. - project_a - .condition(cx_a, |p, _| p.collaborators().len() == 1) - .await; + deterministic.run_until_parked(); + project_a.read_with(cx_a, |project, _| { + assert_eq!(project.collaborators().len(), 2); + }); + project_b.read_with(cx_b, |project, _| { + assert_eq!(project.collaborators().len(), 2); + }); + project_c.read_with(cx_c, |project, _| { + assert_eq!(project.collaborators().len(), 2); + }); - // Drop client B's connection and ensure client A observes client B leaving the project. + // Drop client B's connection and ensure client A and client C observe client B leaving the project. client_b.disconnect(&cx_b.to_async()).unwrap(); - project_a - .condition(cx_a, |p, _| p.collaborators().is_empty()) - .await; - - // Rejoin the project as client B - let _project_b = client_b.build_remote_project(project_id, cx_b).await; + deterministic.run_until_parked(); + project_a.read_with(cx_a, |project, _| { + assert_eq!(project.collaborators().len(), 1); + }); + project_b.read_with(cx_b, |project, _| { + assert!(project.is_read_only()); + }); + project_c.read_with(cx_c, |project, _| { + assert_eq!(project.collaborators().len(), 1); + }); - // Client A sees that a guest has re-joined. - project_a - .condition(cx_a, |p, _| p.collaborators().len() == 1) - .await; + // Client B can't join the project, unless they re-join the room. + cx_b.spawn(|cx| { + Project::remote( + project_id, + client_b.client.clone(), + client_b.user_store.clone(), + client_b.project_store.clone(), + client_b.language_registry.clone(), + FakeFs::new(cx.background()), + cx, + ) + }) + .await + .unwrap_err(); - // Simulate connection loss for client B and ensure client A observes client B leaving the project. - client_b.wait_for_current_user(cx_b).await; - server.disconnect_client(client_b.current_user_id(cx_b)); + // Simulate connection loss for client C and ensure client A observes client C leaving the project. + client_c.wait_for_current_user(cx_c).await; + server.disconnect_client(client_c.current_user_id(cx_c)); cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT); - project_a - .condition(cx_a, |p, _| p.collaborators().is_empty()) - .await; + deterministic.run_until_parked(); + project_a.read_with(cx_a, |project, _| { + assert_eq!(project.collaborators().len(), 0); + }); + project_b.read_with(cx_b, |project, _| { + assert!(project.is_read_only()); + }); + project_c.read_with(cx_c, |project, _| { + assert!(project.is_read_only()); + }); } #[gpui::test(iterations = 10)] @@ -1325,12 +1322,8 @@ async fn test_collaborating_with_diagnostics( 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; - server - .make_contacts(vec![ - (&client_a, cx_a), - (&client_b, cx_b), - (&client_c, cx_c), - ]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) .await; // Set up a fake language server. @@ -1358,7 +1351,7 @@ async fn test_collaborating_with_diagnostics( .await; let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); @@ -1566,8 +1559,8 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; // Set up a fake language server. @@ -1605,7 +1598,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu .await; let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -1739,8 +1732,8 @@ async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut Te 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; client_a @@ -1753,7 +1746,7 @@ async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut Te .await .unwrap(); let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); @@ -1831,8 +1824,8 @@ async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppCon 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; // Set up a fake language server. @@ -1857,7 +1850,7 @@ async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppCon .await; let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -1931,8 +1924,8 @@ async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; // Set up a fake language server. @@ -1964,7 +1957,7 @@ async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { .await; let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -2074,8 +2067,8 @@ async fn test_references(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; // Set up a fake language server. @@ -2107,7 +2100,7 @@ async fn test_references(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { .await; let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -2174,8 +2167,8 @@ async fn test_project_search(cx_a: &mut TestAppContext, cx_b: &mut TestAppContex 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; client_a @@ -2206,7 +2199,7 @@ async fn test_project_search(cx_a: &mut TestAppContext, cx_b: &mut TestAppContex .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete()) .await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); @@ -2252,8 +2245,8 @@ async fn test_document_highlights(cx_a: &mut TestAppContext, cx_b: &mut TestAppC 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; client_a @@ -2280,7 +2273,7 @@ async fn test_document_highlights(cx_a: &mut TestAppContext, cx_b: &mut TestAppC let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -2353,8 +2346,8 @@ async fn test_lsp_hover(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; client_a @@ -2381,7 +2374,7 @@ async fn test_lsp_hover(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -2455,8 +2448,8 @@ async fn test_project_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; // Set up a fake language server. @@ -2490,7 +2483,7 @@ async fn test_project_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte .await; let (project_a, worktree_id) = client_a.build_local_project("/code/crate-1", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -2562,8 +2555,8 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it( 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; // Set up a fake language server. @@ -2590,7 +2583,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it( .await; let (project_a, worktree_id) = client_a.build_local_project("/root", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -2637,8 +2630,8 @@ async fn test_collaborating_with_code_actions( 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; // Set up a fake language server. @@ -2665,7 +2658,7 @@ async fn test_collaborating_with_code_actions( .await; let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); @@ -2847,8 +2840,8 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; // Set up a fake language server. @@ -2886,7 +2879,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T .await; let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -3038,8 +3031,8 @@ async fn test_language_server_statuses( 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; // Set up a fake language server. @@ -3098,7 +3091,7 @@ async fn test_language_server_statuses( }); let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -3559,11 +3552,7 @@ async fn test_contacts( let client_b = server.create_client(cx_b, "user_b").await; let client_c = server.create_client(cx_c, "user_c").await; server - .make_contacts(vec![ - (&client_a, cx_a), - (&client_b, cx_b), - (&client_c, cx_c), - ]) + .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) .await; deterministic.run_until_parked(); @@ -3815,8 +3804,8 @@ async fn test_following(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; cx_a.update(editor::init); cx_b.update(editor::init); @@ -3834,7 +3823,7 @@ async fn test_following(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { .await; let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -4024,8 +4013,8 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; cx_a.update(editor::init); cx_b.update(editor::init); @@ -4045,7 +4034,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T .await; let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); @@ -4192,8 +4181,8 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; cx_a.update(editor::init); cx_b.update(editor::init); @@ -4212,7 +4201,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont .await; let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); let project_b = client_b.build_remote_project(project_id, cx_b).await; @@ -4355,8 +4344,8 @@ async fn test_peers_simultaneously_following_each_other( 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; - server - .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)]) + let (room_id, _rooms) = server + .create_rooms(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; cx_a.update(editor::init); cx_b.update(editor::init); @@ -4365,7 +4354,7 @@ async fn test_peers_simultaneously_following_each_other( let (project_a, _) = client_a.build_local_project("/a", cx_a).await; let workspace_a = client_a.build_workspace(&project_a, cx_a); let project_id = project_a - .update(cx_a, |project, cx| project.share(cx)) + .update(cx_a, |project, cx| project.share(room_id, cx)) .await .unwrap(); @@ -4432,7 +4421,8 @@ async fn test_random_collaboration( let mut server = TestServer::start(cx.foreground(), cx.background()).await; let db = server.app_state.db.clone(); - let host_user_id = db.create_user("host", None, false).await.unwrap(); + + let room_creator_user_id = db.create_user("room-creator", None, false).await.unwrap(); let mut available_guests = vec![ "guest-1".to_string(), "guest-2".to_string(), @@ -4440,23 +4430,32 @@ async fn test_random_collaboration( "guest-4".to_string(), ]; - for username in &available_guests { - let guest_user_id = db.create_user(username, None, false).await.unwrap(); - assert_eq!(*username, format!("guest-{}", guest_user_id)); + for username in Some(&"host".to_string()) + .into_iter() + .chain(&available_guests) + { + let user_id = db.create_user(username, None, false).await.unwrap(); server .app_state .db - .send_contact_request(guest_user_id, host_user_id) + .send_contact_request(user_id, room_creator_user_id) .await .unwrap(); server .app_state .db - .respond_to_contact_request(host_user_id, guest_user_id, true) + .respond_to_contact_request(room_creator_user_id, user_id, true) .await .unwrap(); } + let client = server.create_client(cx, "room-creator").await; + let room = cx + .update(|cx| Room::create(client.client.clone(), client.user_store.clone(), cx)) + .await + .unwrap(); + let room_id = room.read_with(cx, |room, _| room.id()); + let mut clients = Vec::new(); let mut user_ids = Vec::new(); let mut op_start_signals = Vec::new(); @@ -4622,15 +4621,36 @@ async fn test_random_collaboration( .await; host_language_registry.add(Arc::new(language)); + let host_user_id = host.current_user_id(&host_cx); + room.update(cx, |room, cx| room.call(host_user_id.to_proto(), cx)) + .await + .unwrap(); + deterministic.run_until_parked(); + let call = host + .user_store + .read_with(&host_cx, |user_store, _| user_store.incoming_call()); + let host_room = host_cx + .update(|cx| { + Room::join( + call.borrow().as_ref().unwrap(), + host.client.clone(), + host.user_store.clone(), + cx, + ) + }) + .await + .unwrap(); + let host_project_id = host_project - .update(&mut host_cx, |project, cx| project.share(cx)) + .update(&mut host_cx, |project, cx| project.share(room_id, cx)) .await .unwrap(); let op_start_signal = futures::channel::mpsc::unbounded(); - user_ids.push(host.current_user_id(&host_cx)); + user_ids.push(host_user_id); op_start_signals.push(op_start_signal.0); clients.push(host_cx.foreground().spawn(host.simulate_host( + host_room, host_project, op_start_signal.1, rng.clone(), @@ -4692,6 +4712,28 @@ async fn test_random_collaboration( deterministic.start_waiting(); let guest = server.create_client(&mut guest_cx, &guest_username).await; + let guest_user_id = guest.current_user_id(&guest_cx); + + room.update(cx, |room, cx| room.call(guest_user_id.to_proto(), cx)) + .await + .unwrap(); + deterministic.run_until_parked(); + let call = guest + .user_store + .read_with(&guest_cx, |user_store, _| user_store.incoming_call()); + + let guest_room = guest_cx + .update(|cx| { + Room::join( + call.borrow().as_ref().unwrap(), + guest.client.clone(), + guest.user_store.clone(), + cx, + ) + }) + .await + .unwrap(); + let guest_project = Project::remote( host_project_id, guest.client.clone(), @@ -4706,10 +4748,11 @@ async fn test_random_collaboration( deterministic.finish_waiting(); let op_start_signal = futures::channel::mpsc::unbounded(); - user_ids.push(guest.current_user_id(&guest_cx)); + user_ids.push(guest_user_id); op_start_signals.push(op_start_signal.0); clients.push(guest_cx.foreground().spawn(guest.simulate_guest( guest_username.clone(), + guest_room, guest_project, op_start_signal.1, rng.clone(), @@ -5039,12 +5082,14 @@ impl TestServer { self.forbid_connections.store(false, SeqCst); } - async fn make_contacts(&self, mut clients: Vec<(&TestClient, &mut TestAppContext)>) { - while let Some((client_a, cx_a)) = clients.pop() { - for (client_b, cx_b) in &mut clients { + async fn make_contacts(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) { + for ix in 1..clients.len() { + let (left, right) = clients.split_at_mut(ix); + let (client_a, cx_a) = left.last_mut().unwrap(); + for (client_b, cx_b) in right { client_a .user_store - .update(cx_a, |store, cx| { + .update(*cx_a, |store, cx| { store.request_contact(client_b.user_id().unwrap(), cx) }) .await @@ -5061,6 +5106,52 @@ impl TestServer { } } + async fn create_rooms( + &self, + clients: &mut [(&TestClient, &mut TestAppContext)], + ) -> (u64, Vec>) { + self.make_contacts(clients).await; + + let mut rooms = Vec::new(); + + let (left, right) = clients.split_at_mut(1); + let (client_a, cx_a) = &mut left[0]; + + let room_a = cx_a + .update(|cx| Room::create(client_a.client.clone(), client_a.user_store.clone(), cx)) + .await + .unwrap(); + let room_id = room_a.read_with(*cx_a, |room, _| room.id()); + + for (client_b, cx_b) in right { + let user_id_b = client_b.current_user_id(*cx_b).to_proto(); + room_a + .update(*cx_a, |room, cx| room.call(user_id_b, cx)) + .await + .unwrap(); + + cx_b.foreground().run_until_parked(); + let incoming_call = client_b + .user_store + .read_with(*cx_b, |user_store, _| user_store.incoming_call()); + let room_b = cx_b + .update(|cx| { + Room::join( + incoming_call.borrow().as_ref().unwrap(), + client_b.client.clone(), + client_b.user_store.clone(), + cx, + ) + }) + .await + .unwrap(); + rooms.push(room_b); + } + + rooms.insert(0, room_a); + (room_id, rooms) + } + async fn build_app_state(test_db: &TestDb) -> Arc { Arc::new(AppState { db: test_db.db().clone(), @@ -5224,6 +5315,7 @@ impl TestClient { async fn simulate_host( mut self, + _room: ModelHandle, project: ModelHandle, op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>, rng: Arc>, @@ -5361,6 +5453,7 @@ impl TestClient { pub async fn simulate_guest( mut self, guest_username: String, + _room: ModelHandle, project: ModelHandle, op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>, rng: Arc>, diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5b89dc6bf6b6a6cb32b20cf9ebbfcacfb41be044..31f99759cd12a8bdb816f7b54cdc7838ef8f71b4 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -635,6 +635,13 @@ impl Server { }, )?; } + + self.peer.send( + message.sender_id, + proto::UnshareProject { + project_id: project.id.to_proto(), + }, + )?; } } @@ -798,14 +805,6 @@ impl Server { }; tracing::info!(%project_id, %host_user_id, %host_connection_id, "join project"); - let has_contact = self - .app_state - .db - .has_contact(guest_user_id, host_user_id) - .await?; - if !has_contact { - return Err(anyhow!("no such project"))?; - } let mut store = self.store().await; let (project, replica_id) = store.join_project(request.sender_id, project_id)?; diff --git a/crates/collab/src/rpc/store.rs b/crates/collab/src/rpc/store.rs index 2ae52f7c2b11a9b20602be3a0594b219f5749e49..9b241446b52eaf6f21265243741dcf2502cd5f32 100644 --- a/crates/collab/src/rpc/store.rs +++ b/crates/collab/src/rpc/store.rs @@ -39,7 +39,7 @@ struct ConnectionState { pub struct Call { pub caller_user_id: UserId, pub room_id: RoomId, - pub joined: bool, + pub connection_id: Option, } #[derive(Serialize)] @@ -163,7 +163,7 @@ impl Store { let connected_user = self.connected_users.entry(user_id).or_default(); connected_user.connection_ids.insert(connection_id); if let Some(active_call) = connected_user.active_call { - if active_call.joined { + if active_call.connection_id.is_some() { None } else { let room = self.room(active_call.room_id)?; @@ -378,7 +378,7 @@ impl Store { connected_user.active_call = Some(Call { caller_user_id: connection.user_id, room_id, - joined: true, + connection_id: Some(creator_connection_id), }); Ok(room_id) } @@ -404,7 +404,7 @@ impl Store { .as_mut() .ok_or_else(|| anyhow!("not being called"))?; anyhow::ensure!( - active_call.room_id == room_id && !active_call.joined, + active_call.room_id == room_id && active_call.connection_id.is_none(), "not being called on this room" ); @@ -428,7 +428,7 @@ impl Store { )), }), }); - active_call.joined = true; + active_call.connection_id = Some(connection_id); Ok((room, recipient_connection_ids)) } @@ -440,25 +440,20 @@ impl Store { .ok_or_else(|| anyhow!("no such connection"))?; let user_id = connection.user_id; - let mut connected_user = self + let connected_user = self .connected_users - .get_mut(&user_id) + .get(&user_id) .ok_or_else(|| anyhow!("no such connection"))?; anyhow::ensure!( connected_user .active_call - .map_or(false, |call| call.room_id == room_id && call.joined), + .map_or(false, |call| call.room_id == room_id + && call.connection_id == Some(connection_id)), "cannot leave a room before joining it" ); - let room = self - .rooms - .get_mut(&room_id) - .ok_or_else(|| anyhow!("no such room"))?; - room.participants - .retain(|participant| participant.peer_id != connection_id.0); - connected_user.active_call = None; - + // Given that users can only join one room at a time, we can safely unshare + // and leave all projects associated with the connection. let mut unshared_projects = Vec::new(); let mut left_projects = Vec::new(); for project_id in connection.projects.clone() { @@ -468,8 +463,15 @@ impl Store { left_projects.push(project); } } + self.connected_users.get_mut(&user_id).unwrap().active_call = None; + + let room = self + .rooms + .get_mut(&room_id) + .ok_or_else(|| anyhow!("no such room"))?; + room.participants + .retain(|participant| participant.peer_id != connection_id.0); - let room = self.rooms.get(&room_id).unwrap(); Ok(LeftRoom { room, unshared_projects, @@ -521,7 +523,7 @@ impl Store { recipient.active_call = Some(Call { caller_user_id, room_id, - joined: false, + connection_id: None, }); Ok(( @@ -546,7 +548,8 @@ impl Store { .ok_or_else(|| anyhow!("no such connection"))?; anyhow::ensure!(recipient .active_call - .map_or(false, |call| call.room_id == room_id && !call.joined)); + .map_or(false, |call| call.room_id == room_id + && call.connection_id.is_none())); recipient.active_call = None; let room = self .rooms @@ -754,10 +757,22 @@ impl Store { .connections .get_mut(&requester_connection_id) .ok_or_else(|| anyhow!("no such connection"))?; + let user = self + .connected_users + .get(&connection.user_id) + .ok_or_else(|| anyhow!("no such connection"))?; + let active_call = user.active_call.ok_or_else(|| anyhow!("no such project"))?; + anyhow::ensure!( + active_call.connection_id == Some(requester_connection_id), + "no such project" + ); + let project = self .projects .get_mut(&project_id) .ok_or_else(|| anyhow!("no such project"))?; + anyhow::ensure!(project.room_id == active_call.room_id, "no such project"); + connection.projects.insert(project_id); let mut replica_id = 1; while project.active_replica_ids.contains(&replica_id) { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 901e3b7d85857afb1bf7a467daff39289adb73b4..c0ed2e5ad4828ca6caec0e8082d80635d109faa4 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4435,8 +4435,14 @@ impl Project { _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { - this.update(&mut cx, |this, cx| this.disconnected_from_host(cx)); - Ok(()) + this.update(&mut cx, |this, cx| { + if this.is_local() { + this.unshare(cx)?; + } else { + this.disconnected_from_host(cx); + } + Ok(()) + }) } async fn handle_add_collaborator(