Prompt to save files on recent project selection (#8673)
This commit is contained in:
parent
91d1146d97
commit
cdf702aeff
5 changed files with 184 additions and 6 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -7171,11 +7171,15 @@ dependencies = [
|
||||||
name = "recent_projects"
|
name = "recent_projects"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"editor",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"language",
|
||||||
"menu",
|
"menu",
|
||||||
"ordered-float 2.10.0",
|
"ordered-float 2.10.0",
|
||||||
"picker",
|
"picker",
|
||||||
|
"project",
|
||||||
|
"serde_json",
|
||||||
"smol",
|
"smol",
|
||||||
"ui",
|
"ui",
|
||||||
"util",
|
"util",
|
||||||
|
|
|
@ -19,3 +19,10 @@ smol.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
|
language = { workspace = true, features = ["test-support"] }
|
||||||
|
project = { workspace = true, features = ["test-support"] }
|
||||||
|
serde_json.workspace = true
|
||||||
|
workspace = { workspace = true, features = ["test-support"] }
|
||||||
|
|
|
@ -224,11 +224,35 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||||
workspace
|
workspace
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
if workspace.database_id() != *candidate_workspace_id {
|
if workspace.database_id() != *candidate_workspace_id {
|
||||||
workspace.open_workspace_for_paths(
|
let candidate_paths = candidate_workspace_location.paths().as_ref().clone();
|
||||||
replace_current_window,
|
if replace_current_window {
|
||||||
candidate_workspace_location.paths().as_ref().clone(),
|
cx.spawn(move |workspace, mut cx| async move {
|
||||||
cx,
|
let continue_replacing = workspace
|
||||||
)
|
.update(&mut cx, |workspace, cx| {
|
||||||
|
workspace.prepare_to_close(true, cx)
|
||||||
|
})?
|
||||||
|
.await?;
|
||||||
|
if continue_replacing {
|
||||||
|
workspace
|
||||||
|
.update(&mut cx, |workspace, cx| {
|
||||||
|
workspace.open_workspace_for_paths(
|
||||||
|
replace_current_window,
|
||||||
|
candidate_paths,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
workspace.open_workspace_for_paths(
|
||||||
|
replace_current_window,
|
||||||
|
candidate_paths,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Task::ready(Ok(()))
|
Task::ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
@ -407,3 +431,136 @@ impl Render for MatchTooltip {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use editor::Editor;
|
||||||
|
use gpui::{TestAppContext, WindowHandle};
|
||||||
|
use project::Project;
|
||||||
|
use serde_json::json;
|
||||||
|
use workspace::{open_paths, AppState};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_prompts_on_dirty_before_submit(cx: &mut TestAppContext) {
|
||||||
|
let app_state = init_test(cx);
|
||||||
|
app_state
|
||||||
|
.fs
|
||||||
|
.as_fake()
|
||||||
|
.insert_tree(
|
||||||
|
"/dir",
|
||||||
|
json!({
|
||||||
|
"main.ts": "a"
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
cx.update(|cx| open_paths(&[PathBuf::from("/dir/main.ts")], &app_state, None, cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
||||||
|
|
||||||
|
let workspace = cx.update(|cx| cx.windows()[0].downcast::<Workspace>().unwrap());
|
||||||
|
workspace
|
||||||
|
.update(cx, |workspace, _| assert!(!workspace.is_edited()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let editor = workspace
|
||||||
|
.read_with(cx, |workspace, cx| {
|
||||||
|
workspace
|
||||||
|
.active_item(cx)
|
||||||
|
.unwrap()
|
||||||
|
.downcast::<Editor>()
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
workspace
|
||||||
|
.update(cx, |_, cx| {
|
||||||
|
editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
workspace
|
||||||
|
.update(cx, |workspace, _| assert!(workspace.is_edited(), "After inserting more text into the editor without saving, we should have a dirty project"))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let recent_projects_picker = open_recent_projects(&workspace, cx);
|
||||||
|
workspace
|
||||||
|
.update(cx, |_, cx| {
|
||||||
|
recent_projects_picker.update(cx, |picker, cx| {
|
||||||
|
assert_eq!(picker.query(cx), "");
|
||||||
|
let delegate = &mut picker.delegate;
|
||||||
|
delegate.matches = vec![StringMatch {
|
||||||
|
candidate_id: 0,
|
||||||
|
score: 1.0,
|
||||||
|
positions: Vec::new(),
|
||||||
|
string: "fake candidate".to_string(),
|
||||||
|
}];
|
||||||
|
delegate.workspaces = vec![(0, WorkspaceLocation::new(vec!["/test/path/"]))];
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!cx.has_pending_prompt(),
|
||||||
|
"Should have no pending prompt on dirty project before opening the new recent project"
|
||||||
|
);
|
||||||
|
cx.dispatch_action((*workspace).into(), menu::Confirm);
|
||||||
|
workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
assert!(
|
||||||
|
workspace.active_modal::<RecentProjects>(cx).is_none(),
|
||||||
|
"Should remove the modal after selecting new recent project"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert!(
|
||||||
|
cx.has_pending_prompt(),
|
||||||
|
"Dirty workspace should prompt before opening the new recent project"
|
||||||
|
);
|
||||||
|
// Cancel
|
||||||
|
cx.simulate_prompt_answer(0);
|
||||||
|
assert!(
|
||||||
|
!cx.has_pending_prompt(),
|
||||||
|
"Should have no pending prompt after cancelling"
|
||||||
|
);
|
||||||
|
workspace
|
||||||
|
.update(cx, |workspace, _| {
|
||||||
|
assert!(
|
||||||
|
workspace.is_edited(),
|
||||||
|
"Should be in the same dirty project after cancelling"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_recent_projects(
|
||||||
|
workspace: &WindowHandle<Workspace>,
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) -> View<Picker<RecentProjectsDelegate>> {
|
||||||
|
cx.dispatch_action((*workspace).into(), OpenRecent);
|
||||||
|
workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
workspace
|
||||||
|
.active_modal::<RecentProjects>(cx)
|
||||||
|
.unwrap()
|
||||||
|
.read(cx)
|
||||||
|
.picker
|
||||||
|
.clone()
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
|
||||||
|
cx.update(|cx| {
|
||||||
|
let state = AppState::test(cx);
|
||||||
|
language::init(cx);
|
||||||
|
crate::init(cx);
|
||||||
|
editor::init(cx);
|
||||||
|
workspace::init_settings(cx);
|
||||||
|
Project::init_settings(cx);
|
||||||
|
state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -284,7 +284,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_name(cx: &mut TestAppContext) {
|
async fn test_spawn_tasks_modal_query_reuse(cx: &mut TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
let fs = FakeFs::new(cx.executor());
|
let fs = FakeFs::new(cx.executor());
|
||||||
fs.insert_tree(
|
fs.insert_tree(
|
||||||
|
|
|
@ -22,6 +22,16 @@ impl WorkspaceLocation {
|
||||||
pub fn paths(&self) -> Arc<Vec<PathBuf>> {
|
pub fn paths(&self) -> Arc<Vec<PathBuf>> {
|
||||||
self.0.clone()
|
self.0.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub fn new<P: AsRef<Path>>(paths: Vec<P>) -> Self {
|
||||||
|
Self(Arc::new(
|
||||||
|
paths
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| p.as_ref().to_path_buf())
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceLocation {
|
impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceLocation {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue