From 8ff05c6a7227544cd36c208da8be1996e04c41dd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 7 Jan 2024 01:17:49 +0200 Subject: [PATCH 1/6] Prepare for external file drop in pane --- crates/gpui/src/app.rs | 6 --- crates/gpui/src/interactive.rs | 2 +- crates/gpui/src/platform/mac/window.rs | 5 +- crates/gpui/src/window.rs | 6 +-- crates/terminal_view/src/terminal_element.rs | 2 +- crates/terminal_view/src/terminal_panel.rs | 5 +- crates/workspace/src/pane.rs | 49 ++++++++++++++++++-- crates/workspace/src/workspace.rs | 16 ++++--- 8 files changed, 63 insertions(+), 28 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 4ad9540043e1058ee9ab9f5e9df9ef9bbce92057..2a0ff545e92ee6ea43a580a3a5c39e648b6c947f 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1099,12 +1099,6 @@ impl AppContext { pub fn has_active_drag(&self) -> bool { self.active_drag.is_some() } - - pub fn active_drag(&self) -> Option<&T> { - self.active_drag - .as_ref() - .and_then(|drag| drag.value.downcast_ref()) - } } impl Context for AppContext { diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index 0be917350df812ce07de2e95d1dac52cd59637ad..6f396d31aa481571d3331e816f30dbf788d47816 100644 --- a/crates/gpui/src/interactive.rs +++ b/crates/gpui/src/interactive.rs @@ -214,7 +214,7 @@ impl Render for ExternalPaths { pub enum FileDropEvent { Entered { position: Point, - files: ExternalPaths, + paths: ExternalPaths, }, Pending { position: Point, diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 12ffc36afc08f5da22d244b7a3c14bebfb527acd..2beac528c18f53cfa9a39b008dbebf3825502b30 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1673,10 +1673,7 @@ extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDr if send_new_event(&window_state, { let position = drag_event_position(&window_state, dragging_info); let paths = external_paths_from_event(dragging_info); - InputEvent::FileDrop(FileDropEvent::Entered { - position, - files: paths, - }) + InputEvent::FileDrop(FileDropEvent::Entered { position, paths }) }) { NSDragOperationCopy } else { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 71e6cb9e559a97634ee7d2df8610569838c32a5d..a0d3d0f886bbb7aeed136196740272c2da3e4f43 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1462,12 +1462,12 @@ impl<'a> WindowContext<'a> { // Translate dragging and dropping of external files from the operating system // to internal drag and drop events. InputEvent::FileDrop(file_drop) => match file_drop { - FileDropEvent::Entered { position, files } => { + FileDropEvent::Entered { position, paths } => { self.window.mouse_position = position; if self.active_drag.is_none() { self.active_drag = Some(AnyDrag { - value: Box::new(files.clone()), - view: self.new_view(|_| files).into(), + value: Box::new(paths.clone()), + view: self.new_view(|_| paths).into(), cursor_offset: position, }); } diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index ffdca7d8135d2702a3cbc9b4d42070e8ec4e41a4..4eb26bf50745dece3bb8e7c27b3b15bfbab8d624 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -693,9 +693,9 @@ impl TerminalElement { .join(""); new_text.push(' '); terminal.update(cx, |terminal, _| { - // todo!() long paths are not displayed properly albeit the text is there terminal.paste(&new_text); }); + cx.stop_propagation(); } }); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 5e3e4c3c23d27ab3f59f31b759573c9d1c2203d3..86ac4a9818e688edcd2838c9dc2d5526485686c8 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -65,11 +65,8 @@ impl TerminalPanel { return item.downcast::().is_some(); } } - if a.downcast_ref::().is_some() { - return true; - } - false + a.downcast_ref::().is_some() })), cx, ); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 21c5962bebb92904935b2a2c2207cda622e29dda..1798112fcc1f28655225d86fb349bb0446463170 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -8,9 +8,10 @@ use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use gpui::{ actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AnyElement, AppContext, - AsyncWindowContext, DismissEvent, Div, DragMoveEvent, EntityId, EventEmitter, FocusHandle, - FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, Render, - ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext, + AsyncWindowContext, DismissEvent, Div, DragMoveEvent, EntityId, EventEmitter, ExternalPaths, + FocusHandle, FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, + PromptLevel, Render, ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, + WeakView, WindowContext, }; use parking_lot::Mutex; use project::{Project, ProjectEntryId, ProjectPath}; @@ -1555,6 +1556,10 @@ impl Pane { this.drag_split_direction = None; this.handle_project_entry_drop(entry_id, cx) })) + .on_drop(cx.listener(move |this, paths, cx| { + this.drag_split_direction = None; + this.handle_external_paths_drop(paths, cx) + })) .when_some(item.tab_tooltip_text(cx), |tab, text| { tab.tooltip(move |cx| Tooltip::text(text.clone(), cx)) }) @@ -1721,6 +1726,10 @@ impl Pane { .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { this.drag_split_direction = None; this.handle_project_entry_drop(entry_id, cx) + })) + .on_drop(cx.listener(move |this, paths, cx| { + this.drag_split_direction = None; + this.handle_external_paths_drop(paths, cx) })), ) } @@ -1855,6 +1864,35 @@ impl Pane { .log_err(); } + fn handle_external_paths_drop( + &mut self, + paths: &ExternalPaths, + cx: &mut ViewContext<'_, Pane>, + ) { + // let mut to_pane = cx.view().clone(); + // let split_direction = self.drag_split_direction; + // let project_entry_id = *project_entry_id; + // self.workspace + // .update(cx, |_, cx| { + // cx.defer(move |workspace, cx| { + // if let Some(path) = workspace + // .project() + // .read(cx) + // .path_for_entry(project_entry_id, cx) + // { + // if let Some(split_direction) = split_direction { + // to_pane = workspace.split_pane(to_pane, split_direction, cx); + // } + // workspace + // .open_path(path, Some(to_pane.downgrade()), true, cx) + // .detach_and_log_err(cx); + // } + // }); + // }) + // .log_err(); + dbg!("@@@@@@@@@@@@@@", paths); + } + pub fn display_nav_history_buttons(&mut self, display: bool) { self.display_nav_history_buttons = display; } @@ -1956,6 +1994,7 @@ impl Render for Pane { .group("") .on_drag_move::(cx.listener(Self::handle_drag_move)) .on_drag_move::(cx.listener(Self::handle_drag_move)) + .on_drag_move::(cx.listener(Self::handle_drag_move)) .map(|div| { if let Some(item) = self.active_item() { div.v_flex() @@ -1985,6 +2024,7 @@ impl Render for Pane { )) .group_drag_over::("", |style| style.visible()) .group_drag_over::("", |style| style.visible()) + .group_drag_over::("", |style| style.visible()) .when_some(self.can_drop_predicate.clone(), |this, p| { this.can_drop(move |a, cx| p(a, cx)) }) @@ -1994,6 +2034,9 @@ impl Render for Pane { .on_drop(cx.listener(move |this, entry_id, cx| { this.handle_project_entry_drop(entry_id, cx) })) + .on_drop(cx.listener(move |this, paths, cx| { + this.handle_external_paths_drop(paths, cx) + })) .map(|div| match self.drag_split_direction { None => div.top_0().left_0().right_0().bottom_0(), Some(SplitDirection::Up) => div.top_0().left_0().right_0().h_32(), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6b29496f2cfd919a60e4947adb2f989fbb425486..78595d2c3126845cc06d5c14cc5e51084334d65e 100644 --- a/crates/workspace/src/workspace.rs +++ b/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, 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, 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, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; @@ -544,7 +544,11 @@ impl Workspace { weak_handle.clone(), project.clone(), pane_history_timestamp.clone(), - None, + Some(Arc::new(|a, _| { + a.downcast_ref::().is_some() + || a.downcast_ref::().is_some() + || a.downcast_ref::().is_some() + })), cx, ) }); From c4e306162cdb00707ba2a43091c89635b4f96544 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 7 Jan 2024 01:18:02 +0200 Subject: [PATCH 2/6] Implement external file drop in pane --- crates/journal/src/journal.rs | 2 +- crates/terminal_view/src/terminal_view.rs | 2 +- crates/workspace/src/pane.rs | 37 +++++++++-------------- crates/workspace/src/workspace.rs | 12 +++++--- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index a1236297ed1e3f0af70ddc557994087304493c7c..a1620664c6342fcb45df78e0d8ff8487272b2b1c 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -100,7 +100,7 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut WindowContext) { let opened = workspace .update(&mut cx, |workspace, cx| { - workspace.open_paths(vec![entry_path], true, cx) + workspace.open_paths(vec![entry_path], true, None, cx) })? .await; diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index d519523974f7a63ee210cd4cb4e2c1c06f2da8db..d788a1b62771c29560afa782a91d3f741a50a4ce 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -197,7 +197,7 @@ impl TerminalView { cx.spawn(|_, mut cx| async move { let opened_items = task_workspace .update(&mut cx, |workspace, cx| { - workspace.open_paths(vec![path.path_like], is_dir, cx) + workspace.open_paths(vec![path.path_like], is_dir, None, cx) }) .context("workspace update")? .await; diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 1798112fcc1f28655225d86fb349bb0446463170..fe41d0f8b2b38c673ddc6a4790f845ad65bd8b1b 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1869,28 +1869,21 @@ impl Pane { paths: &ExternalPaths, cx: &mut ViewContext<'_, Pane>, ) { - // let mut to_pane = cx.view().clone(); - // let split_direction = self.drag_split_direction; - // let project_entry_id = *project_entry_id; - // self.workspace - // .update(cx, |_, cx| { - // cx.defer(move |workspace, cx| { - // if let Some(path) = workspace - // .project() - // .read(cx) - // .path_for_entry(project_entry_id, cx) - // { - // if let Some(split_direction) = split_direction { - // to_pane = workspace.split_pane(to_pane, split_direction, cx); - // } - // workspace - // .open_path(path, Some(to_pane.downgrade()), true, cx) - // .detach_and_log_err(cx); - // } - // }); - // }) - // .log_err(); - dbg!("@@@@@@@@@@@@@@", paths); + let mut to_pane = cx.view().clone(); + let split_direction = self.drag_split_direction; + let paths = paths.paths().to_vec(); + self.workspace + .update(cx, |_, cx| { + cx.defer(move |workspace, cx| { + if let Some(split_direction) = split_direction { + to_pane = workspace.split_pane(to_pane, split_direction, cx); + } + workspace + .open_paths(paths, true, Some(to_pane.downgrade()), cx) + .detach(); + }); + }) + .log_err(); } pub fn display_nav_history_buttons(&mut self, display: bool) { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 78595d2c3126845cc06d5c14cc5e51084334d65e..f23dfca85754be24fd8792b3975a927b0ee4d331 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1322,6 +1322,7 @@ impl Workspace { &mut self, mut abs_paths: Vec, visible: bool, + pane: Option>, cx: &mut ViewContext, ) -> Task, anyhow::Error>>>> { log::info!("open paths {abs_paths:?}"); @@ -1351,12 +1352,13 @@ impl Workspace { let this = this.clone(); let abs_path = abs_path.clone(); let fs = fs.clone(); + let pane = pane.clone(); let task = cx.spawn(move |mut cx| async move { let (worktree, project_path) = project_path?; if fs.is_file(&abs_path).await { Some( this.update(&mut cx, |this, cx| { - this.open_path(project_path, None, true, cx) + this.open_path(project_path, pane, true, cx) }) .log_err()? .await, @@ -1402,7 +1404,7 @@ impl Workspace { cx.spawn(|this, mut cx| async move { if let Some(paths) = paths.await.log_err().flatten() { let results = this - .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))? + .update(&mut cx, |this, cx| this.open_paths(paths, true, None, cx))? .await; for result in results.into_iter().flatten() { result.log_err(); @@ -1788,7 +1790,7 @@ impl Workspace { cx.spawn(|workspace, mut cx| async move { let open_paths_task_result = workspace .update(&mut cx, |workspace, cx| { - workspace.open_paths(vec![abs_path.clone()], visible, cx) + workspace.open_paths(vec![abs_path.clone()], visible, None, cx) }) .with_context(|| format!("open abs path {abs_path:?} task spawn"))? .await; @@ -4087,7 +4089,7 @@ pub fn open_paths( existing.clone(), existing .update(&mut cx, |workspace, cx| { - workspace.open_paths(abs_paths, true, cx) + workspace.open_paths(abs_paths, true, None, cx) })? .await, )) @@ -4135,7 +4137,7 @@ pub fn create_and_open_local_file( let mut items = workspace .update(&mut cx, |workspace, cx| { workspace.with_local_workspace(cx, |workspace, cx| { - workspace.open_paths(vec![path.to_path_buf()], false, cx) + workspace.open_paths(vec![path.to_path_buf()], false, None, cx) }) })? .await? From 518868a12f72caa7d4be54ff16cd5e6b5604dff0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 7 Jan 2024 02:21:43 +0200 Subject: [PATCH 3/6] Implement terminal pane drag and drop overrides --- 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(-) diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 4eb26bf50745dece3bb8e7c27b3b15bfbab8d624..d936716032a53b432d2f6f1a5dc6b79069656c8b 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/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::(), - StyleRefinement::default().bg(cx.theme().colors().drop_target_background), - )); - self.interactivity.on_drop::({ - 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) { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 86ac4a9818e688edcd2838c9dc2d5526485686c8..c19d0bfc6cbbf9e34548e5e2d22fa029cd0ce7d0 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/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::() { - if let Some(item) = tab.pane.read(cx).item_for_index(tab.ix) { - return item.downcast::().is_some(); - } - } - - a.downcast_ref::().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::() { + if let Some(item) = tab.pane.read(cx).item_for_index(tab.ix) { + if item.downcast::().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::() { + 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::() { + 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::()) + { + 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 for TerminalPanel {} impl Render for TerminalPanel { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index fe41d0f8b2b38c673ddc6a4790f845ad65bd8b1b..985311b18a847be2fe1f3eff5763ea981f553382 100644 --- a/crates/workspace/src/pane.rs +++ b/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, drag_split_direction: Option, can_drop_predicate: Option bool>>, + custom_drop_handle: + Option) -> ControlFlow<(), ()>>>, can_split: bool, render_tab_bar_buttons: Rc) -> AnyElement>, _subscriptions: Vec, @@ -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(&mut self, can_drop: F) - // where - // F: 'static + Fn(&DragAndDrop, &WindowContext) -> bool, - // { - // self.can_drop = Rc::new(can_drop); - // } - pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext) { self.can_split = can_split; cx.notify(); @@ -528,6 +525,14 @@ impl Pane { cx.notify(); } + pub fn set_custom_drop_handle(&mut self, cx: &mut ViewContext, handle: F) + where + F: 'static + Fn(&mut Pane, &dyn Any, &mut ViewContext) -> ControlFlow<(), ()>, + { + self.custom_drop_handle = Some(Arc::new(handle)); + cx.notify(); + } + pub fn nav_history_for_item(&self, item: &View) -> 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(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f23dfca85754be24fd8792b3975a927b0ee4d331..8e66f06b4ac510be07860d89e174c44b85822b29 100644 --- a/crates/workspace/src/workspace.rs +++ b/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::().is_some() - || a.downcast_ref::().is_some() - || a.downcast_ref::().is_some() - })), + None, cx, ) }); From c499e1ed3842b0e635d0c9da47deaf7e59a01680 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 7 Jan 2024 02:32:15 +0200 Subject: [PATCH 4/6] Fix panic during terminal tab drag and drop --- crates/terminal_view/src/terminal_panel.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index c19d0bfc6cbbf9e34548e5e2d22fa029cd0ce7d0..c0f8e6209b208cb8125b82a8626b85fac76cbac1 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -99,7 +99,12 @@ impl TerminalPanel { let workspace = workspace.weak_handle(); pane.set_custom_drop_handle(cx, move |pane, dropped_item, cx| { if let Some(tab) = dropped_item.downcast_ref::() { - if let Some(item) = tab.pane.read(cx).item_for_index(tab.ix) { + let item = if &tab.pane == cx.view() { + pane.item_for_index(tab.ix) + } else { + tab.pane.read(cx).item_for_index(tab.ix) + }; + if let Some(item) = item { if item.downcast::().is_some() { return ControlFlow::Continue(()); } else if let Some(project_path) = item.project_path(cx) { From 4f88a50aad07f4ec59996878c4a3a92008fd90d6 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 7 Jan 2024 02:59:56 +0200 Subject: [PATCH 5/6] On external file drop, add visible project entries for directories only --- crates/journal/src/journal.rs | 4 +- crates/terminal_view/src/terminal_view.rs | 11 +++- crates/workspace/src/pane.rs | 9 ++- crates/workspace/src/workspace.rs | 76 ++++++++++++++++++----- 4 files changed, 77 insertions(+), 23 deletions(-) diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index a1620664c6342fcb45df78e0d8ff8487272b2b1c..2ae74e7f5d5d60c9485c22235633b1a23f952605 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -11,7 +11,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use workspace::{AppState, Workspace}; +use workspace::{AppState, OpenVisible, Workspace}; actions!(journal, [NewJournalEntry]); @@ -100,7 +100,7 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut WindowContext) { let opened = workspace .update(&mut cx, |workspace, cx| { - workspace.open_paths(vec![entry_path], true, None, cx) + workspace.open_paths(vec![entry_path], OpenVisible::All, None, cx) })? .await; diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index d788a1b62771c29560afa782a91d3f741a50a4ce..7c07d55585d25a770d1ada9147fe6249bff9dd58 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -29,7 +29,8 @@ use workspace::{ notifications::NotifyResultExt, register_deserializable_item, searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle}, - CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId, + CloseActiveItem, NewCenterTerminal, OpenVisible, Pane, ToolbarItemLocation, Workspace, + WorkspaceId, }; use anyhow::Context; @@ -192,12 +193,18 @@ impl TerminalView { } let potential_abs_paths = possible_open_targets(&workspace, maybe_path, cx); if let Some(path) = potential_abs_paths.into_iter().next() { + // TODO kb wrong lib call let is_dir = path.path_like.is_dir(); let task_workspace = workspace.clone(); cx.spawn(|_, mut cx| async move { let opened_items = task_workspace .update(&mut cx, |workspace, cx| { - workspace.open_paths(vec![path.path_like], is_dir, None, cx) + workspace.open_paths( + vec![path.path_like], + OpenVisible::OnlyDirectories, + None, + cx, + ) }) .context("workspace update")? .await; diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 985311b18a847be2fe1f3eff5763ea981f553382..04a51fc655be0d7b5d2b890479bef484b5cbc14a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,7 +2,7 @@ use crate::{ item::{ClosePosition, Item, ItemHandle, ItemSettings, WeakItemHandle}, toolbar::Toolbar, workspace_settings::{AutosaveSetting, WorkspaceSettings}, - NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace, + NewCenterTerminal, NewFile, NewSearch, OpenVisible, SplitDirection, ToggleZoom, Workspace, }; use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; @@ -1899,7 +1899,12 @@ impl Pane { to_pane = workspace.split_pane(to_pane, split_direction, cx); } workspace - .open_paths(paths, true, Some(to_pane.downgrade()), cx) + .open_paths( + paths, + OpenVisible::OnlyDirectories, + Some(to_pane.downgrade()), + cx, + ) .detach(); }); }) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8e66f06b4ac510be07860d89e174c44b85822b29..4ad184ee79c9f4780fb6e33cc6f41c951d846351 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -431,6 +431,13 @@ pub enum Event { WorkspaceCreated(WeakView), } +pub enum OpenVisible { + All, + None, + OnlyFiles, + OnlyDirectories, +} + pub struct Workspace { weak_self: WeakView, workspace_actions: Vec) -> Div>>, @@ -1317,7 +1324,7 @@ impl Workspace { pub fn open_paths( &mut self, mut abs_paths: Vec, - visible: bool, + visible: OpenVisible, pane: Option>, cx: &mut ViewContext, ) -> Task, anyhow::Error>>>> { @@ -1329,19 +1336,43 @@ impl Workspace { abs_paths.sort_unstable(); cx.spawn(move |this, mut cx| async move { let mut tasks = Vec::with_capacity(abs_paths.len()); + for abs_path in &abs_paths { - let project_path = match this - .update(&mut cx, |this, cx| { - Workspace::project_path_for_path( - this.project.clone(), - abs_path, - visible, - cx, - ) - }) - .log_err() - { - Some(project_path) => project_path.await.log_err(), + let visible = match visible { + OpenVisible::All => Some(true), + OpenVisible::None => Some(false), + OpenVisible::OnlyFiles => match fs.metadata(abs_path).await.log_err() { + Some(Some(metadata)) => Some(!metadata.is_dir), + Some(None) => { + log::error!("No metadata for file {abs_path:?}"); + None + } + None => None, + }, + OpenVisible::OnlyDirectories => match fs.metadata(abs_path).await.log_err() { + Some(Some(metadata)) => Some(metadata.is_dir), + Some(None) => { + log::error!("No metadata for file {abs_path:?}"); + None + } + None => None, + }, + }; + let project_path = match visible { + Some(visible) => match this + .update(&mut cx, |this, cx| { + Workspace::project_path_for_path( + this.project.clone(), + abs_path, + visible, + cx, + ) + }) + .log_err() + { + Some(project_path) => project_path.await.log_err(), + None => None, + }, None => None, }; @@ -1400,7 +1431,9 @@ impl Workspace { cx.spawn(|this, mut cx| async move { if let Some(paths) = paths.await.log_err().flatten() { let results = this - .update(&mut cx, |this, cx| this.open_paths(paths, true, None, cx))? + .update(&mut cx, |this, cx| { + this.open_paths(paths, OpenVisible::All, None, cx) + })? .await; for result in results.into_iter().flatten() { result.log_err(); @@ -1786,7 +1819,16 @@ impl Workspace { cx.spawn(|workspace, mut cx| async move { let open_paths_task_result = workspace .update(&mut cx, |workspace, cx| { - workspace.open_paths(vec![abs_path.clone()], visible, None, cx) + workspace.open_paths( + vec![abs_path.clone()], + if visible { + OpenVisible::All + } else { + OpenVisible::None + }, + None, + cx, + ) }) .with_context(|| format!("open abs path {abs_path:?} task spawn"))? .await; @@ -4085,7 +4127,7 @@ pub fn open_paths( existing.clone(), existing .update(&mut cx, |workspace, cx| { - workspace.open_paths(abs_paths, true, None, cx) + workspace.open_paths(abs_paths, OpenVisible::All, None, cx) })? .await, )) @@ -4133,7 +4175,7 @@ pub fn create_and_open_local_file( let mut items = workspace .update(&mut cx, |workspace, cx| { workspace.with_local_workspace(cx, |workspace, cx| { - workspace.open_paths(vec![path.to_path_buf()], false, None, cx) + workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx) }) })? .await? From df937eaeebea2f5472dd91750f1582be7c586a57 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 7 Jan 2024 13:32:09 +0200 Subject: [PATCH 6/6] Use fs to determine if file path is a dir --- crates/terminal_view/src/terminal_view.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 7c07d55585d25a770d1ada9147fe6249bff9dd58..5372b90bee921565b8460cfe5af2f81d9fb65d4d 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -193,10 +193,18 @@ impl TerminalView { } let potential_abs_paths = possible_open_targets(&workspace, maybe_path, cx); if let Some(path) = potential_abs_paths.into_iter().next() { - // TODO kb wrong lib call - let is_dir = path.path_like.is_dir(); let task_workspace = workspace.clone(); cx.spawn(|_, mut cx| async move { + let fs = task_workspace.update(&mut cx, |workspace, cx| { + workspace.project().read(cx).fs().clone() + })?; + let is_dir = fs + .metadata(&path.path_like) + .await? + .with_context(|| { + format!("Missing metadata for file {:?}", path.path_like) + })? + .is_dir; let opened_items = task_workspace .update(&mut cx, |workspace, cx| { workspace.open_paths(