diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 1bc713b5fb..b1888bb243 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -784,6 +784,27 @@ pub(crate) mod test { } } + impl TestProjectItem { + pub fn new(id: u64, path: &str, cx: &mut MutableAppContext) -> ModelHandle { + let entry_id = Some(ProjectEntryId::from_proto(id)); + let project_path = Some(ProjectPath { + worktree_id: WorktreeId::from_usize(0), + path: Path::new(path).into(), + }); + cx.add_model(|_| Self { + entry_id, + project_path, + }) + } + + pub fn new_untitled(cx: &mut MutableAppContext) -> ModelHandle { + cx.add_model(|_| Self { + project_path: None, + entry_id: None, + }) + } + } + impl TestItem { pub fn new() -> Self { Self { @@ -829,22 +850,9 @@ pub(crate) mod test { self } - pub fn with_project_items( - mut self, - items: &[(u64, &str)], - cx: &mut MutableAppContext, - ) -> Self { - self.project_items - .extend(items.iter().copied().map(|(id, path)| { - let id = ProjectEntryId::from_proto(id); - cx.add_model(|_| TestProjectItem { - entry_id: Some(id), - project_path: Some(ProjectPath { - worktree_id: WorktreeId::from_usize(0), - path: Path::new(path).into(), - }), - }) - })); + pub fn with_project_items(mut self, items: &[ModelHandle]) -> Self { + self.project_items.clear(); + self.project_items.extend(items.iter().cloned()); self } @@ -938,8 +946,12 @@ pub(crate) mod test { self.has_conflict } - fn can_save(&self, _: &AppContext) -> bool { + fn can_save(&self, cx: &AppContext) -> bool { !self.project_items.is_empty() + && self + .project_items + .iter() + .all(|item| item.read(cx).entry_id.is_some()) } fn save( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index de298e7be1..05d6de1ee2 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -804,7 +804,7 @@ impl Pane { items_to_close.sort_by_key(|item| !item.is_singleton(cx)); cx.spawn(|workspace, mut cx| async move { - let mut saved_project_entry_ids = HashSet::default(); + let mut saved_project_items_ids = HashSet::default(); for item in items_to_close.clone() { // Find the item's current index and its set of project entries. Avoid // storing these in advance, in case they have changed since this task @@ -836,7 +836,7 @@ impl Pane { }); let should_save = project_item_ids .iter() - .any(|id| saved_project_entry_ids.insert(*id)); + .any(|id| saved_project_items_ids.insert(*id)); if should_save && !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx) @@ -1672,7 +1672,7 @@ mod tests { use std::sync::Arc; use super::*; - use crate::item::test::TestItem; + use crate::item::test::{TestItem, TestProjectItem}; use gpui::{executor::Deterministic, TestAppContext}; use project::FakeFs; @@ -1861,7 +1861,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_items(&[(1, "one.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]); Pane::add_item( workspace, @@ -1880,7 +1880,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 1") - .with_project_items(&[(1, "1.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); Pane::add_item( workspace, @@ -1899,7 +1899,7 @@ mod tests { let item = TestItem::new() .with_singleton(true) .with_label("buffer 2") - .with_project_items(&[(2, "2.txt")], cx); + .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]); Pane::add_item( workspace, @@ -1918,7 +1918,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1") - .with_project_items(&[(1, "1.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); Pane::add_item( workspace, @@ -1937,7 +1937,7 @@ mod tests { let item = TestItem::new() .with_singleton(false) .with_label("multibuffer 1b") - .with_project_items(&[(1, "1.txt")], cx); + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]); Pane::add_item( workspace, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a521d02b5f..47e2be29df 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2727,7 +2727,7 @@ pub fn open_new(app_state: &Arc, cx: &mut MutableAppContext) -> Task<( mod tests { use std::{cell::RefCell, rc::Rc}; - use crate::item::test::{TestItem, TestItemEvent}; + use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; use super::*; use fs::FakeFs; @@ -2835,10 +2835,10 @@ mod tests { }); let item1 = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(1, "one.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]) }); let item2 = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(2, "two.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)]) }); // Add an item to an empty pane @@ -2943,7 +2943,7 @@ mod tests { let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_items(&[(1, "1.txt")], cx) + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); workspace.update(cx, |w, cx| { w.add_item(Box::new(item2.clone()), cx); @@ -2971,21 +2971,25 @@ mod tests { let item1 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) - .with_project_items(&[(1, "1.txt")], cx) + .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let item2 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_items(&[(2, "2.txt")], cx) + .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]) }); let item3 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_conflict(true) - .with_project_items(&[(3, "3.txt")], cx) + .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)]) + }); + let item4 = cx.add_view(&workspace, |cx| { + TestItem::new() + .with_dirty(true) + .with_project_items(&[TestProjectItem::new_untitled(cx)]) }); - let item4 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true)); let pane = workspace.update(cx, |workspace, cx| { workspace.add_item(Box::new(item1.clone()), cx); workspace.add_item(Box::new(item2.clone()), cx); @@ -3007,15 +3011,20 @@ mod tests { [item1_id, item3_id, item4_id].contains(&id) }) }); - cx.foreground().run_until_parked(); + + // There's a prompt to save item 1. pane.read_with(cx, |pane, _| { assert_eq!(pane.items_len(), 4); assert_eq!(pane.active_item().unwrap().id(), item1.id()); }); + assert!(cx.has_pending_prompt(window_id)); + // Confirm saving item 1. cx.simulate_prompt_answer(window_id, 0); cx.foreground().run_until_parked(); + + // Item 1 is saved. There's a prompt to save item 3. pane.read_with(cx, |pane, cx| { assert_eq!(item1.read(cx).save_count, 1); assert_eq!(item1.read(cx).save_as_count, 0); @@ -3023,9 +3032,13 @@ mod tests { assert_eq!(pane.items_len(), 3); assert_eq!(pane.active_item().unwrap().id(), item3.id()); }); + assert!(cx.has_pending_prompt(window_id)); + // Cancel saving item 3. cx.simulate_prompt_answer(window_id, 1); cx.foreground().run_until_parked(); + + // Item 3 is reloaded. There's a prompt to save item 4. pane.read_with(cx, |pane, cx| { assert_eq!(item3.read(cx).save_count, 0); assert_eq!(item3.read(cx).save_as_count, 0); @@ -3033,11 +3046,17 @@ mod tests { assert_eq!(pane.items_len(), 2); assert_eq!(pane.active_item().unwrap().id(), item4.id()); }); + assert!(cx.has_pending_prompt(window_id)); + // Confirm saving item 4. cx.simulate_prompt_answer(window_id, 0); cx.foreground().run_until_parked(); + + // There's a prompt for a path for item 4. cx.simulate_new_path_selection(|_| Some(Default::default())); close_items.await.unwrap(); + + // The requested items are closed. pane.read_with(cx, |pane, cx| { assert_eq!(item4.read(cx).save_count, 0); assert_eq!(item4.read(cx).save_as_count, 1); @@ -3063,10 +3082,13 @@ mod tests { let single_entry_items = (0..=4) .map(|project_entry_id| { cx.add_view(&workspace, |cx| { - TestItem::new().with_dirty(true).with_project_items( - &[(project_entry_id, &format!("{project_entry_id}.txt"))], - cx, - ) + TestItem::new() + .with_dirty(true) + .with_project_items(&[TestProjectItem::new( + project_entry_id, + &format!("{project_entry_id}.txt"), + cx, + )]) }) }) .collect::>(); @@ -3074,13 +3096,19 @@ mod tests { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_items(&[(2, "2.txt"), (3, "3.txt")], cx) + .with_project_items(&[ + single_entry_items[2].read(cx).project_items[0].clone(), + single_entry_items[3].read(cx).project_items[0].clone(), + ]) }); let item_3_4 = cx.add_view(&workspace, |cx| { TestItem::new() .with_dirty(true) .with_singleton(false) - .with_project_items(&[(3, "3.txt"), (4, "4.txt")], cx) + .with_project_items(&[ + single_entry_items[3].read(cx).project_items[0].clone(), + single_entry_items[4].read(cx).project_items[0].clone(), + ]) }); // Create two panes that contain the following project entries: @@ -3159,7 +3187,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(1, "1.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let item_id = item.id(); workspace.update(cx, |workspace, cx| { @@ -3278,7 +3306,7 @@ mod tests { }); let item = cx.add_view(&workspace, |cx| { - TestItem::new().with_project_items(&[(1, "1.txt")], cx) + TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) }); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());