From f3e6a0beabb0ccb49d527ced03841a71098445e2 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 29 May 2024 12:34:58 +0200 Subject: [PATCH] project panel: Copy dragged entry when opt is pressed (#12348) Partially fixes #5119 TODO: - [ ] Change cursor style to Copy when dragging an entry with opt pressed. Release Notes: - Drag-and-dropping a project panel entry with opt modifier pressed now copies the entry instead of moving it. --- crates/project_panel/src/project_panel.rs | 137 +++++++++++++--------- 1 file changed, 83 insertions(+), 54 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 73a64af6f8..5eafa215e1 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -74,6 +74,15 @@ struct DraggedSelection { marked_selections: Arc>, } +impl DraggedSelection { + fn items<'a>(&'a self) -> Box + 'a> { + if self.marked_selections.contains(&self.active_selection) { + Box::new(self.marked_selections.iter()) + } else { + Box::new(std::iter::once(&self.active_selection)) + } + } +} #[derive(Clone, Debug)] struct EditState { worktree_id: WorktreeId, @@ -1202,6 +1211,50 @@ impl ProjectPanel { } } + fn create_paste_path( + &self, + source: &SelectedEntry, + (worktree, target_entry): (Model, &Entry), + cx: &AppContext, + ) -> Option { + let mut new_path = target_entry.path.to_path_buf(); + // If we're pasting into a file, or a directory into itself, go up one level. + if target_entry.is_file() || (target_entry.is_dir() && target_entry.id == source.entry_id) { + new_path.pop(); + } + let clipboard_entry_file_name = self + .project + .read(cx) + .path_for_entry(source.entry_id, cx)? + .path + .file_name()? + .to_os_string(); + new_path.push(&clipboard_entry_file_name); + let extension = new_path.extension().map(|e| e.to_os_string()); + let file_name_without_extension = Path::new(&clipboard_entry_file_name).file_stem()?; + let mut ix = 0; + { + let worktree = worktree.read(cx); + while worktree.entry_for_path(&new_path).is_some() { + new_path.pop(); + + let mut new_file_name = file_name_without_extension.to_os_string(); + new_file_name.push(" copy"); + if ix > 0 { + new_file_name.push(format!(" {}", ix)); + } + if let Some(extension) = extension.as_ref() { + new_file_name.push("."); + new_file_name.push(extension); + } + + new_path.push(new_file_name); + ix += 1; + } + } + Some(new_path) + } + fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { maybe!({ let (worktree, entry) = self.selected_entry_handle(cx)?; @@ -1216,46 +1269,8 @@ impl ProjectPanel { if clipboard_entry.worktree_id != worktree_id { return None; } - - let clipboard_entry_file_name = self - .project - .read(cx) - .path_for_entry(clipboard_entry.entry_id, cx)? - .path - .file_name()? - .to_os_string(); - - let mut new_path = entry.path.to_path_buf(); - // If we're pasting into a file, or a directory into itself, go up one level. - if entry.is_file() || (entry.is_dir() && entry.id == clipboard_entry.entry_id) { - new_path.pop(); - } - - new_path.push(&clipboard_entry_file_name); - let extension = new_path.extension().map(|e| e.to_os_string()); - let file_name_without_extension = - Path::new(&clipboard_entry_file_name).file_stem()?; - let mut ix = 0; - { - let worktree = worktree.read(cx); - while worktree.entry_for_path(&new_path).is_some() { - new_path.pop(); - - let mut new_file_name = file_name_without_extension.to_os_string(); - new_file_name.push(" copy"); - if ix > 0 { - new_file_name.push(format!(" {}", ix)); - } - if let Some(extension) = extension.as_ref() { - new_file_name.push("."); - new_file_name.push(extension); - } - - new_path.push(new_file_name); - ix += 1; - } - } - + let new_path = + self.create_paste_path(clipboard_entry, self.selected_entry_handle(cx)?, cx)?; if clipboard_entries.is_cut() { self.project .update(cx, |project, cx| { @@ -1686,24 +1701,38 @@ impl ProjectPanel { fn drag_onto( &mut self, selections: &DraggedSelection, - dragged_entry_id: ProjectEntryId, + target_entry_id: ProjectEntryId, is_file: bool, cx: &mut ViewContext, ) { - if selections - .marked_selections - .contains(&selections.active_selection) - { - for selection in selections.marked_selections.iter() { - self.move_entry(selection.entry_id, dragged_entry_id, is_file, cx); - } + let should_copy = cx.modifiers().alt; + if should_copy { + let _ = maybe!({ + let project = self.project.read(cx); + let target_worktree = project.worktree_for_entry(target_entry_id, cx)?; + let target_entry = target_worktree + .read(cx) + .entry_for_id(target_entry_id)? + .clone(); + for selection in selections.items() { + let new_path = self.create_paste_path( + &selection, + (target_worktree.clone(), &target_entry), + cx, + )?; + self.project + .update(cx, |project, cx| { + project.copy_entry(selection.entry_id, new_path, cx) + }) + .detach_and_log_err(cx) + } + + Some(()) + }); } else { - self.move_entry( - selections.active_selection.entry_id, - dragged_entry_id, - is_file, - cx, - ); + for selection in selections.items() { + self.move_entry(selection.entry_id, target_entry_id, is_file, cx); + } } }