diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index eb4b8ea7ea77520d2e45e5c1f1d2e955aa3ae596..06b05e2a11d5c34d7a71babfadbf2282ff3b6afa 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2202,6 +2202,14 @@ impl Pane { let path_style = project.read_with(cx, |project, cx| project.path_style(cx)); if save_intent == SaveIntent::Skip { + let is_saveable_singleton = cx.update(|_window, cx| { + item.can_save(cx) && item.buffer_kind(cx) == ItemBufferKind::Singleton + })?; + if is_saveable_singleton { + pane.update_in(cx, |_, window, cx| item.reload(project, window, cx))? + .await + .log_err(); + } return Ok(true); }; let Some(item_ix) = pane @@ -2350,13 +2358,20 @@ impl Pane { match answer { Ok(0) => {} Ok(1) => { - // Don't save this file + // Don't save this file - reload from disk to discard changes pane.update_in(cx, |pane, _, cx| { if pane.is_tab_pinned(item_ix) && !item.can_save(cx) { pane.pinned_tab_count -= 1; } }) .log_err(); + if can_save && is_singleton { + pane.update_in(cx, |_, window, cx| { + item.reload(project.clone(), window, cx) + })? + .await + .log_err(); + } return Ok(true); } _ => return Ok(false), // Cancel @@ -7204,6 +7219,166 @@ mod tests { assert_item_labels(&pane, ["Dirty*^"], cx); } + #[gpui::test] + async fn test_discard_all_reloads_from_disk(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + + let project = Project::test(fs, None, cx).await; + let (workspace, cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + let item_a = add_labeled_item(&pane, "A", true, cx); + item_a.update(cx, |item, cx| { + item.project_items + .push(TestProjectItem::new_dirty(1, "A.txt", cx)) + }); + let item_b = add_labeled_item(&pane, "B", true, cx); + item_b.update(cx, |item, cx| { + item.project_items + .push(TestProjectItem::new_dirty(2, "B.txt", cx)) + }); + assert_item_labels(&pane, ["A^", "B*^"], cx); + + let close_task = pane.update_in(cx, |pane, window, cx| { + pane.close_all_items( + &CloseAllItems { + save_intent: None, + close_pinned: false, + }, + window, + cx, + ) + }); + + cx.executor().run_until_parked(); + cx.simulate_prompt_answer("Discard all"); + close_task.await.unwrap(); + assert_item_labels(&pane, [], cx); + + item_a.read_with(cx, |item, _| { + assert_eq!(item.reload_count, 1, "item A should have been reloaded"); + assert!( + !item.is_dirty, + "item A should no longer be dirty after reload" + ); + }); + item_b.read_with(cx, |item, _| { + assert_eq!(item.reload_count, 1, "item B should have been reloaded"); + assert!( + !item.is_dirty, + "item B should no longer be dirty after reload" + ); + }); + } + + #[gpui::test] + async fn test_dont_save_single_file_reloads_from_disk(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + + let project = Project::test(fs, None, cx).await; + let (workspace, cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + let item = add_labeled_item(&pane, "Dirty", true, cx); + item.update(cx, |item, cx| { + item.project_items + .push(TestProjectItem::new_dirty(1, "Dirty.txt", cx)) + }); + assert_item_labels(&pane, ["Dirty*^"], cx); + + let close_task = pane.update_in(cx, |pane, window, cx| { + pane.close_item_by_id(item.item_id(), SaveIntent::Close, window, cx) + }); + + cx.executor().run_until_parked(); + cx.simulate_prompt_answer("Don't Save"); + close_task.await.unwrap(); + assert_item_labels(&pane, [], cx); + + item.read_with(cx, |item, _| { + assert_eq!(item.reload_count, 1, "item should have been reloaded"); + assert!( + !item.is_dirty, + "item should no longer be dirty after reload" + ); + }); + } + + #[gpui::test] + async fn test_discard_does_not_reload_multibuffer(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + + let project = Project::test(fs, None, cx).await; + let (workspace, cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + let singleton_item = pane.update_in(cx, |pane, window, cx| { + let item = Box::new(cx.new(|cx| { + TestItem::new(cx) + .with_label("Singleton") + .with_dirty(true) + .with_buffer_kind(ItemBufferKind::Singleton) + })); + pane.add_item(item.clone(), false, false, None, window, cx); + item + }); + singleton_item.update(cx, |item, cx| { + item.project_items + .push(TestProjectItem::new_dirty(1, "Singleton.txt", cx)) + }); + + let multi_item = pane.update_in(cx, |pane, window, cx| { + let item = Box::new(cx.new(|cx| { + TestItem::new(cx) + .with_label("Multi") + .with_dirty(true) + .with_buffer_kind(ItemBufferKind::Multibuffer) + })); + pane.add_item(item.clone(), false, false, None, window, cx); + item + }); + multi_item.update(cx, |item, cx| { + item.project_items + .push(TestProjectItem::new_dirty(2, "Multi.txt", cx)) + }); + + let close_task = pane.update_in(cx, |pane, window, cx| { + pane.close_all_items( + &CloseAllItems { + save_intent: None, + close_pinned: false, + }, + window, + cx, + ) + }); + + cx.executor().run_until_parked(); + cx.simulate_prompt_answer("Discard all"); + close_task.await.unwrap(); + assert_item_labels(&pane, [], cx); + + singleton_item.read_with(cx, |item, _| { + assert_eq!(item.reload_count, 1, "singleton should have been reloaded"); + assert!( + !item.is_dirty, + "singleton should no longer be dirty after reload" + ); + }); + multi_item.read_with(cx, |item, _| { + assert_eq!( + item.reload_count, 0, + "multibuffer should not have been reloaded" + ); + }); + } + #[gpui::test] async fn test_close_multibuffer_items(cx: &mut TestAppContext) { init_test(cx);