From b1e3b38cb3b5e969ad63714942b2f936c4ba7759 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 12 Jul 2022 09:05:39 +0200 Subject: [PATCH] Don't prompt guest to save when closing window after disconnection --- crates/collab/src/integration_tests.rs | 43 ++++++++++++++++++++++---- crates/project/src/project.rs | 8 +++-- crates/workspace/src/workspace.rs | 34 ++++++++++---------- 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 7767b361c1bbbd327ae765ab62a8308ca3cb6b61..e7a6c633f568abff603676fedc6690cd6cf5da64 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -267,7 +267,8 @@ async fn test_host_disconnect( cx_b: &mut TestAppContext, cx_c: &mut TestAppContext, ) { - cx_a.foreground().forbid_parking(); + cx_b.update(editor::init); + 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; @@ -298,10 +299,23 @@ async fn test_host_disconnect( let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await; assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); - project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + let (_, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx)); + let editor_b = workspace_b + .update(cx_b, |workspace, cx| { + workspace.open_path((worktree_id, "b.txt"), true, cx) + }) .await + .unwrap() + .downcast::() .unwrap(); + cx_b.read(|cx| { + assert_eq!( + cx.focused_view_id(workspace_b.window_id()), + Some(editor_b.id()) + ); + }); + editor_b.update(cx_b, |editor, cx| editor.insert("X", cx)); + assert!(cx_b.is_window_edited(workspace_b.window_id())); // Request to join that project as client C let project_c = cx_c.spawn(|cx| { @@ -328,14 +342,31 @@ async fn test_host_disconnect( .condition(cx_b, |project, _| project.is_read_only()) .await; assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared())); - cx_b.update(|_| { - drop(project_b); - }); assert!(matches!( project_c.await.unwrap_err(), project::JoinProjectError::HostWentOffline )); + // Ensure client B's edited state is reset and that the whole window is blurred. + cx_b.read(|cx| { + assert_eq!(cx.focused_view_id(workspace_b.window_id()), None); + }); + assert!(!cx_b.is_window_edited(workspace_b.window_id())); + + // Ensure client B is not prompted to save edits when closing window after disconnecting. + workspace_b + .update(cx_b, |workspace, cx| { + workspace.close(&Default::default(), cx) + }) + .unwrap() + .await + .unwrap(); + assert_eq!(cx_b.window_ids().len(), 0); + cx_b.update(|_| { + drop(workspace_b); + drop(project_b); + }); + // Ensure guests can still join. let project_b2 = client_b.build_remote_project(&project_a, cx_a, cx_b).await; assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 11f28cd29aaed2694f38cf6f9d3b0add0b855aa4..127475f3c4bb3dc61c4355a364a87e389116af87 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -190,6 +190,7 @@ pub enum Event { language_server_id: usize, }, RemoteIdChanged(Option), + DisconnectedFromHost, CollaboratorLeft(PeerId), ContactRequestedJoin(Arc), ContactCancelledJoinRequest(Arc), @@ -569,7 +570,7 @@ impl Project { // Even if we're initially connected, any future change of the status means we momentarily disconnected. if !is_connected || status.next().await.is_some() { if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| this.removed_from_project(cx)) + this.update(&mut cx, |this, cx| this.disconnected_from_host(cx)) } } Ok(()) @@ -1434,7 +1435,7 @@ impl Project { } } - fn removed_from_project(&mut self, cx: &mut ModelContext) { + fn disconnected_from_host(&mut self, cx: &mut ModelContext) { if let ProjectClientState::Remote { sharing_has_stopped, .. @@ -1451,6 +1452,7 @@ impl Project { }); } } + cx.emit(Event::DisconnectedFromHost); cx.notify(); } } @@ -4628,7 +4630,7 @@ impl Project { _: Arc, mut cx: AsyncAppContext, ) -> Result<()> { - this.update(&mut cx, |this, cx| this.removed_from_project(cx)); + this.update(&mut cx, |this, cx| this.disconnected_from_host(cx)); Ok(()) } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index fdfa640718fdd066f801a81142c54e7a99c9c9b3..da62fe7e54c9db8de0ff545dd01d50e3b1b3154c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -805,17 +805,10 @@ enum FollowerItem { impl Workspace { pub fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { - cx.observe(&project, |_, project, cx| { - if project.read(cx).is_read_only() { - cx.blur(); - } - cx.notify() - }) - .detach(); cx.observe_window_activation(Self::on_window_activation_changed) .detach(); - - cx.subscribe(&project, move |this, project, event, cx| { + cx.observe(&project, |_, _, cx| cx.notify()).detach(); + cx.subscribe(&project, move |this, _, event, cx| { match event { project::Event::RemoteIdChanged(remote_id) => { this.project_remote_id_changed(*remote_id, cx); @@ -826,11 +819,12 @@ impl Workspace { project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => { this.update_window_title(cx); } + project::Event::DisconnectedFromHost => { + this.update_window_edited(cx); + cx.blur(); + } _ => {} } - if project.read(cx).is_read_only() { - cx.blur(); - } cx.notify() }) .detach(); @@ -1029,6 +1023,10 @@ impl Workspace { should_prompt_to_save: bool, cx: &mut ViewContext, ) -> Task> { + if self.project.read(cx).is_read_only() { + return Task::ready(Ok(true)); + } + let dirty_items = self .panes .iter() @@ -1045,11 +1043,10 @@ impl Workspace { let project = self.project.clone(); cx.spawn_weak(|_, mut cx| async move { - // let mut saved_project_entry_ids = HashSet::default(); for (pane, item) in dirty_items { - let (is_singl, project_entry_ids) = + let (singleton, project_entry_ids) = cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx))); - if is_singl || !project_entry_ids.is_empty() { + if singleton || !project_entry_ids.is_empty() { if let Some(ix) = pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref())) { @@ -1910,9 +1907,10 @@ impl Workspace { } fn update_window_edited(&mut self, cx: &mut ViewContext) { - let is_edited = self - .items(cx) - .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); + let is_edited = !self.project.read(cx).is_read_only() + && self + .items(cx) + .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); if is_edited != self.window_edited { self.window_edited = is_edited; cx.set_window_edited(self.window_edited)