Don't prompt guest to save when closing window after disconnection

This commit is contained in:
Antonio Scandurra 2022-07-12 09:05:39 +02:00
parent 0bcd209a3f
commit b1e3b38cb3
3 changed files with 58 additions and 27 deletions

View file

@ -267,7 +267,8 @@ async fn test_host_disconnect(
cx_b: &mut TestAppContext, cx_b: &mut TestAppContext,
cx_c: &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 mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
let client_a = server.create_client(cx_a, "user_a").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_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; 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())); assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
project_b let (_, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx));
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "b.txt"), true, cx)
})
.await .await
.unwrap()
.downcast::<Editor>()
.unwrap(); .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 // Request to join that project as client C
let project_c = cx_c.spawn(|cx| { let project_c = cx_c.spawn(|cx| {
@ -328,14 +342,31 @@ async fn test_host_disconnect(
.condition(cx_b, |project, _| project.is_read_only()) .condition(cx_b, |project, _| project.is_read_only())
.await; .await;
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared())); assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
cx_b.update(|_| {
drop(project_b);
});
assert!(matches!( assert!(matches!(
project_c.await.unwrap_err(), project_c.await.unwrap_err(),
project::JoinProjectError::HostWentOffline 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. // Ensure guests can still join.
let project_b2 = client_b.build_remote_project(&project_a, cx_a, cx_b).await; 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())); assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));

View file

@ -190,6 +190,7 @@ pub enum Event {
language_server_id: usize, language_server_id: usize,
}, },
RemoteIdChanged(Option<u64>), RemoteIdChanged(Option<u64>),
DisconnectedFromHost,
CollaboratorLeft(PeerId), CollaboratorLeft(PeerId),
ContactRequestedJoin(Arc<User>), ContactRequestedJoin(Arc<User>),
ContactCancelledJoinRequest(Arc<User>), ContactCancelledJoinRequest(Arc<User>),
@ -569,7 +570,7 @@ impl Project {
// Even if we're initially connected, any future change of the status means we momentarily disconnected. // 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 !is_connected || status.next().await.is_some() {
if let Some(this) = this.upgrade(&cx) { 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(()) Ok(())
@ -1434,7 +1435,7 @@ impl Project {
} }
} }
fn removed_from_project(&mut self, cx: &mut ModelContext<Self>) { fn disconnected_from_host(&mut self, cx: &mut ModelContext<Self>) {
if let ProjectClientState::Remote { if let ProjectClientState::Remote {
sharing_has_stopped, sharing_has_stopped,
.. ..
@ -1451,6 +1452,7 @@ impl Project {
}); });
} }
} }
cx.emit(Event::DisconnectedFromHost);
cx.notify(); cx.notify();
} }
} }
@ -4628,7 +4630,7 @@ impl Project {
_: Arc<Client>, _: Arc<Client>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
this.update(&mut cx, |this, cx| this.removed_from_project(cx)); this.update(&mut cx, |this, cx| this.disconnected_from_host(cx));
Ok(()) Ok(())
} }

View file

@ -805,17 +805,10 @@ enum FollowerItem {
impl Workspace { impl Workspace {
pub fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self { pub fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> 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) cx.observe_window_activation(Self::on_window_activation_changed)
.detach(); .detach();
cx.observe(&project, |_, _, cx| cx.notify()).detach();
cx.subscribe(&project, move |this, project, event, cx| { cx.subscribe(&project, move |this, _, event, cx| {
match event { match event {
project::Event::RemoteIdChanged(remote_id) => { project::Event::RemoteIdChanged(remote_id) => {
this.project_remote_id_changed(*remote_id, cx); this.project_remote_id_changed(*remote_id, cx);
@ -826,11 +819,12 @@ impl Workspace {
project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => { project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
this.update_window_title(cx); 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() cx.notify()
}) })
.detach(); .detach();
@ -1029,6 +1023,10 @@ impl Workspace {
should_prompt_to_save: bool, should_prompt_to_save: bool,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<bool>> { ) -> Task<Result<bool>> {
if self.project.read(cx).is_read_only() {
return Task::ready(Ok(true));
}
let dirty_items = self let dirty_items = self
.panes .panes
.iter() .iter()
@ -1045,11 +1043,10 @@ impl Workspace {
let project = self.project.clone(); let project = self.project.clone();
cx.spawn_weak(|_, mut cx| async move { cx.spawn_weak(|_, mut cx| async move {
// let mut saved_project_entry_ids = HashSet::default();
for (pane, item) in dirty_items { 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))); 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) = if let Some(ix) =
pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref())) 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<Self>) { fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
let is_edited = self let is_edited = !self.project.read(cx).is_read_only()
.items(cx) && self
.any(|item| item.has_conflict(cx) || item.is_dirty(cx)); .items(cx)
.any(|item| item.has_conflict(cx) || item.is_dirty(cx));
if is_edited != self.window_edited { if is_edited != self.window_edited {
self.window_edited = is_edited; self.window_edited = is_edited;
cx.set_window_edited(self.window_edited) cx.set_window_edited(self.window_edited)