diff --git a/Cargo.lock b/Cargo.lock index 0ce0621443ce998245474bded4f2d2296591cd1c..56531e8c40df2ef1dcb9c22929f9267e94689379 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12648,6 +12648,7 @@ dependencies = [ "git", "git_ui", "gpui", + "itertools 0.14.0", "language", "menu", "notifications", diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index 0385c3789e923da95a1eca7a5a469bad00020639..88d85c75f9e6452a72eb4181a94a8bf6395ba754 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -26,6 +26,7 @@ file_icons.workspace = true git_ui.workspace = true git.workspace = true gpui.workspace = true +itertools.workspace = true menu.workspace = true pretty_assertions.workspace = true project.workspace = true diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index dd9caf0ca5481fbda7cd77c88edec9b3bcfe2ec1..81450f850e0ac386c6adb2aca2abf5c30de0a700 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -64,7 +64,7 @@ use ui::{ }; use util::{ ResultExt, TakeUntilExt, TryFutureExt, maybe, - paths::compare_paths, + paths::{PathStyle, compare_paths}, rel_path::{RelPath, RelPathBuf}, }; use workspace::{ @@ -4855,149 +4855,189 @@ impl ProjectPanel { .border_r_2() .border_color(border_color) .hover(|style| style.bg(bg_hover_color).border_color(border_hover_color)) - .when(is_sticky, |this| { - this.block_mouse_except_scroll() - }) + .when(is_sticky, |this| this.block_mouse_except_scroll()) .when(!is_sticky, |this| { - this - .when(is_highlighted && folded_directory_drag_target.is_none(), |this| this.border_color(transparent_white()).bg(item_colors.drag_over)) - .when(settings.drag_and_drop, |this| this - .on_drag_move::(cx.listener( - move |this, event: &DragMoveEvent, _, cx| { - let is_current_target = this.drag_target_entry.as_ref() - .and_then(|entry| match entry { - DragTarget::Entry { entry_id: target_id, .. } => Some(*target_id), - DragTarget::Background { .. } => None, - }) == 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 + this.when( + is_highlighted && folded_directory_drag_target.is_none(), + |this| { + this.border_color(transparent_white()) + .bg(item_colors.drag_over) + }, + ) + .when(settings.drag_and_drop, |this| { + this.on_drag_move::(cx.listener( + move |this, event: &DragMoveEvent, _, cx| { + let is_current_target = + this.drag_target_entry + .as_ref() + .and_then(|entry| match entry { + DragTarget::Entry { + entry_id: target_id, + .. + } => Some(*target_id), + DragTarget::Background { .. } => None, + }) + == 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; + } + return; + } + if is_current_target { - this.drag_target_entry = None; + return; } - return; - } - if is_current_target { - return; - } + this.marked_entries.clear(); - this.marked_entries.clear(); + 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; + }; - 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(DragTarget::Entry { + entry_id, + highlight_entry_id, + }); + }, + )) + .on_drop(cx.listener( + move |this, external_paths: &ExternalPaths, window, cx| { + this.drag_target_entry = None; + this.hover_scroll_task.take(); + this.drop_external_files(external_paths.paths(), entry_id, window, cx); + cx.stop_propagation(); + }, + )) + .on_drag_move::(cx.listener( + move |this, event: &DragMoveEvent, window, cx| { + let is_current_target = + this.drag_target_entry + .as_ref() + .and_then(|entry| match entry { + DragTarget::Entry { + entry_id: target_id, + .. + } => Some(*target_id), + DragTarget::Background { .. } => None, + }) + == Some(entry_id); - this.drag_target_entry = Some(DragTarget::Entry { - entry_id, - highlight_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; + } + return; + } - }, - )) - .on_drop(cx.listener( - move |this, external_paths: &ExternalPaths, window, cx| { - this.drag_target_entry = None; - this.hover_scroll_task.take(); - this.drop_external_files(external_paths.paths(), entry_id, window, cx); - cx.stop_propagation(); - }, - )) - .on_drag_move::(cx.listener( - move |this, event: &DragMoveEvent, window, cx| { - let is_current_target = this.drag_target_entry.as_ref() - .and_then(|entry| match entry { - DragTarget::Entry { entry_id: target_id, .. } => Some(*target_id), - DragTarget::Background { .. } => None, - }) == 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; + return; } - return; - } - if is_current_target { - return; - } + let drag_state = event.drag(cx); - let drag_state = event.drag(cx); - - if drag_state.items().count() == 1 { - this.marked_entries.clear(); - this.marked_entries.push(drag_state.active_selection); - } + if drag_state.items().count() == 1 { + this.marked_entries.clear(); + this.marked_entries.push(drag_state.active_selection); + } - 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 highlight_entry_id = this.highlight_entry_for_selection_drag(target_entry, target_worktree, drag_state, cx)?; - Some((target_entry.id, highlight_entry_id)) - }) else { - 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 highlight_entry_id = this.highlight_entry_for_selection_drag( + target_entry, + target_worktree, + drag_state, + cx, + )?; + Some((target_entry.id, highlight_entry_id)) + }) else { + return; + }; - this.drag_target_entry = Some(DragTarget::Entry { - entry_id, - highlight_entry_id, - }); + this.drag_target_entry = Some(DragTarget::Entry { + entry_id, + highlight_entry_id, + }); - this.hover_expand_task.take(); + this.hover_expand_task.take(); - if !kind.is_dir() - || this - .state - .expanded_dir_ids - .get(&details.worktree_id) - .is_some_and(|ids| ids.binary_search(&entry_id).is_ok()) - { - return; - } + if !kind.is_dir() + || this + .state + .expanded_dir_ids + .get(&details.worktree_id) + .is_some_and(|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().and_then(|entry| match entry { - DragTarget::Entry { entry_id: target_id, .. } => Some(*target_id), - DragTarget::Background { .. } => None, - }) == 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)), - false, - false, - window, - cx, - ); - cx.notify(); - } - }) - .ok(); - })); - }, - )) - .on_drag( - dragged_selection, - { - let active_component = self.state.ancestors.get(&entry_id).and_then(|ancestors| ancestors.active_component(&details.filename)); + 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().and_then(|entry| { + match entry { + DragTarget::Entry { + entry_id: target_id, + .. + } => Some(*target_id), + DragTarget::Background { .. } => None, + } + }) == 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)), + false, + false, + window, + cx, + ); + cx.notify(); + } + }) + .ok(); + })); + }, + )) + .on_drag(dragged_selection, { + let active_component = + self.state.ancestors.get(&entry_id).and_then(|ancestors| { + ancestors.active_component(&details.filename) + }); move |selection, click_offset, _window, cx| { - let filename = active_component.as_ref().unwrap_or_else(|| &details.filename); + let filename = active_component + .as_ref() + .unwrap_or_else(|| &details.filename); cx.new(|_| DraggedProjectEntryView { icon: details.icon.clone(), filename: filename.clone(), @@ -5006,19 +5046,19 @@ impl ProjectPanel { selections: selection.marked_selections.clone(), }) } - } - ) - .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() { - return; - } - this.drag_onto(selections, entry_id, kind.is_file(), window, cx); - }), - )) + }) + .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() { + return; + } + this.drag_onto(selections, entry_id, kind.is_file(), window, cx); + }, + )) + }) }) .on_mouse_down( MouseButton::Left, @@ -5029,9 +5069,7 @@ impl ProjectPanel { ) .on_click( cx.listener(move |project_panel, event: &gpui::ClickEvent, window, cx| { - if event.is_right_click() || event.first_focus() - || show_editor - { + if event.is_right_click() || event.first_focus() || show_editor { return; } if event.standard_click() { @@ -5039,7 +5077,11 @@ impl ProjectPanel { } cx.stop_propagation(); - if let Some(selection) = project_panel.state.selection.filter(|_| event.modifiers().shift) { + if let Some(selection) = project_panel + .state + .selection + .filter(|_| event.modifiers().shift) + { let current_selection = project_panel.index_for_selection(selection); let clicked_entry = SelectedEntry { entry_id, @@ -5080,7 +5122,11 @@ impl ProjectPanel { project_panel.split_entry(entry_id, false, None, cx); } else { project_panel.state.selection = Some(selection); - if let Some(position) = project_panel.marked_entries.iter().position(|e| *e == selection) { + if let Some(position) = project_panel + .marked_entries + .iter() + .position(|e| *e == selection) + { project_panel.marked_entries.remove(position); } else { project_panel.marked_entries.push(selection); @@ -5089,28 +5135,37 @@ impl ProjectPanel { } else if kind.is_dir() { project_panel.marked_entries.clear(); if is_sticky - && let Some((_, _, index)) = project_panel.index_for_entry(entry_id, worktree_id) { - project_panel.scroll_handle.scroll_to_item_strict_with_offset(index, ScrollStrategy::Top, sticky_index.unwrap_or(0)); - cx.notify(); - // move down by 1px so that clicked item - // don't count as sticky anymore - cx.on_next_frame(window, |_, window, cx| { - cx.on_next_frame(window, |this, _, cx| { - let mut offset = this.scroll_handle.offset(); - offset.y += px(1.); - this.scroll_handle.set_offset(offset); - cx.notify(); - }); + && let Some((_, _, index)) = + project_panel.index_for_entry(entry_id, worktree_id) + { + project_panel + .scroll_handle + .scroll_to_item_strict_with_offset( + index, + ScrollStrategy::Top, + sticky_index.unwrap_or(0), + ); + cx.notify(); + // move down by 1px so that clicked item + // don't count as sticky anymore + cx.on_next_frame(window, |_, window, cx| { + cx.on_next_frame(window, |this, _, cx| { + let mut offset = this.scroll_handle.offset(); + offset.y += px(1.); + this.scroll_handle.set_offset(offset); + cx.notify(); }); - return; - } + }); + return; + } if event.modifiers().alt { project_panel.toggle_expand_all(entry_id, window, cx); } else { project_panel.toggle_expanded(entry_id, window, cx); } } else { - let preview_tabs_enabled = PreviewTabsSettings::get_global(cx).enable_preview_from_project_panel; + let preview_tabs_enabled = + PreviewTabsSettings::get_global(cx).enable_preview_from_project_panel; let click_count = event.click_count(); let focus_opened_item = click_count > 1; let allow_preview = preview_tabs_enabled && click_count == 1; @@ -5124,9 +5179,7 @@ impl ProjectPanel { .indent_step_size(px(settings.indent_size)) .spacing(match settings.entry_spacing { ProjectPanelEntrySpacing::Comfortable => ListItemSpacing::Dense, - ProjectPanelEntrySpacing::Standard => { - ListItemSpacing::ExtraDense - } + ProjectPanelEntrySpacing::Standard => ListItemSpacing::ExtraDense, }) .selectable(false) .when_some(canonical_path, |this, path| { @@ -5135,12 +5188,7 @@ impl ProjectPanel { .id("symlink_icon") .pr_3() .tooltip(move |_window, cx| { - Tooltip::with_meta( - path.to_string(), - None, - "Symbolic Link", - cx, - ) + Tooltip::with_meta(path.to_string(), None, "Symbolic Link", cx) }) .child( Icon::new(IconName::ArrowUpRight) @@ -5200,168 +5248,37 @@ impl ProjectPanel { .invisible() .flex_none() }) - .child( - if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) { - h_flex().h_6().w_full().child(editor.clone()) - } else { - h_flex().h_6().map(|mut this| { - if let Some(folded_ancestors) = self.state.ancestors.get(&entry_id) { - let components = Path::new(&file_name) - .components() - .map(|comp| comp.as_os_str().to_string_lossy().into_owned()) - .collect::>(); - let active_index = folded_ancestors.active_index(); - let components_len = components.len(); - let delimiter = SharedString::new(path_style.primary_separator()); - for (index, component) in components.iter().enumerate() { - if index != 0 { - let delimiter_target_index = index - 1; - let target_entry_id = folded_ancestors.ancestors.get(components_len - 1 - delimiter_target_index).cloned(); - this = this.child( - div() - .when(!is_sticky, |div| { - div - .when(settings.drag_and_drop, |div| 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); - } - })) - .on_drag_move(cx.listener( - move |this, event: &DragMoveEvent, _, _| { - if event.bounds.contains(&event.event.position) { - this.folded_directory_drag_target = Some( - FoldedDirectoryDragTarget { - entry_id, - index: delimiter_target_index, - is_delimiter_target: true, - } - ); - } else { - let is_current_target = this.folded_directory_drag_target - .is_some_and(|target| - target.entry_id == entry_id && - target.index == delimiter_target_index && - target.is_delimiter_target - ); - if is_current_target { - this.folded_directory_drag_target = None; - } - } - - }, - ))) - }) - .child( - Label::new(delimiter.clone()) - .single_line() - .color(filename_text_color) - ) - ); - } - let id = SharedString::from(format!( - "project_panel_path_component_{}_{index}", - entry_id.to_usize() - )); - let label = div() - .id(id) - .px_0p5() - .rounded_xs() - .hover(|style| style.bg(cx.theme().colors().element_active)) - .when(!is_sticky,| div| { - div - .when(index != components_len - 1, |div|{ - let target_entry_id = folded_ancestors.ancestors.get(components_len - 1 - index).cloned(); - div - .when(settings.drag_and_drop, |div| div - .on_drag_move(cx.listener( - move |this, event: &DragMoveEvent, _, _| { - if event.bounds.contains(&event.event.position) { - this.folded_directory_drag_target = Some( - FoldedDirectoryDragTarget { - entry_id, - index, - is_delimiter_target: false, - } - ); - } else { - let is_current_target = this.folded_directory_drag_target - .as_ref() - .is_some_and(|target| - target.entry_id == entry_id && - target.index == index && - !target.is_delimiter_target - ); - if is_current_target { - this.folded_directory_drag_target = None; - } - } - }, - )) - .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); - } - })) - .when(folded_directory_drag_target.is_some_and(|target| - target.entry_id == entry_id && - target.index == index - ), |this| { - this.bg(item_colors.drag_over) - })) - }) - }) - .on_mouse_down( - MouseButton::Left, - cx.listener(move |this, _, _, cx| { - if let Some(folds) = this.state.ancestors.get_mut(&entry_id) { - if folds.set_active_index(index) { - cx.notify(); - } - } - }), - ) - .on_mouse_down( - MouseButton::Right, - cx.listener(move |this, _, _, cx| { - if let Some(folds) = this.state.ancestors.get_mut(&entry_id) { - if folds.set_active_index(index) { - cx.notify(); - } - } - }), - ) - .child( - Label::new(component) - .single_line() - .color(filename_text_color) - .when( - index == active_index - && (is_active || is_marked), - |this| this.underline(), - ), - ); - - this = this.child(label); - } - - this - } else { - this.child( - Label::new(file_name) - .single_line() - .color(filename_text_color), - ) + .child(if show_editor { + h_flex().h_6().w_full().child(self.filename_editor.clone()) + } else { + h_flex() + .h_6() + .map(|this| match self.state.ancestors.get(&entry_id) { + Some(folded_ancestors) => { + this.children(self.render_folder_elements( + folded_ancestors, + entry_id, + file_name, + path_style, + is_sticky, + kind.is_file(), + is_active || is_marked, + settings.drag_and_drop, + item_colors.drag_over, + folded_directory_drag_target, + filename_text_color, + cx, + )) } + + None => this.child( + Label::new(file_name) + .single_line() + .color(filename_text_color) + .into_any_element(), + ), }) - }, - ) + }) .on_secondary_mouse_down(cx.listener( move |this, event: &MouseDownEvent, window, cx| { // Stop propagation to prevent the catch-all context menu for the project @@ -5379,32 +5296,232 @@ impl ProjectPanel { )) .overflow_x(), ) - .when_some( - validation_color_and_message, - |this, (color, message)| { - this - .relative() - .child( - deferred( - div() - .occlude() - .absolute() - .top_full() - .left(px(-1.)) // Used px over rem so that it doesn't change with font size - .right(px(-0.5)) - .py_1() - .px_2() - .border_1() - .border_color(color) - .bg(cx.theme().colors().background) - .child( - Label::new(message) + .when_some(validation_color_and_message, |this, (color, message)| { + this.relative().child(deferred( + div() + .occlude() + .absolute() + .top_full() + .left(px(-1.)) // Used px over rem so that it doesn't change with font size + .right(px(-0.5)) + .py_1() + .px_2() + .border_1() + .border_color(color) + .bg(cx.theme().colors().background) + .child( + Label::new(message) .color(Color::from(color)) - .size(LabelSize::Small) - ) + .size(LabelSize::Small), + ), + )) + }) + } + + fn render_folder_elements( + &self, + folded_ancestors: &FoldedAncestors, + entry_id: ProjectEntryId, + file_name: String, + path_style: PathStyle, + is_sticky: bool, + is_file: bool, + is_active_or_marked: bool, + drag_and_drop_enabled: bool, + drag_over_color: Hsla, + folded_directory_drag_target: Option, + filename_text_color: Color, + cx: &Context, + ) -> impl Iterator { + let components = Path::new(&file_name) + .components() + .map(|comp| comp.as_os_str().to_string_lossy().into_owned()) + .collect::>(); + let active_index = folded_ancestors.active_index(); + let components_len = components.len(); + let delimiter = SharedString::new(path_style.primary_separator()); + + let path_component_elements = + components + .into_iter() + .enumerate() + .map(move |(index, component)| { + div() + .id(SharedString::from(format!( + "project_panel_path_component_{}_{index}", + entry_id.to_usize() + ))) + .px_0p5() + .rounded_xs() + .hover(|style| style.bg(cx.theme().colors().element_active)) + .when(!is_sticky, |div| { + div.when(index != components_len - 1, |div| { + let target_entry_id = folded_ancestors + .ancestors + .get(components_len - 1 - index) + .cloned(); + div.when(drag_and_drop_enabled, |div| { + div.on_drag_move(cx.listener( + move |this, + event: &DragMoveEvent, + _, + _| { + if event.bounds.contains(&event.event.position) { + this.folded_directory_drag_target = + Some(FoldedDirectoryDragTarget { + entry_id, + index, + is_delimiter_target: false, + }); + } else { + let is_current_target = this + .folded_directory_drag_target + .as_ref() + .is_some_and(|target| { + target.entry_id == entry_id + && target.index == index + && !target.is_delimiter_target + }); + if is_current_target { + this.folded_directory_drag_target = None; + } + } + }, + )) + .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, + is_file, + window, + cx, + ); + } + }, + )) + .when( + folded_directory_drag_target.is_some_and(|target| { + target.entry_id == entry_id && target.index == index + }), + |this| this.bg(drag_over_color), + ) + }) + }) + }) + .on_mouse_down( + MouseButton::Left, + cx.listener(move |this, _, _, cx| { + if let Some(folds) = this.state.ancestors.get_mut(&entry_id) { + if folds.set_active_index(index) { + cx.notify(); + } + } + }), ) - ) - } + .on_mouse_down( + MouseButton::Right, + cx.listener(move |this, _, _, cx| { + if let Some(folds) = this.state.ancestors.get_mut(&entry_id) { + if folds.set_active_index(index) { + cx.notify(); + } + } + }), + ) + .child( + Label::new(component) + .single_line() + .color(filename_text_color) + .when(index == active_index && is_active_or_marked, |this| { + this.underline() + }), + ) + .into_any() + }); + + let mut separator_index = 0; + itertools::intersperse_with(path_component_elements, move || { + separator_index += 1; + self.render_entry_path_separator( + entry_id, + separator_index, + components_len, + is_sticky, + is_file, + drag_and_drop_enabled, + filename_text_color, + &delimiter, + folded_ancestors, + cx, + ) + .into_any() + }) + } + + fn render_entry_path_separator( + &self, + entry_id: ProjectEntryId, + index: usize, + components_len: usize, + is_sticky: bool, + is_file: bool, + drag_and_drop_enabled: bool, + filename_text_color: Color, + delimiter: &SharedString, + folded_ancestors: &FoldedAncestors, + cx: &Context, + ) -> Div { + let delimiter_target_index = index - 1; + let target_entry_id = folded_ancestors + .ancestors + .get(components_len - 1 - delimiter_target_index) + .cloned(); + div() + .when(!is_sticky, |div| { + div.when(drag_and_drop_enabled, |div| { + 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, is_file, window, cx); + } + }, + )) + .on_drag_move(cx.listener( + move |this, event: &DragMoveEvent, _, _| { + if event.bounds.contains(&event.event.position) { + this.folded_directory_drag_target = + Some(FoldedDirectoryDragTarget { + entry_id, + index: delimiter_target_index, + is_delimiter_target: true, + }); + } else { + let is_current_target = + this.folded_directory_drag_target.is_some_and(|target| { + target.entry_id == entry_id + && target.index == delimiter_target_index + && target.is_delimiter_target + }); + if is_current_target { + this.folded_directory_drag_target = None; + } + } + }, + )) + }) + }) + .child( + Label::new(delimiter.clone()) + .single_line() + .color(filename_text_color), ) }