project_panel: Highlight containing folder which would be the target of the drop operation (#31976)
Part of https://github.com/zed-industries/zed/issues/14496 This PR adds highlighting on the containing folder which would be the target of the drop operation. It only highlights those directories where actual drop is possible, i.e. same directory where drag started is not highlighted. - [x] Tests https://github.com/user-attachments/assets/46528467-e07a-4574-a8d5-beab25e70162 Release Notes: - Improved project panel to show a highlight on the containing folder which would be the target of the drop operation.
This commit is contained in:
parent
2645591cd5
commit
d8195a8fd7
2 changed files with 391 additions and 89 deletions
|
@ -22,7 +22,7 @@ use gpui::{
|
|||
Hsla, InteractiveElement, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior,
|
||||
MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, ScrollStrategy,
|
||||
Stateful, Styled, Subscription, Task, UniformListScrollHandle, WeakEntity, Window, actions,
|
||||
anchored, deferred, div, impl_actions, point, px, size, uniform_list,
|
||||
anchored, deferred, div, impl_actions, point, px, size, transparent_white, uniform_list,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use language::DiagnosticSeverity;
|
||||
|
@ -85,8 +85,7 @@ pub struct ProjectPanel {
|
|||
ancestors: HashMap<ProjectEntryId, FoldedAncestors>,
|
||||
folded_directory_drag_target: Option<FoldedDirectoryDragTarget>,
|
||||
last_worktree_root_id: Option<ProjectEntryId>,
|
||||
last_selection_drag_over_entry: Option<ProjectEntryId>,
|
||||
last_external_paths_drag_over_entry: Option<ProjectEntryId>,
|
||||
drag_target_entry: Option<DragTargetEntry>,
|
||||
expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
|
||||
unfolded_dir_ids: HashSet<ProjectEntryId>,
|
||||
// Currently selected leaf entry (see auto-folding for a definition of that) in a file tree
|
||||
|
@ -112,6 +111,13 @@ pub struct ProjectPanel {
|
|||
hover_expand_task: Option<Task<()>>,
|
||||
}
|
||||
|
||||
struct DragTargetEntry {
|
||||
/// The entry currently under the mouse cursor during a drag operation
|
||||
entry_id: ProjectEntryId,
|
||||
/// Highlight this entry along with all of its children
|
||||
highlight_entry_id: Option<ProjectEntryId>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct FoldedDirectoryDragTarget {
|
||||
entry_id: ProjectEntryId,
|
||||
|
@ -472,9 +478,8 @@ impl ProjectPanel {
|
|||
visible_entries: Default::default(),
|
||||
ancestors: Default::default(),
|
||||
folded_directory_drag_target: None,
|
||||
drag_target_entry: None,
|
||||
last_worktree_root_id: Default::default(),
|
||||
last_external_paths_drag_over_entry: None,
|
||||
last_selection_drag_over_entry: None,
|
||||
expanded_dir_ids: Default::default(),
|
||||
unfolded_dir_ids: Default::default(),
|
||||
selection: None,
|
||||
|
@ -3703,6 +3708,67 @@ impl ProjectPanel {
|
|||
(depth, difference)
|
||||
}
|
||||
|
||||
fn highlight_entry_for_external_drag(
|
||||
&self,
|
||||
target_entry: &Entry,
|
||||
target_worktree: &Worktree,
|
||||
) -> Option<ProjectEntryId> {
|
||||
// Always highlight directory or parent directory if it's file
|
||||
if target_entry.is_dir() {
|
||||
Some(target_entry.id)
|
||||
} else if let Some(parent_entry) = target_entry
|
||||
.path
|
||||
.parent()
|
||||
.and_then(|parent_path| target_worktree.entry_for_path(parent_path))
|
||||
{
|
||||
Some(parent_entry.id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_entry_for_selection_drag(
|
||||
&self,
|
||||
target_entry: &Entry,
|
||||
target_worktree: &Worktree,
|
||||
dragged_selection: &DraggedSelection,
|
||||
cx: &Context<Self>,
|
||||
) -> Option<ProjectEntryId> {
|
||||
let target_parent_path = target_entry.path.parent();
|
||||
|
||||
// In case of single item drag, we do not highlight existing
|
||||
// directory which item belongs too
|
||||
if dragged_selection.items().count() == 1 {
|
||||
let active_entry_path = self
|
||||
.project
|
||||
.read(cx)
|
||||
.path_for_entry(dragged_selection.active_selection.entry_id, cx)?;
|
||||
|
||||
if let Some(active_parent_path) = active_entry_path.path.parent() {
|
||||
// Do not highlight active entry parent
|
||||
if active_parent_path == target_entry.path.as_ref() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Do not highlight active entry sibling files
|
||||
if Some(active_parent_path) == target_parent_path && target_entry.is_file() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always highlight directory or parent directory if it's file
|
||||
if target_entry.is_dir() {
|
||||
Some(target_entry.id)
|
||||
} else if let Some(parent_entry) =
|
||||
target_parent_path.and_then(|parent_path| target_worktree.entry_for_path(parent_path))
|
||||
{
|
||||
Some(parent_entry.id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_entry(
|
||||
&self,
|
||||
entry_id: ProjectEntryId,
|
||||
|
@ -3745,6 +3811,8 @@ impl ProjectPanel {
|
|||
.as_ref()
|
||||
.map(|f| f.to_string_lossy().to_string());
|
||||
let path = details.path.clone();
|
||||
let path_for_external_paths = path.clone();
|
||||
let path_for_dragged_selection = path.clone();
|
||||
|
||||
let depth = details.depth;
|
||||
let worktree_id = details.worktree_id;
|
||||
|
@ -3802,6 +3870,27 @@ impl ProjectPanel {
|
|||
};
|
||||
|
||||
let folded_directory_drag_target = self.folded_directory_drag_target;
|
||||
let is_highlighted = {
|
||||
if let Some(highlight_entry_id) = self
|
||||
.drag_target_entry
|
||||
.as_ref()
|
||||
.and_then(|drag_target| drag_target.highlight_entry_id)
|
||||
{
|
||||
// Highlight if same entry or it's children
|
||||
if entry_id == highlight_entry_id {
|
||||
true
|
||||
} else {
|
||||
maybe!({
|
||||
let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?;
|
||||
let highlight_entry = worktree.read(cx).entry_for_id(highlight_entry_id)?;
|
||||
Some(path.starts_with(&highlight_entry.path))
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
div()
|
||||
.id(entry_id.to_proto() as usize)
|
||||
|
@ -3815,95 +3904,111 @@ impl ProjectPanel {
|
|||
.hover(|style| style.bg(bg_hover_color).border_color(border_hover_color))
|
||||
.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;
|
||||
let is_current_target = this.drag_target_entry.as_ref()
|
||||
.map(|entry| entry.entry_id) == Some(entry_id);
|
||||
|
||||
if !event.bounds.contains(&event.event.position) {
|
||||
// Entry responsible for setting drag target is also responsible to
|
||||
// clear it up after drag is out of bounds
|
||||
if is_current_target {
|
||||
this.drag_target_entry = None;
|
||||
}
|
||||
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 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(),
|
||||
});
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
|
||||
if is_current_target {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some((entry_id, highlight_entry_id)) = maybe!({
|
||||
let target_worktree = this.project.read(cx).worktree_for_id(selection.worktree_id, cx)?.read(cx);
|
||||
let target_entry = target_worktree.entry_for_path(&path_for_external_paths)?;
|
||||
let highlight_entry_id = this.highlight_entry_for_external_drag(target_entry, target_worktree);
|
||||
Some((target_entry.id, highlight_entry_id))
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
this.drag_target_entry = Some(DragTargetEntry {
|
||||
entry_id,
|
||||
highlight_entry_id,
|
||||
});
|
||||
this.marked_entries.clear();
|
||||
},
|
||||
))
|
||||
.on_drop(cx.listener(
|
||||
move |this, external_paths: &ExternalPaths, window, cx| {
|
||||
this.drag_target_entry = None;
|
||||
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) {
|
||||
if this.last_selection_drag_over_entry == Some(entry_id) {
|
||||
return;
|
||||
}
|
||||
this.last_selection_drag_over_entry = Some(entry_id);
|
||||
this.hover_expand_task.take();
|
||||
let is_current_target = this.drag_target_entry.as_ref()
|
||||
.map(|entry| entry.entry_id) == Some(entry_id);
|
||||
|
||||
if !kind.is_dir()
|
||||
|| this
|
||||
.expanded_dir_ids
|
||||
.get(&details.worktree_id)
|
||||
.map_or(false, |ids| ids.binary_search(&entry_id).is_ok())
|
||||
{
|
||||
return;
|
||||
if !event.bounds.contains(&event.event.position) {
|
||||
// Entry responsible for setting drag target is also responsible to
|
||||
// clear it up after drag is out of bounds
|
||||
if is_current_target {
|
||||
this.drag_target_entry = None;
|
||||
}
|
||||
|
||||
let bounds = event.bounds;
|
||||
this.hover_expand_task =
|
||||
Some(cx.spawn_in(window, async move |this, cx| {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(500))
|
||||
.await;
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.hover_expand_task.take();
|
||||
if this.last_selection_drag_over_entry == Some(entry_id)
|
||||
&& bounds.contains(&window.mouse_position())
|
||||
{
|
||||
this.expand_entry(worktree_id, entry_id, cx);
|
||||
this.update_visible_entries(
|
||||
Some((worktree_id, entry_id)),
|
||||
cx,
|
||||
);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if is_current_target {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some((entry_id, highlight_entry_id)) = maybe!({
|
||||
let target_worktree = this.project.read(cx).worktree_for_id(selection.worktree_id, cx)?.read(cx);
|
||||
let target_entry = target_worktree.entry_for_path(&path_for_dragged_selection)?;
|
||||
let dragged_selection = event.drag(cx);
|
||||
let highlight_entry_id = this.highlight_entry_for_selection_drag(target_entry, target_worktree, dragged_selection, cx);
|
||||
Some((target_entry.id, highlight_entry_id))
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
this.drag_target_entry = Some(DragTargetEntry {
|
||||
entry_id,
|
||||
highlight_entry_id,
|
||||
});
|
||||
this.marked_entries.clear();
|
||||
this.hover_expand_task.take();
|
||||
|
||||
if !kind.is_dir()
|
||||
|| this
|
||||
.expanded_dir_ids
|
||||
.get(&details.worktree_id)
|
||||
.map_or(false, |ids| ids.binary_search(&entry_id).is_ok())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let bounds = event.bounds;
|
||||
this.hover_expand_task =
|
||||
Some(cx.spawn_in(window, async move |this, cx| {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(500))
|
||||
.await;
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.hover_expand_task.take();
|
||||
if this.drag_target_entry.as_ref().map(|entry| entry.entry_id) == Some(entry_id)
|
||||
&& bounds.contains(&window.mouse_position())
|
||||
{
|
||||
this.expand_entry(worktree_id, entry_id, cx);
|
||||
this.update_visible_entries(
|
||||
Some((worktree_id, entry_id)),
|
||||
cx,
|
||||
);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}));
|
||||
},
|
||||
))
|
||||
.on_drag(
|
||||
|
@ -3917,14 +4022,10 @@ impl ProjectPanel {
|
|||
})
|
||||
},
|
||||
)
|
||||
.drag_over::<DraggedSelection>(move |style, _, _, _| {
|
||||
if folded_directory_drag_target.is_some() {
|
||||
return style;
|
||||
}
|
||||
style.bg(item_colors.drag_over)
|
||||
})
|
||||
.when(is_highlighted && folded_directory_drag_target.is_none(), |this| this.border_color(transparent_white()).bg(item_colors.drag_over))
|
||||
.on_drop(
|
||||
cx.listener(move |this, selections: &DraggedSelection, window, cx| {
|
||||
this.drag_target_entry = None;
|
||||
this.hover_scroll_task.take();
|
||||
this.hover_expand_task.take();
|
||||
if folded_directory_drag_target.is_some() {
|
||||
|
@ -4126,6 +4227,7 @@ impl ProjectPanel {
|
|||
div()
|
||||
.on_drop(cx.listener(move |this, selections: &DraggedSelection, window, cx| {
|
||||
this.hover_scroll_task.take();
|
||||
this.drag_target_entry = None;
|
||||
this.folded_directory_drag_target = None;
|
||||
if let Some(target_entry_id) = target_entry_id {
|
||||
this.drag_onto(selections, target_entry_id, kind.is_file(), window, cx);
|
||||
|
@ -4208,6 +4310,7 @@ impl ProjectPanel {
|
|||
))
|
||||
.on_drop(cx.listener(move |this, selections: &DraggedSelection, window,cx| {
|
||||
this.hover_scroll_task.take();
|
||||
this.drag_target_entry = None;
|
||||
this.folded_directory_drag_target = None;
|
||||
if let Some(target_entry_id) = target_entry_id {
|
||||
this.drag_onto(selections, target_entry_id, kind.is_file(), window, cx);
|
||||
|
@ -4573,13 +4676,14 @@ impl Render for ProjectPanel {
|
|||
.map(|(_, worktree_entries, _)| worktree_entries.len())
|
||||
.sum();
|
||||
|
||||
fn handle_drag_move_scroll<T: 'static>(
|
||||
fn handle_drag_move<T: 'static>(
|
||||
this: &mut ProjectPanel,
|
||||
e: &DragMoveEvent<T>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<ProjectPanel>,
|
||||
) {
|
||||
if !e.bounds.contains(&e.event.position) {
|
||||
this.drag_target_entry = None;
|
||||
return;
|
||||
}
|
||||
this.hover_scroll_task.take();
|
||||
|
@ -4633,8 +4737,8 @@ impl Render for ProjectPanel {
|
|||
h_flex()
|
||||
.id("project-panel")
|
||||
.group("project-panel")
|
||||
.on_drag_move(cx.listener(handle_drag_move_scroll::<ExternalPaths>))
|
||||
.on_drag_move(cx.listener(handle_drag_move_scroll::<DraggedSelection>))
|
||||
.on_drag_move(cx.listener(handle_drag_move::<ExternalPaths>))
|
||||
.on_drag_move(cx.listener(handle_drag_move::<DraggedSelection>))
|
||||
.size_full()
|
||||
.relative()
|
||||
.on_hover(cx.listener(|this, hovered, window, cx| {
|
||||
|
@ -4890,8 +4994,7 @@ impl Render for ProjectPanel {
|
|||
})
|
||||
.on_drop(cx.listener(
|
||||
move |this, external_paths: &ExternalPaths, window, cx| {
|
||||
this.last_external_paths_drag_over_entry = None;
|
||||
this.marked_entries.clear();
|
||||
this.drag_target_entry = None;
|
||||
this.hover_scroll_task.take();
|
||||
if let Some(task) = this
|
||||
.workspace
|
||||
|
|
|
@ -5098,6 +5098,205 @@ async fn test_create_entries_without_selection(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_highlight_entry_for_external_drag(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"dir1": {
|
||||
"file1.txt": "",
|
||||
"dir2": {
|
||||
"file2.txt": ""
|
||||
}
|
||||
},
|
||||
"file3.txt": ""
|
||||
}),
|
||||
)
|
||||
.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();
|
||||
|
||||
panel.update(cx, |panel, cx| {
|
||||
let project = panel.project.read(cx);
|
||||
let worktree = project.visible_worktrees(cx).next().unwrap();
|
||||
let worktree = worktree.read(cx);
|
||||
|
||||
// Test 1: Target is a directory, should highlight the directory itself
|
||||
let dir_entry = worktree.entry_for_path("dir1").unwrap();
|
||||
let result = panel.highlight_entry_for_external_drag(dir_entry, worktree);
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(dir_entry.id),
|
||||
"Should highlight directory itself"
|
||||
);
|
||||
|
||||
// Test 2: Target is nested file, should highlight immediate parent
|
||||
let nested_file = worktree.entry_for_path("dir1/dir2/file2.txt").unwrap();
|
||||
let nested_parent = worktree.entry_for_path("dir1/dir2").unwrap();
|
||||
let result = panel.highlight_entry_for_external_drag(nested_file, worktree);
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(nested_parent.id),
|
||||
"Should highlight immediate parent"
|
||||
);
|
||||
|
||||
// Test 3: Target is root level file, should highlight root
|
||||
let root_file = worktree.entry_for_path("file3.txt").unwrap();
|
||||
let result = panel.highlight_entry_for_external_drag(root_file, worktree);
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(worktree.root_entry().unwrap().id),
|
||||
"Root level file should return None"
|
||||
);
|
||||
|
||||
// Test 4: Target is root itself, should highlight root
|
||||
let root_entry = worktree.root_entry().unwrap();
|
||||
let result = panel.highlight_entry_for_external_drag(root_entry, worktree);
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(root_entry.id),
|
||||
"Root level file should return None"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_highlight_entry_for_selection_drag(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"parent_dir": {
|
||||
"child_file.txt": "",
|
||||
"sibling_file.txt": "",
|
||||
"child_dir": {
|
||||
"nested_file.txt": ""
|
||||
}
|
||||
},
|
||||
"other_dir": {
|
||||
"other_file.txt": ""
|
||||
}
|
||||
}),
|
||||
)
|
||||
.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();
|
||||
|
||||
panel.update(cx, |panel, cx| {
|
||||
let project = panel.project.read(cx);
|
||||
let worktree = project.visible_worktrees(cx).next().unwrap();
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
let worktree = worktree.read(cx);
|
||||
|
||||
let parent_dir = worktree.entry_for_path("parent_dir").unwrap();
|
||||
let child_file = worktree
|
||||
.entry_for_path("parent_dir/child_file.txt")
|
||||
.unwrap();
|
||||
let sibling_file = worktree
|
||||
.entry_for_path("parent_dir/sibling_file.txt")
|
||||
.unwrap();
|
||||
let child_dir = worktree.entry_for_path("parent_dir/child_dir").unwrap();
|
||||
let other_dir = worktree.entry_for_path("other_dir").unwrap();
|
||||
let other_file = worktree.entry_for_path("other_dir/other_file.txt").unwrap();
|
||||
|
||||
// Test 1: Single item drag, don't highlight parent directory
|
||||
let dragged_selection = DraggedSelection {
|
||||
active_selection: SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: child_file.id,
|
||||
},
|
||||
marked_selections: Arc::new(BTreeSet::from([SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: child_file.id,
|
||||
}])),
|
||||
};
|
||||
let result =
|
||||
panel.highlight_entry_for_selection_drag(parent_dir, worktree, &dragged_selection, cx);
|
||||
assert_eq!(result, None, "Should not highlight parent of dragged item");
|
||||
|
||||
// Test 2: Single item drag, don't highlight sibling files
|
||||
let result = panel.highlight_entry_for_selection_drag(
|
||||
sibling_file,
|
||||
worktree,
|
||||
&dragged_selection,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(result, None, "Should not highlight sibling files");
|
||||
|
||||
// Test 3: Single item drag, highlight unrelated directory
|
||||
let result =
|
||||
panel.highlight_entry_for_selection_drag(other_dir, worktree, &dragged_selection, cx);
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(other_dir.id),
|
||||
"Should highlight unrelated directory"
|
||||
);
|
||||
|
||||
// Test 4: Single item drag, highlight sibling directory
|
||||
let result =
|
||||
panel.highlight_entry_for_selection_drag(child_dir, worktree, &dragged_selection, cx);
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(child_dir.id),
|
||||
"Should highlight sibling directory"
|
||||
);
|
||||
|
||||
// Test 5: Multiple items drag, highlight parent directory
|
||||
let dragged_selection = DraggedSelection {
|
||||
active_selection: SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: child_file.id,
|
||||
},
|
||||
marked_selections: Arc::new(BTreeSet::from([
|
||||
SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: child_file.id,
|
||||
},
|
||||
SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: sibling_file.id,
|
||||
},
|
||||
])),
|
||||
};
|
||||
let result =
|
||||
panel.highlight_entry_for_selection_drag(parent_dir, worktree, &dragged_selection, cx);
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(parent_dir.id),
|
||||
"Should highlight parent with multiple items"
|
||||
);
|
||||
|
||||
// Test 6: Target is file in different directory, highlight parent
|
||||
let result =
|
||||
panel.highlight_entry_for_selection_drag(other_file, worktree, &dragged_selection, cx);
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(other_dir.id),
|
||||
"Should highlight parent of target file"
|
||||
);
|
||||
|
||||
// Test 7: Target is directory, always highlight
|
||||
let result =
|
||||
panel.highlight_entry_for_selection_drag(child_dir, worktree, &dragged_selection, cx);
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(child_dir.id),
|
||||
"Should always highlight directories"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn select_path(panel: &Entity<ProjectPanel>, path: impl AsRef<Path>, cx: &mut VisualTestContext) {
|
||||
let path = path.as_ref();
|
||||
panel.update(cx, |panel, cx| {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue