diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 06baa3b1e4..98881e09ba 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -2343,6 +2343,11 @@ impl ProjectPanel { }) .detach_and_log_err(cx); + if clip_is_cut { + // Convert the clipboard cut entry to a copy entry after the first paste. + self.clipboard = self.clipboard.take().map(ClipboardEntry::to_copy_entry); + } + self.expand_entry(worktree_id, entry.id, cx); Some(()) }); @@ -5033,6 +5038,13 @@ impl ClipboardEntry { ClipboardEntry::Copied(entries) | ClipboardEntry::Cut(entries) => entries, } } + + fn to_copy_entry(self) -> Self { + match self { + ClipboardEntry::Copied(_) => self, + ClipboardEntry::Cut(entries) => ClipboardEntry::Copied(entries), + } + } } #[cfg(test)] diff --git a/crates/project_panel/src/project_panel_tests.rs b/crates/project_panel/src/project_panel_tests.rs index 984a93eb14..22176ed9d7 100644 --- a/crates/project_panel/src/project_panel_tests.rs +++ b/crates/project_panel/src/project_panel_tests.rs @@ -1170,6 +1170,91 @@ async fn test_copy_paste(cx: &mut gpui::TestAppContext) { }); } +#[gpui::test] +async fn test_cut_paste(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/root", + json!({ + "one.txt": "", + "two.txt": "", + "a": {}, + "b": {} + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/root".as_ref()], cx).await; + let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let cx = &mut VisualTestContext::from_window(*workspace, cx); + let panel = workspace.update(cx, ProjectPanel::new).unwrap(); + + select_path_with_mark(&panel, "root/one.txt", cx); + select_path_with_mark(&panel, "root/two.txt", cx); + + assert_eq!( + visible_entries_as_strings(&panel, 0..50, cx), + &[ + "v root", + " > a", + " > b", + " one.txt <== marked", + " two.txt <== selected <== marked", + ] + ); + + panel.update_in(cx, |panel, window, cx| { + panel.cut(&Default::default(), window, cx); + }); + + select_path(&panel, "root/a", cx); + + panel.update_in(cx, |panel, window, cx| { + panel.paste(&Default::default(), window, cx); + }); + cx.executor().run_until_parked(); + + assert_eq!( + visible_entries_as_strings(&panel, 0..50, cx), + &[ + "v root", + " v a", + " one.txt <== marked", + " two.txt <== selected <== marked", + " > b", + ], + "Cut entries should be moved on first paste." + ); + + panel.update_in(cx, |panel, window, cx| { + panel.cancel(&menu::Cancel {}, window, cx) + }); + cx.executor().run_until_parked(); + + select_path(&panel, "root/b", cx); + + panel.update_in(cx, |panel, window, cx| { + panel.paste(&Default::default(), window, cx); + }); + cx.executor().run_until_parked(); + + assert_eq!( + visible_entries_as_strings(&panel, 0..50, cx), + &[ + "v root", + " v a", + " one.txt", + " two.txt", + " v b", + " one.txt", + " two.txt <== selected", + ], + "Cut entries should only be copied for the second paste!" + ); +} + #[gpui::test] async fn test_cut_paste_between_different_worktrees(cx: &mut gpui::TestAppContext) { init_test(cx);