Implement dragging external files to remote projects (#28987)

Release Notes:

- Added the ability to copy external files into remote projects by
dragging them onto the project panel.

---------

Co-authored-by: Peter Tripp <petertripp@gmail.com>
This commit is contained in:
Max Brunsfeld 2025-04-17 11:06:56 -07:00 committed by GitHub
parent fade49a11a
commit 7e928dd615
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 275 additions and 82 deletions

View file

@ -2983,16 +2983,18 @@ impl ProjectPanel {
let open_file_after_drop = paths.len() == 1 && paths[0].is_file();
let Some((target_directory, worktree)) = maybe!({
let worktree = self.project.read(cx).worktree_for_entry(entry_id, cx)?;
let Some((target_directory, worktree, fs)) = maybe!({
let project = self.project.read(cx);
let fs = project.fs().clone();
let worktree = project.worktree_for_entry(entry_id, cx)?;
let entry = worktree.read(cx).entry_for_id(entry_id)?;
let path = worktree.read(cx).absolutize(&entry.path).ok()?;
let target_directory = if path.is_dir() {
path
let path = entry.path.clone();
let target_directory = if entry.is_dir() {
path.to_path_buf()
} else {
path.parent()?.to_path_buf()
};
Some((target_directory, worktree))
Some((target_directory, worktree, fs))
}) else {
return;
};
@ -3034,10 +3036,10 @@ impl ProjectPanel {
}
let task = worktree.update( cx, |worktree, cx| {
worktree.copy_external_entries(target_directory, paths, true, cx)
worktree.copy_external_entries(target_directory.into(), paths, fs, cx)
})?;
let opened_entries = task.await?;
let opened_entries = task.await.with_context(|| "failed to copy external paths")?;
this.update(cx, |this, cx| {
if open_file_after_drop && !opened_entries.is_empty() {
this.open_entry(opened_entries[0], true, false, cx);
@ -3697,7 +3699,6 @@ impl ProjectPanel {
let depth = details.depth;
let worktree_id = details.worktree_id;
let selections = Arc::new(self.marked_entries.clone());
let is_local = self.project.read(cx).is_local();
let dragged_selection = DraggedSelection {
active_selection: selection,
@ -3762,60 +3763,57 @@ impl ProjectPanel {
.border_r_2()
.border_color(border_color)
.hover(|style| style.bg(bg_hover_color).border_color(border_hover_color))
.when(is_local, |div| {
div.on_drag_move::<ExternalPaths>(cx.listener(
move |this, event: &DragMoveEvent<ExternalPaths>, _, cx| {
if event.bounds.contains(&event.event.position) {
if this.last_external_paths_drag_over_entry == Some(entry_id) {
return;
}
this.last_external_paths_drag_over_entry = Some(entry_id);
this.marked_entries.clear();
.on_drag_move::<ExternalPaths>(cx.listener(
move |this, event: &DragMoveEvent<ExternalPaths>, _, cx| {
if event.bounds.contains(&event.event.position) {
if this.last_external_paths_drag_over_entry == Some(entry_id) {
return;
}
this.last_external_paths_drag_over_entry = Some(entry_id);
this.marked_entries.clear();
let Some((worktree, path, entry)) = maybe!({
let worktree = this
.project
.read(cx)
.worktree_for_id(selection.worktree_id, cx)?;
let worktree = worktree.read(cx);
let abs_path = worktree.absolutize(&path).log_err()?;
let path = if abs_path.is_dir() {
path.as_ref()
} else {
path.parent()?
};
let entry = worktree.entry_for_path(path)?;
Some((worktree, path, entry))
}) else {
return;
let Some((worktree, path, entry)) = maybe!({
let worktree = this
.project
.read(cx)
.worktree_for_id(selection.worktree_id, cx)?;
let worktree = worktree.read(cx);
let entry = worktree.entry_for_path(&path)?;
let path = if entry.is_dir() {
path.as_ref()
} else {
path.parent()?
};
Some((worktree, path, entry))
}) else {
return;
};
this.marked_entries.insert(SelectedEntry {
entry_id: entry.id,
worktree_id: worktree.id(),
});
for entry in worktree.child_entries(path) {
this.marked_entries.insert(SelectedEntry {
entry_id: entry.id,
worktree_id: worktree.id(),
});
for entry in worktree.child_entries(path) {
this.marked_entries.insert(SelectedEntry {
entry_id: entry.id,
worktree_id: worktree.id(),
});
}
cx.notify();
}
},
))
.on_drop(cx.listener(
move |this, external_paths: &ExternalPaths, window, cx| {
this.hover_scroll_task.take();
this.last_external_paths_drag_over_entry = None;
this.marked_entries.clear();
this.drop_external_files(external_paths.paths(), entry_id, window, cx);
cx.stop_propagation();
},
))
})
cx.notify();
}
},
))
.on_drop(cx.listener(
move |this, external_paths: &ExternalPaths, window, cx| {
this.hover_scroll_task.take();
this.last_external_paths_drag_over_entry = None;
this.marked_entries.clear();
this.drop_external_files(external_paths.paths(), entry_id, window, cx);
cx.stop_propagation();
},
))
.on_drag_move::<DraggedSelection>(cx.listener(
move |this, event: &DragMoveEvent<DraggedSelection>, window, cx| {
if event.bounds.contains(&event.event.position) {