Implement terminal pane drag and drop overrides

Kirill Bulatov created

Change summary

crates/terminal_view/src/terminal_element.rs | 32 +--------
crates/terminal_view/src/terminal_panel.rs   | 74 ++++++++++++++++++---
crates/workspace/src/pane.rs                 | 40 ++++++++--
crates/workspace/src/workspace.rs            | 16 +--
4 files changed, 103 insertions(+), 59 deletions(-)

Detailed changes

crates/terminal_view/src/terminal_element.rs 🔗

@@ -1,12 +1,12 @@
 use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
 use gpui::{
     div, fill, point, px, red, relative, AnyElement, AsyncWindowContext, AvailableSpace,
-    BorrowWindow, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font,
-    FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveBounds, InteractiveElement,
+    BorrowWindow, Bounds, DispatchPhase, Element, ElementId, FocusHandle, Font, FontStyle,
+    FontWeight, HighlightStyle, Hsla, InteractiveBounds, InteractiveElement,
     InteractiveElementState, Interactivity, IntoElement, LayoutId, Model, ModelContext,
     ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, PlatformInputHandler, Point,
-    ShapedLine, StatefulInteractiveElement, StyleRefinement, Styled, TextRun, TextStyle,
-    TextSystem, UnderlineStyle, WhiteSpace, WindowContext,
+    ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle,
+    WhiteSpace, WindowContext,
 };
 use itertools::Itertools;
 use language::CursorShape;
@@ -25,7 +25,7 @@ use terminal::{
 use theme::{ActiveTheme, Theme, ThemeSettings};
 use ui::Tooltip;
 
-use std::{any::TypeId, mem};
+use std::mem;
 use std::{fmt::Debug, ops::RangeInclusive};
 
 ///The information generated during layout that is necessary for painting
@@ -677,28 +677,6 @@ impl TerminalElement {
             }
         });
 
-        self.interactivity.drag_over_styles.push((
-            TypeId::of::<ExternalPaths>(),
-            StyleRefinement::default().bg(cx.theme().colors().drop_target_background),
-        ));
-        self.interactivity.on_drop::<ExternalPaths>({
-            let focus = focus.clone();
-            let terminal = terminal.clone();
-            move |external_paths, cx| {
-                cx.focus(&focus);
-                let mut new_text = external_paths
-                    .paths()
-                    .iter()
-                    .map(|path| format!(" {path:?}"))
-                    .join("");
-                new_text.push(' ');
-                terminal.update(cx, |terminal, _| {
-                    terminal.paste(&new_text);
-                });
-                cx.stop_propagation();
-            }
-        });
-
         // Mouse mode handlers:
         // All mouse modes need the extra click handlers
         if mode.intersects(TermMode::MOUSE_MODE) {

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -1,4 +1,4 @@
-use std::{path::PathBuf, sync::Arc};
+use std::{ops::ControlFlow, path::PathBuf, sync::Arc};
 
 use crate::TerminalView;
 use db::kvp::KEY_VALUE_STORE;
@@ -7,7 +7,8 @@ use gpui::{
     FocusHandle, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, Subscription,
     Task, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
-use project::Fs;
+use itertools::Itertools;
+use project::{Fs, ProjectEntryId};
 use search::{buffer_search::DivRegistrar, BufferSearchBar};
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
@@ -19,7 +20,7 @@ use workspace::{
     item::Item,
     pane,
     ui::Icon,
-    Pane, Workspace,
+    DraggedTab, Pane, Workspace,
 };
 
 use anyhow::Result;
@@ -59,15 +60,7 @@ impl TerminalPanel {
                 workspace.weak_handle(),
                 workspace.project().clone(),
                 Default::default(),
-                Some(Arc::new(|a, cx| {
-                    if let Some(tab) = a.downcast_ref::<workspace::pane::DraggedTab>() {
-                        if let Some(item) = tab.pane.read(cx).item_for_index(tab.ix) {
-                            return item.downcast::<TerminalView>().is_some();
-                        }
-                    }
-
-                    a.downcast_ref::<ExternalPaths>().is_some()
-                })),
+                None,
                 cx,
             );
             pane.set_can_split(false, cx);
@@ -102,6 +95,47 @@ impl TerminalPanel {
                     })
                     .into_any_element()
             });
+
+            let workspace = workspace.weak_handle();
+            pane.set_custom_drop_handle(cx, move |pane, dropped_item, cx| {
+                if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>() {
+                    if let Some(item) = tab.pane.read(cx).item_for_index(tab.ix) {
+                        if item.downcast::<TerminalView>().is_some() {
+                            return ControlFlow::Continue(());
+                        } else if let Some(project_path) = item.project_path(cx) {
+                            if let Some(entry_path) = workspace
+                                .update(cx, |workspace, cx| {
+                                    workspace
+                                        .project()
+                                        .read(cx)
+                                        .absolute_path(&project_path, cx)
+                                })
+                                .log_err()
+                                .flatten()
+                            {
+                                add_paths_to_terminal(pane, &[entry_path], cx);
+                            }
+                        }
+                    }
+                } else if let Some(&entry_id) = dropped_item.downcast_ref::<ProjectEntryId>() {
+                    if let Some(entry_path) = workspace
+                        .update(cx, |workspace, cx| {
+                            let project = workspace.project().read(cx);
+                            project
+                                .path_for_entry(entry_id, cx)
+                                .and_then(|project_path| project.absolute_path(&project_path, cx))
+                        })
+                        .log_err()
+                        .flatten()
+                    {
+                        add_paths_to_terminal(pane, &[entry_path], cx);
+                    }
+                } else if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {
+                    add_paths_to_terminal(pane, paths.paths(), cx);
+                }
+
+                ControlFlow::Break(())
+            });
             let buffer_search_bar = cx.new_view(search::BufferSearchBar::new);
             pane.toolbar()
                 .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx));
@@ -326,6 +360,22 @@ impl TerminalPanel {
     }
 }
 
+fn add_paths_to_terminal(pane: &mut Pane, paths: &[PathBuf], cx: &mut ViewContext<'_, Pane>) {
+    if let Some(terminal_view) = pane
+        .active_item()
+        .and_then(|item| item.downcast::<TerminalView>())
+    {
+        cx.focus_view(&terminal_view);
+        let mut new_text = paths.iter().map(|path| format!(" {path:?}")).join("");
+        new_text.push(' ');
+        terminal_view.update(cx, |terminal_view, cx| {
+            terminal_view.terminal().update(cx, |terminal, _| {
+                terminal.paste(&new_text);
+            });
+        });
+    }
+}
+
 impl EventEmitter<PanelEvent> for TerminalPanel {}
 
 impl Render for TerminalPanel {

crates/workspace/src/pane.rs 🔗

@@ -20,6 +20,7 @@ use settings::Settings;
 use std::{
     any::Any,
     cmp, fmt, mem,
+    ops::ControlFlow,
     path::{Path, PathBuf},
     rc::Rc,
     sync::{
@@ -183,6 +184,8 @@ pub struct Pane {
     project: Model<Project>,
     drag_split_direction: Option<SplitDirection>,
     can_drop_predicate: Option<Arc<dyn Fn(&dyn Any, &mut WindowContext) -> bool>>,
+    custom_drop_handle:
+        Option<Arc<dyn Fn(&mut Pane, &dyn Any, &mut ViewContext<Pane>) -> ControlFlow<(), ()>>>,
     can_split: bool,
     render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement>,
     _subscriptions: Vec<Subscription>,
@@ -375,6 +378,7 @@ impl Pane {
             workspace,
             project,
             can_drop_predicate,
+            custom_drop_handle: None,
             can_split: true,
             render_tab_bar_buttons: Rc::new(move |pane, cx| {
                 h_stack()
@@ -501,13 +505,6 @@ impl Pane {
         self.active_item_index
     }
 
-    //     pub fn on_can_drop<F>(&mut self, can_drop: F)
-    //     where
-    //         F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
-    //     {
-    //         self.can_drop = Rc::new(can_drop);
-    //     }
-
     pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext<Self>) {
         self.can_split = can_split;
         cx.notify();
@@ -528,6 +525,14 @@ impl Pane {
         cx.notify();
     }
 
+    pub fn set_custom_drop_handle<F>(&mut self, cx: &mut ViewContext<Self>, handle: F)
+    where
+        F: 'static + Fn(&mut Pane, &dyn Any, &mut ViewContext<Pane>) -> ControlFlow<(), ()>,
+    {
+        self.custom_drop_handle = Some(Arc::new(handle));
+        cx.notify();
+    }
+
     pub fn nav_history_for_item<T: Item>(&self, item: &View<T>) -> ItemNavHistory {
         ItemNavHistory {
             history: self.nav_history.clone(),
@@ -1818,8 +1823,13 @@ impl Pane {
         &mut self,
         dragged_tab: &DraggedTab,
         ix: usize,
-        cx: &mut ViewContext<'_, Pane>,
+        cx: &mut ViewContext<'_, Self>,
     ) {
+        if let Some(custom_drop_handle) = self.custom_drop_handle.clone() {
+            if let ControlFlow::Break(()) = custom_drop_handle(self, dragged_tab, cx) {
+                return;
+            }
+        }
         let mut to_pane = cx.view().clone();
         let split_direction = self.drag_split_direction;
         let item_id = dragged_tab.item_id;
@@ -1839,8 +1849,13 @@ impl Pane {
     fn handle_project_entry_drop(
         &mut self,
         project_entry_id: &ProjectEntryId,
-        cx: &mut ViewContext<'_, Pane>,
+        cx: &mut ViewContext<'_, Self>,
     ) {
+        if let Some(custom_drop_handle) = self.custom_drop_handle.clone() {
+            if let ControlFlow::Break(()) = custom_drop_handle(self, project_entry_id, cx) {
+                return;
+            }
+        }
         let mut to_pane = cx.view().clone();
         let split_direction = self.drag_split_direction;
         let project_entry_id = *project_entry_id;
@@ -1867,8 +1882,13 @@ impl Pane {
     fn handle_external_paths_drop(
         &mut self,
         paths: &ExternalPaths,
-        cx: &mut ViewContext<'_, Pane>,
+        cx: &mut ViewContext<'_, Self>,
     ) {
+        if let Some(custom_drop_handle) = self.custom_drop_handle.clone() {
+            if let ControlFlow::Break(()) = custom_drop_handle(self, paths, cx) {
+                return;
+            }
+        }
         let mut to_pane = cx.view().clone();
         let split_direction = self.drag_split_direction;
         let paths = paths.paths().to_vec();

crates/workspace/src/workspace.rs 🔗

@@ -27,11 +27,11 @@ use futures::{
 use gpui::{
     actions, canvas, div, impl_actions, point, size, Action, AnyElement, AnyModel, AnyView,
     AnyWeakView, AnyWindowHandle, AppContext, AsyncAppContext, AsyncWindowContext, BorrowWindow,
-    Bounds, Context, Div, DragMoveEvent, Element, Entity, EntityId, EventEmitter, ExternalPaths,
-    FocusHandle, FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext,
-    LayoutId, ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point,
-    PromptLevel, Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext,
-    WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
+    Bounds, Context, Div, DragMoveEvent, Element, Entity, EntityId, EventEmitter, FocusHandle,
+    FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId,
+    ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel,
+    Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
+    WindowBounds, WindowContext, WindowHandle, WindowOptions,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;
@@ -544,11 +544,7 @@ impl Workspace {
                 weak_handle.clone(),
                 project.clone(),
                 pane_history_timestamp.clone(),
-                Some(Arc::new(|a, _| {
-                    a.downcast_ref::<ExternalPaths>().is_some()
-                        || a.downcast_ref::<DraggedTab>().is_some()
-                        || a.downcast_ref::<ProjectEntryId>().is_some()
-                })),
+                None,
                 cx,
             )
         });