Close the empty project before adding a project to a window (#54450)
This prevents the user from getting into a state where they have unsaved untitled buffers, but no way to get back to them. Release Notes: - N/A
This commit is contained in:
1 parent
dd5cf89e03
commit
7fc65c7db6
2 files changed
+186
-10
No files matched your search
@@ -1930,15 +1930,55 @@ impl MultiWorkspace {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Workspace>>> {
|
||||
if self.multi_workspace_enabled(cx) {
|
||||
self.find_or_create_local_workspace(
|
||||
PathList::new(&paths),
|
||||
None,
|
||||
&[],
|
||||
None,
|
||||
OpenMode::Activate,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
let empty_workspace = if self
|
||||
.active_workspace
|
||||
.read(cx)
|
||||
.project()
|
||||
.read(cx)
|
||||
.visible_worktrees(cx)
|
||||
.next()
|
||||
.is_none()
|
||||
{
|
||||
Some(self.active_workspace.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
if let Some(empty_workspace) = empty_workspace.as_ref() {
|
||||
let should_continue = empty_workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.prepare_to_close(CloseIntent::ReplaceWindow, window, cx)
|
||||
})?
|
||||
.await?;
|
||||
if !should_continue {
|
||||
return Ok(empty_workspace.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let create_task = this.update_in(cx, |this, window, cx| {
|
||||
this.find_or_create_local_workspace(
|
||||
PathList::new(&paths),
|
||||
None,
|
||||
empty_workspace.as_slice(),
|
||||
None,
|
||||
OpenMode::Activate,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
let new_workspace = create_task.await?;
|
||||
|
||||
if let Some(empty_workspace) = empty_workspace {
|
||||
this.update(cx, |this, cx| {
|
||||
if this.is_workspace_retained(&empty_workspace) {
|
||||
this.detach_workspace(&empty_workspace, cx);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(new_workspace)
|
||||
})
|
||||
} else {
|
||||
let workspace = self.workspace().clone();
|
||||
cx.spawn_in(window, async move |_this, cx| {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::*;
|
||||
use crate::item::test::TestItem;
|
||||
use client::proto;
|
||||
use fs::{FakeFs, Fs};
|
||||
use gpui::TestAppContext;
|
||||
use gpui::{TestAppContext, VisualTestContext};
|
||||
use project::DisableAiSettings;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
@@ -767,3 +768,138 @@ async fn test_remote_project_root_dir_changes_update_groups(cx: &mut TestAppCont
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_open_project_closes_empty_workspace_but_not_non_empty_ones(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let app_state = cx.update(AppState::test);
|
||||
let fs = app_state.fs.as_fake();
|
||||
fs.insert_tree(path!("/project_a"), json!({ "file_a.txt": "" }))
|
||||
.await;
|
||||
fs.insert_tree(path!("/project_b"), json!({ "file_b.txt": "" }))
|
||||
.await;
|
||||
|
||||
// Start with an empty (no-worktrees) workspace.
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
cx.run_until_parked();
|
||||
|
||||
window
|
||||
.update(cx, |mw, _window, cx| mw.open_sidebar(cx))
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
let empty_workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
|
||||
// Add a dirty untitled item to the empty workspace.
|
||||
let dirty_item = cx.new(|cx| TestItem::new(cx).with_dirty(true));
|
||||
empty_workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.add_item_to_active_pane(Box::new(dirty_item.clone()), None, true, window, cx);
|
||||
});
|
||||
|
||||
// Opening a project while the lone empty workspace has unsaved
|
||||
// changes prompts the user.
|
||||
let open_task = window
|
||||
.update(cx, |mw, window, cx| {
|
||||
mw.open_project(
|
||||
vec![PathBuf::from(path!("/project_a"))],
|
||||
OpenMode::Activate,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
// Cancelling keeps the empty workspace.
|
||||
assert!(cx.has_pending_prompt(),);
|
||||
cx.simulate_prompt_answer("Cancel");
|
||||
cx.run_until_parked();
|
||||
assert_eq!(open_task.await.unwrap(), empty_workspace);
|
||||
window
|
||||
.read_with(cx, |mw, _cx| {
|
||||
assert_eq!(mw.workspaces().count(), 1);
|
||||
assert_eq!(mw.workspace(), &empty_workspace);
|
||||
assert_eq!(mw.project_group_keys(), vec![]);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Discarding the unsaved changes closes the empty workspace
|
||||
// and opens the new project in its place.
|
||||
let open_task = window
|
||||
.update(cx, |mw, window, cx| {
|
||||
mw.open_project(
|
||||
vec![PathBuf::from(path!("/project_a"))],
|
||||
OpenMode::Activate,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
assert!(cx.has_pending_prompt(),);
|
||||
cx.simulate_prompt_answer("Don't Save");
|
||||
cx.run_until_parked();
|
||||
|
||||
let workspace_a = open_task.await.unwrap();
|
||||
assert_ne!(workspace_a, empty_workspace);
|
||||
|
||||
window
|
||||
.read_with(cx, |mw, _cx| {
|
||||
assert_eq!(mw.workspaces().count(), 1);
|
||||
assert_eq!(mw.workspace(), &workspace_a);
|
||||
assert_eq!(
|
||||
mw.project_group_keys(),
|
||||
vec![ProjectGroupKey::new(
|
||||
None,
|
||||
PathList::new(&[path!("/project_a")])
|
||||
)]
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
assert!(
|
||||
empty_workspace.read_with(cx, |workspace, _cx| workspace.session_id().is_none()),
|
||||
"the detached empty workspace should no longer be attached to the session",
|
||||
);
|
||||
|
||||
let dirty_item = cx.new(|cx| TestItem::new(cx).with_dirty(true));
|
||||
workspace_a.update_in(cx, |workspace, window, cx| {
|
||||
workspace.add_item_to_active_pane(Box::new(dirty_item.clone()), None, true, window, cx);
|
||||
});
|
||||
|
||||
// Opening another project does not close the existing project or prompt.
|
||||
let workspace_b = window
|
||||
.update(cx, |mw, window, cx| {
|
||||
mw.open_project(
|
||||
vec![PathBuf::from(path!("/project_b"))],
|
||||
OpenMode::Activate,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
assert!(!cx.has_pending_prompt());
|
||||
assert_ne!(workspace_b, workspace_a);
|
||||
window
|
||||
.read_with(cx, |mw, _cx| {
|
||||
assert_eq!(mw.workspaces().count(), 2);
|
||||
assert_eq!(mw.workspace(), &workspace_b);
|
||||
assert_eq!(
|
||||
mw.project_group_keys(),
|
||||
vec![
|
||||
ProjectGroupKey::new(None, PathList::new(&[path!("/project_b")])),
|
||||
ProjectGroupKey::new(None, PathList::new(&[path!("/project_a")]))
|
||||
]
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
assert!(workspace_a.read_with(cx, |workspace, _cx| workspace.session_id().is_some()),);
|
||||
}
|
||||
Reference in new issue
Block a user