External file drag and drop (#3933)

Kirill Bulatov created

Deals with https://github.com/zed-industries/community/issues/1317
Deals with https://github.com/zed-industries/community/issues/486

Reworks pane drag and drop code to support 
* dropping external files into main pane (supports splits same as tabs
and project entries drop) — this will open the file dropped
* dropping external files, tabs and project entries drop into the
terminal — this will add file abs path into the terminal

Release Notes:

- Added a way to drag and drop external files into Zed main & terminal
panes; support tabs and project entries drop into terminal pane

Change summary

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/journal/src/journal.rs                |  4 
crates/terminal_view/src/terminal_element.rs | 32 +------
crates/terminal_view/src/terminal_panel.rs   | 82 ++++++++++++++++---
crates/terminal_view/src/terminal_view.rs    | 21 ++++
crates/workspace/src/pane.rs                 | 87 ++++++++++++++++++---
crates/workspace/src/workspace.rs            | 80 +++++++++++++++----
10 files changed, 233 insertions(+), 92 deletions(-)

Detailed changes

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<T: 'static>(&self) -> Option<&T> {
-        self.active_drag
-            .as_ref()
-            .and_then(|drag| drag.value.downcast_ref())
-    }
 }
 
 impl Context for AppContext {

crates/gpui/src/interactive.rs 🔗

@@ -214,7 +214,7 @@ impl Render for ExternalPaths {
 pub enum FileDropEvent {
     Entered {
         position: Point<Pixels>,
-        files: ExternalPaths,
+        paths: ExternalPaths,
     },
     Pending {
         position: Point<Pixels>,

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 {

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,
                         });
                     }

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<AppState>, 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;
 

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, _| {
-                    // 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) {

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::<workspace::pane::DraggedTab>() {
-                        if let Some(item) = tab.pane.read(cx).item_for_index(tab.ix) {
-                            return item.downcast::<TerminalView>().is_some();
-                        }
-                    }
-                    if a.downcast_ref::<ExternalPaths>().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::<DraggedTab>() {
+                    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::<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));
@@ -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::<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/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;

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<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>,
@@ -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<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();
@@ -527,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(),
@@ -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::<DraggedTab>(cx.listener(Self::handle_drag_move))
                     .on_drag_move::<ProjectEntryId>(cx.listener(Self::handle_drag_move))
+                    .on_drag_move::<ExternalPaths>(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::<DraggedTab>("", |style| style.visible())
                             .group_drag_over::<ProjectEntryId>("", |style| style.visible())
+                            .group_drag_over::<ExternalPaths>("", |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(),

crates/workspace/src/workspace.rs 🔗

@@ -431,6 +431,13 @@ pub enum Event {
     WorkspaceCreated(WeakView<Workspace>),
 }
 
+pub enum OpenVisible {
+    All,
+    None,
+    OnlyFiles,
+    OnlyDirectories,
+}
+
 pub struct Workspace {
     weak_self: WeakView<Self>,
     workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
@@ -1315,7 +1322,8 @@ impl Workspace {
     pub fn open_paths(
         &mut self,
         mut abs_paths: Vec<PathBuf>,
-        visible: bool,
+        visible: OpenVisible,
+        pane: Option<WeakView<Pane>>,
         cx: &mut ViewContext<Self>,
     ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, 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?