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/journal/src/journal.rs b/crates/journal/src/journal.rs index a1236297ed1e3f0af70ddc557994087304493c7c..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, cx) + workspace.open_paths(vec![entry_path], OpenVisible::All, None, cx) })? .await; diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index ffdca7d8135d2702a3cbc9b4d42070e8ec4e41a4..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, _| { - // todo!() long paths are not displayed properly albeit the text is there - terminal.paste(&new_text); - }); - } - }); - // 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 5e3e4c3c23d27ab3f59f31b759573c9d1c2203d3..c0f8e6209b208cb8125b82a8626b85fac76cbac1 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,18 +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(); - } - } - if a.downcast_ref::().is_some() { - return true; - } - - false - })), + None, cx, ); pane.set_can_split(false, cx); @@ -105,6 +95,52 @@ 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::() { + 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) { + 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)); @@ -329,6 +365,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/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index d519523974f7a63ee210cd4cb4e2c1c06f2da8db..5372b90bee921565b8460cfe5af2f81d9fb65d4d 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,26 @@ impl TerminalView { } let potential_abs_paths = possible_open_targets(&workspace, maybe_path, cx); if let Some(path) = potential_abs_paths.into_iter().next() { - 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(vec![path.path_like], is_dir, 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 21c5962bebb92904935b2a2c2207cda622e29dda..04a51fc655be0d7b5d2b890479bef484b5cbc14a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,15 +2,16 @@ 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}; 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}; @@ -19,6 +20,7 @@ use settings::Settings; use std::{ any::Any, cmp, fmt, mem, + ops::ControlFlow, path::{Path, PathBuf}, rc::Rc, sync::{ @@ -182,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, @@ -374,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() @@ -500,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(); @@ -527,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(), @@ -1555,6 +1561,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 +1731,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) })), ) } @@ -1809,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; @@ -1830,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; @@ -1855,6 +1879,38 @@ impl Pane { .log_err(); } + fn handle_external_paths_drop( + &mut self, + paths: &ExternalPaths, + 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(); + 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, + OpenVisible::OnlyDirectories, + Some(to_pane.downgrade()), + cx, + ) + .detach(); + }); + }) + .log_err(); + } + pub fn display_nav_history_buttons(&mut self, display: bool) { self.display_nav_history_buttons = display; } @@ -1956,6 +2012,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 +2042,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 +2052,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 967e76efd65dd049c0213720266f24d40af88df0..84b84677d1c13e25be0b3fa1fc63725cb93e5c89 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>>, @@ -1315,7 +1322,8 @@ impl Workspace { pub fn open_paths( &mut self, mut abs_paths: Vec, - visible: bool, + visible: OpenVisible, + pane: Option>, cx: &mut ViewContext, ) -> Task, anyhow::Error>>>> { log::info!("open paths {abs_paths:?}"); @@ -1326,31 +1334,56 @@ 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, }; 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, @@ -1396,7 +1429,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, 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(); @@ -1782,7 +1817,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, 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; @@ -4081,7 +4125,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, OpenVisible::All, None, cx) })? .await, )) @@ -4129,7 +4173,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()], OpenVisible::None, None, cx) }) })? .await?