From 78fbd1307adc212beb098838a9881edce35f36a3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 22 Sep 2021 14:50:32 +0200 Subject: [PATCH] Remove remote worktrees and close their buffers when host unshares --- server/src/rpc.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++ zed/src/workspace.rs | 24 ++++++++++-- zed/src/worktree.rs | 17 ++++++++- 3 files changed, 127 insertions(+), 4 deletions(-) diff --git a/server/src/rpc.rs b/server/src/rpc.rs index ae71f543ef..fec6182fcc 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -968,10 +968,12 @@ mod tests { editor::{Editor, EditorStyle, Insert}, fs::{FakeFs, Fs as _}, language::LanguageRegistry, + people_panel::JoinWorktree, rpc::{self, Client, Credentials, EstablishConnectionError}, settings, test::FakeHttpClient, user::UserStore, + workspace::Workspace, worktree::Worktree, }; use zrpc::Peer; @@ -1087,6 +1089,94 @@ mod tests { .await; } + #[gpui::test] + async fn test_unshare_worktree(mut cx_a: TestAppContext, mut cx_b: TestAppContext) { + cx_b.update(zed::workspace::init); + let lang_registry = Arc::new(LanguageRegistry::new()); + + // Connect to a server as 2 clients. + let mut server = TestServer::start().await; + let (client_a, _) = server.create_client(&mut cx_a, "user_a").await; + let (client_b, user_store_b) = server.create_client(&mut cx_b, "user_b").await; + let app_state_b = zed::AppState { + rpc: client_b, + user_store: user_store_b, + ..Arc::try_unwrap(cx_b.update(zed::test::test_app_state)) + .ok() + .unwrap() + }; + + cx_a.foreground().forbid_parking(); + + // Share a local worktree as client A + let fs = Arc::new(FakeFs::new()); + fs.insert_tree( + "/a", + json!({ + ".zed.toml": r#"collaborators = ["user_b"]"#, + "a.txt": "a-contents", + "b.txt": "b-contents", + }), + ) + .await; + let worktree_a = Worktree::open_local( + client_a.clone(), + "/a".as_ref(), + fs, + lang_registry.clone(), + &mut cx_a.to_async(), + ) + .await + .unwrap(); + worktree_a + .read_with(&cx_a, |tree, _| tree.as_local().unwrap().scan_complete()) + .await; + + let remote_worktree_id = worktree_a + .update(&mut cx_a, |tree, cx| tree.as_local_mut().unwrap().share(cx)) + .await + .unwrap(); + + let (window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(&app_state_b, cx)); + cx_b.update(|cx| { + cx.dispatch_action( + window_b, + vec![workspace_b.id()], + &JoinWorktree(remote_worktree_id), + ); + }); + workspace_b + .condition(&cx_b, |workspace, _| workspace.worktrees().len() == 1) + .await; + + let local_worktree_id_b = workspace_b.read_with(&cx_b, |workspace, cx| { + let active_pane = workspace.active_pane().read(cx); + assert!(active_pane.active_item().is_none()); + workspace.worktrees().iter().next().unwrap().id() + }); + workspace_b + .update(&mut cx_b, |worktree, cx| { + worktree.open_entry((local_worktree_id_b, Path::new("a.txt").into()), cx) + }) + .unwrap() + .await; + workspace_b.read_with(&cx_b, |workspace, cx| { + let active_pane = workspace.active_pane().read(cx); + assert!(active_pane.active_item().is_some()); + }); + + worktree_a.update(&mut cx_a, |tree, cx| { + tree.as_local_mut().unwrap().unshare(cx); + }); + workspace_b + .condition(&cx_b, |workspace, _| workspace.worktrees().len() == 0) + .await; + workspace_b.read_with(&cx_b, |workspace, cx| { + let active_pane = workspace.active_pane().read(cx); + assert!(active_pane.active_item().is_none()); + }); + } + #[gpui::test] async fn test_propagate_saves_and_fs_changes_in_shared_worktree( mut cx_a: TestAppContext, diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index 4581e38d90..19071b7326 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -13,7 +13,7 @@ use crate::{ settings::Settings, user, util::TryFutureExt as _, - worktree::{File, Worktree}, + worktree::{self, File, Worktree}, AppState, Authenticate, }; use anyhow::Result; @@ -886,9 +886,27 @@ impl Workspace { rpc.authenticate_and_connect(&cx).await?; let worktree = Worktree::open_remote(rpc.clone(), worktree_id, languages, &mut cx).await?; - this.update(&mut cx, |workspace, cx| { + this.update(&mut cx, |this, cx| { cx.observe(&worktree, |_, _, cx| cx.notify()).detach(); - workspace.worktrees.insert(worktree); + cx.subscribe(&worktree, move |this, _, event, cx| match event { + worktree::Event::Closed => { + this.worktrees.retain(|worktree| { + worktree.update(cx, |worktree, cx| { + if let Some(worktree) = worktree.as_remote_mut() { + if worktree.remote_id() == worktree_id { + worktree.close_all_buffers(cx); + return false; + } + } + true + }) + }); + + cx.notify(); + } + }) + .detach(); + this.worktrees.insert(worktree); cx.notify(); }); diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index a3c5b4e4c4..de819af141 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -62,8 +62,12 @@ pub enum Worktree { Remote(RemoteWorktree), } +pub enum Event { + Closed, +} + impl Entity for Worktree { - type Event = (); + type Event = Event; fn release(&mut self, cx: &mut MutableAppContext) { match self { @@ -223,6 +227,7 @@ impl Worktree { rpc.subscribe_to_entity(remote_id, cx, Self::handle_update), rpc.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer), rpc.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved), + rpc.subscribe_to_entity(remote_id, cx, Self::handle_unshare), ]; Worktree::Remote(RemoteWorktree { @@ -522,6 +527,16 @@ impl Worktree { Ok(()) } + pub fn handle_unshare( + &mut self, + _: TypedEnvelope, + _: Arc, + cx: &mut ModelContext, + ) -> Result<()> { + cx.emit(Event::Closed); + Ok(()) + } + fn poll_snapshot(&mut self, cx: &mut ModelContext) { match self { Self::Local(worktree) => {