Show file open error view instead of the modal (#36764)
Closes https://github.com/zed-industries/zed/issues/36672 Before: either <img width="966" height="642" alt="image" src="https://github.com/user-attachments/assets/7263ea3c-3d48-4f4d-be9e-16b24ca6f60b" /> (when opening from the project panel) or <img width="959" height="1019" alt="image" src="https://github.com/user-attachments/assets/834041d4-f4d6-46db-b333-803169ec4803" /> (for the rest of the cases) After: <img width="2032" height="1167" alt="Screenshot 2025-08-22 at 19 34 10" src="https://github.com/user-attachments/assets/1aa4530b-69f6-4c3a-8ea1-d4035dbb28da" /> (the unified error view) Release Notes: - Improved unsupported file opening in Zed --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
eb0f9ddcdc
commit
42ae3301d0
11 changed files with 316 additions and 45 deletions
|
@ -855,7 +855,7 @@
|
||||||
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||||
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
||||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
"ctrl-shift-enter": "workspace::OpenWithSystem",
|
||||||
"alt-d": "project_panel::CompareMarkedFiles",
|
"alt-d": "project_panel::CompareMarkedFiles",
|
||||||
"shift-find": "project_panel::NewSearchInDirectory",
|
"shift-find": "project_panel::NewSearchInDirectory",
|
||||||
"ctrl-alt-shift-f": "project_panel::NewSearchInDirectory",
|
"ctrl-alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||||
|
@ -1198,5 +1198,12 @@
|
||||||
"alt-shift-l": "onboarding::SignIn",
|
"alt-shift-l": "onboarding::SignIn",
|
||||||
"alt-shift-a": "onboarding::OpenAccount"
|
"alt-shift-a": "onboarding::OpenAccount"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "InvalidBuffer",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-shift-enter": "workspace::OpenWithSystem"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -915,7 +915,7 @@
|
||||||
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
|
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
|
||||||
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||||
"alt-cmd-r": "project_panel::RevealInFileManager",
|
"alt-cmd-r": "project_panel::RevealInFileManager",
|
||||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
"ctrl-shift-enter": "workspace::OpenWithSystem",
|
||||||
"alt-d": "project_panel::CompareMarkedFiles",
|
"alt-d": "project_panel::CompareMarkedFiles",
|
||||||
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||||
"cmd-alt-shift-f": "project_panel::NewSearchInDirectory",
|
"cmd-alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||||
|
@ -1301,5 +1301,12 @@
|
||||||
"alt-tab": "onboarding::SignIn",
|
"alt-tab": "onboarding::SignIn",
|
||||||
"alt-shift-a": "onboarding::OpenAccount"
|
"alt-shift-a": "onboarding::OpenAccount"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "InvalidBuffer",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-shift-enter": "workspace::OpenWithSystem"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -819,7 +819,7 @@
|
||||||
"v": "project_panel::OpenPermanent",
|
"v": "project_panel::OpenPermanent",
|
||||||
"p": "project_panel::Open",
|
"p": "project_panel::Open",
|
||||||
"x": "project_panel::RevealInFileManager",
|
"x": "project_panel::RevealInFileManager",
|
||||||
"s": "project_panel::OpenWithSystem",
|
"s": "workspace::OpenWithSystem",
|
||||||
"z d": "project_panel::CompareMarkedFiles",
|
"z d": "project_panel::CompareMarkedFiles",
|
||||||
"] c": "project_panel::SelectNextGitEntry",
|
"] c": "project_panel::SelectNextGitEntry",
|
||||||
"[ c": "project_panel::SelectPrevGitEntry",
|
"[ c": "project_panel::SelectPrevGitEntry",
|
||||||
|
|
|
@ -57,7 +57,9 @@ use util::{
|
||||||
use workspace::{
|
use workspace::{
|
||||||
CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
|
CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
|
||||||
OpenOptions, ViewId,
|
OpenOptions, ViewId,
|
||||||
|
invalid_buffer_view::InvalidBufferView,
|
||||||
item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
|
item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
|
||||||
|
register_project_item,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -24348,6 +24350,41 @@ async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
register_project_item::<Editor>(cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
fs.insert_tree("/root1", json!({})).await;
|
||||||
|
fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(fs, ["/root1".as_ref()], cx).await;
|
||||||
|
let (workspace, cx) =
|
||||||
|
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||||
|
|
||||||
|
let worktree_id = project.update(cx, |project, cx| {
|
||||||
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
|
});
|
||||||
|
|
||||||
|
let handle = workspace
|
||||||
|
.update_in(cx, |workspace, window, cx| {
|
||||||
|
let project_path = (worktree_id, "one.pdf");
|
||||||
|
workspace.open_path(project_path, None, true, window, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
handle.to_any().entity_type(),
|
||||||
|
TypeId::of::<InvalidBufferView>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
|
fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
|
||||||
editor
|
editor
|
||||||
|
|
|
@ -42,6 +42,7 @@ use ui::{IconDecorationKind, prelude::*};
|
||||||
use util::{ResultExt, TryFutureExt, paths::PathExt};
|
use util::{ResultExt, TryFutureExt, paths::PathExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
|
CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
|
||||||
|
invalid_buffer_view::InvalidBufferView,
|
||||||
item::{FollowableItem, Item, ItemEvent, ProjectItem, SaveOptions},
|
item::{FollowableItem, Item, ItemEvent, ProjectItem, SaveOptions},
|
||||||
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
||||||
};
|
};
|
||||||
|
@ -1401,6 +1402,16 @@ impl ProjectItem for Editor {
|
||||||
|
|
||||||
editor
|
editor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn for_broken_project_item(
|
||||||
|
abs_path: PathBuf,
|
||||||
|
is_local: bool,
|
||||||
|
e: &anyhow::Error,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Option<InvalidBufferView> {
|
||||||
|
Some(InvalidBufferView::new(abs_path, is_local, e, window, cx))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clip_ranges<'a>(
|
fn clip_ranges<'a>(
|
||||||
|
|
|
@ -69,6 +69,7 @@ use workspace::{
|
||||||
notifications::{DetachAndPromptErr, NotifyTaskExt},
|
notifications::{DetachAndPromptErr, NotifyTaskExt},
|
||||||
};
|
};
|
||||||
use worktree::CreatedEntry;
|
use worktree::CreatedEntry;
|
||||||
|
use zed_actions::workspace::OpenWithSystem;
|
||||||
|
|
||||||
const PROJECT_PANEL_KEY: &str = "ProjectPanel";
|
const PROJECT_PANEL_KEY: &str = "ProjectPanel";
|
||||||
const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX;
|
const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX;
|
||||||
|
@ -255,8 +256,6 @@ actions!(
|
||||||
RevealInFileManager,
|
RevealInFileManager,
|
||||||
/// Removes the selected folder from the project.
|
/// Removes the selected folder from the project.
|
||||||
RemoveFromProject,
|
RemoveFromProject,
|
||||||
/// Opens the selected file with the system's default application.
|
|
||||||
OpenWithSystem,
|
|
||||||
/// Cuts the selected file or directory.
|
/// Cuts the selected file or directory.
|
||||||
Cut,
|
Cut,
|
||||||
/// Pastes the previously cut or copied item.
|
/// Pastes the previously cut or copied item.
|
||||||
|
|
111
crates/workspace/src/invalid_buffer_view.rs
Normal file
111
crates/workspace/src/invalid_buffer_view.rs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
|
use gpui::{EventEmitter, FocusHandle, Focusable};
|
||||||
|
use ui::{
|
||||||
|
App, Button, ButtonCommon, ButtonStyle, Clickable, Context, FluentBuilder, InteractiveElement,
|
||||||
|
KeyBinding, ParentElement, Render, SharedString, Styled as _, Window, h_flex, v_flex,
|
||||||
|
};
|
||||||
|
use zed_actions::workspace::OpenWithSystem;
|
||||||
|
|
||||||
|
use crate::Item;
|
||||||
|
|
||||||
|
/// A view to display when a certain buffer fails to open.
|
||||||
|
pub struct InvalidBufferView {
|
||||||
|
/// Which path was attempted to open.
|
||||||
|
pub abs_path: Arc<PathBuf>,
|
||||||
|
/// An error message, happened when opening the buffer.
|
||||||
|
pub error: SharedString,
|
||||||
|
is_local: bool,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InvalidBufferView {
|
||||||
|
pub fn new(
|
||||||
|
abs_path: PathBuf,
|
||||||
|
is_local: bool,
|
||||||
|
e: &anyhow::Error,
|
||||||
|
_: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
is_local,
|
||||||
|
abs_path: Arc::new(abs_path),
|
||||||
|
error: format!("{e}").into(),
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Item for InvalidBufferView {
|
||||||
|
type Event = ();
|
||||||
|
|
||||||
|
fn tab_content_text(&self, mut detail: usize, _: &App) -> SharedString {
|
||||||
|
// Ensure we always render at least the filename.
|
||||||
|
detail += 1;
|
||||||
|
|
||||||
|
let path = self.abs_path.as_path();
|
||||||
|
|
||||||
|
let mut prefix = path;
|
||||||
|
while detail > 0 {
|
||||||
|
if let Some(parent) = prefix.parent() {
|
||||||
|
prefix = parent;
|
||||||
|
detail -= 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = if detail > 0 {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
path.strip_prefix(prefix).unwrap_or(path)
|
||||||
|
};
|
||||||
|
|
||||||
|
SharedString::new(path.to_string_lossy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<()> for InvalidBufferView {}
|
||||||
|
|
||||||
|
impl Focusable for InvalidBufferView {
|
||||||
|
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for InvalidBufferView {
|
||||||
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {
|
||||||
|
let abs_path = self.abs_path.clone();
|
||||||
|
v_flex()
|
||||||
|
.size_full()
|
||||||
|
.track_focus(&self.focus_handle(cx))
|
||||||
|
.flex_none()
|
||||||
|
.justify_center()
|
||||||
|
.overflow_hidden()
|
||||||
|
.key_context("InvalidBuffer")
|
||||||
|
.child(
|
||||||
|
h_flex().size_full().justify_center().child(
|
||||||
|
v_flex()
|
||||||
|
.justify_center()
|
||||||
|
.gap_2()
|
||||||
|
.child("Cannot display the file contents in Zed")
|
||||||
|
.when(self.is_local, |contents| {
|
||||||
|
contents.child(
|
||||||
|
h_flex().justify_center().child(
|
||||||
|
Button::new("open-with-system", "Open in Default App")
|
||||||
|
.on_click(move |_, _, cx| {
|
||||||
|
cx.open_with_system(&abs_path);
|
||||||
|
})
|
||||||
|
.style(ButtonStyle::Outlined)
|
||||||
|
.key_binding(KeyBinding::for_action(
|
||||||
|
&OpenWithSystem,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
CollaboratorId, DelayedDebouncedEditAction, FollowableViewRegistry, ItemNavHistory,
|
CollaboratorId, DelayedDebouncedEditAction, FollowableViewRegistry, ItemNavHistory,
|
||||||
SerializableItemRegistry, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
|
SerializableItemRegistry, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
|
||||||
|
invalid_buffer_view::InvalidBufferView,
|
||||||
pane::{self, Pane},
|
pane::{self, Pane},
|
||||||
persistence::model::ItemId,
|
persistence::model::ItemId,
|
||||||
searchable::SearchableItemHandle,
|
searchable::SearchableItemHandle,
|
||||||
|
@ -22,6 +23,7 @@ use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
|
path::PathBuf,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
|
@ -1161,6 +1163,22 @@ pub trait ProjectItem: Item {
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
|
||||||
|
/// A fallback handler, which will be called after [`project::ProjectItem::try_open`] fails,
|
||||||
|
/// with the error from that failure as an argument.
|
||||||
|
/// Allows to open an item that can gracefully display and handle errors.
|
||||||
|
fn for_broken_project_item(
|
||||||
|
_abs_path: PathBuf,
|
||||||
|
_is_local: bool,
|
||||||
|
_e: &anyhow::Error,
|
||||||
|
_window: &mut Window,
|
||||||
|
_cx: &mut App,
|
||||||
|
) -> Option<InvalidBufferView>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::{
|
||||||
CloseWindow, NewFile, NewTerminal, OpenInTerminal, OpenOptions, OpenTerminal, OpenVisible,
|
CloseWindow, NewFile, NewTerminal, OpenInTerminal, OpenOptions, OpenTerminal, OpenVisible,
|
||||||
SplitDirection, ToggleFileFinder, ToggleProjectSymbols, ToggleZoom, Workspace,
|
SplitDirection, ToggleFileFinder, ToggleProjectSymbols, ToggleZoom, Workspace,
|
||||||
WorkspaceItemBuilder,
|
WorkspaceItemBuilder,
|
||||||
|
invalid_buffer_view::InvalidBufferView,
|
||||||
item::{
|
item::{
|
||||||
ActivateOnClose, ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
|
ActivateOnClose, ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
|
||||||
ProjectItemKind, SaveOptions, ShowCloseButton, ShowDiagnostics, TabContentParams,
|
ProjectItemKind, SaveOptions, ShowCloseButton, ShowDiagnostics, TabContentParams,
|
||||||
|
@ -897,19 +898,43 @@ impl Pane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let set_up_existing_item =
|
||||||
|
|index: usize, pane: &mut Self, window: &mut Window, cx: &mut Context<Self>| {
|
||||||
|
// If the item is already open, and the item is a preview item
|
||||||
|
// and we are not allowing items to open as preview, mark the item as persistent.
|
||||||
|
if let Some(preview_item_id) = pane.preview_item_id
|
||||||
|
&& let Some(tab) = pane.items.get(index)
|
||||||
|
&& tab.item_id() == preview_item_id
|
||||||
|
&& !allow_preview
|
||||||
|
{
|
||||||
|
pane.set_preview_item_id(None, cx);
|
||||||
|
}
|
||||||
|
if activate {
|
||||||
|
pane.activate_item(index, focus_item, focus_item, window, cx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let set_up_new_item = |new_item: Box<dyn ItemHandle>,
|
||||||
|
destination_index: Option<usize>,
|
||||||
|
pane: &mut Self,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>| {
|
||||||
|
if allow_preview {
|
||||||
|
pane.set_preview_item_id(Some(new_item.item_id()), cx);
|
||||||
|
}
|
||||||
|
pane.add_item_inner(
|
||||||
|
new_item,
|
||||||
|
true,
|
||||||
|
focus_item,
|
||||||
|
activate,
|
||||||
|
destination_index,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if let Some((index, existing_item)) = existing_item {
|
if let Some((index, existing_item)) = existing_item {
|
||||||
// If the item is already open, and the item is a preview item
|
set_up_existing_item(index, self, window, cx);
|
||||||
// and we are not allowing items to open as preview, mark the item as persistent.
|
|
||||||
if let Some(preview_item_id) = self.preview_item_id
|
|
||||||
&& let Some(tab) = self.items.get(index)
|
|
||||||
&& tab.item_id() == preview_item_id
|
|
||||||
&& !allow_preview
|
|
||||||
{
|
|
||||||
self.set_preview_item_id(None, cx);
|
|
||||||
}
|
|
||||||
if activate {
|
|
||||||
self.activate_item(index, focus_item, focus_item, window, cx);
|
|
||||||
}
|
|
||||||
existing_item
|
existing_item
|
||||||
} else {
|
} else {
|
||||||
// If the item is being opened as preview and we have an existing preview tab,
|
// If the item is being opened as preview and we have an existing preview tab,
|
||||||
|
@ -921,21 +946,46 @@ impl Pane {
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_item = build_item(self, window, cx);
|
let new_item = build_item(self, window, cx);
|
||||||
|
// A special case that won't ever get a `project_entry_id` but has to be deduplicated nonetheless.
|
||||||
|
if let Some(invalid_buffer_view) = new_item.downcast::<InvalidBufferView>() {
|
||||||
|
let mut already_open_view = None;
|
||||||
|
let mut views_to_close = HashSet::default();
|
||||||
|
for existing_error_view in self
|
||||||
|
.items_of_type::<InvalidBufferView>()
|
||||||
|
.filter(|item| item.read(cx).abs_path == invalid_buffer_view.read(cx).abs_path)
|
||||||
|
{
|
||||||
|
if already_open_view.is_none()
|
||||||
|
&& existing_error_view.read(cx).error == invalid_buffer_view.read(cx).error
|
||||||
|
{
|
||||||
|
already_open_view = Some(existing_error_view);
|
||||||
|
} else {
|
||||||
|
views_to_close.insert(existing_error_view.item_id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if allow_preview {
|
let resulting_item = match already_open_view {
|
||||||
self.set_preview_item_id(Some(new_item.item_id()), cx);
|
Some(already_open_view) => {
|
||||||
|
if let Some(index) = self.index_for_item_id(already_open_view.item_id()) {
|
||||||
|
set_up_existing_item(index, self, window, cx);
|
||||||
|
}
|
||||||
|
Box::new(already_open_view) as Box<_>
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
set_up_new_item(new_item.clone(), destination_index, self, window, cx);
|
||||||
|
new_item
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.close_items(window, cx, SaveIntent::Skip, |existing_item| {
|
||||||
|
views_to_close.contains(&existing_item)
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
resulting_item
|
||||||
|
} else {
|
||||||
|
set_up_new_item(new_item.clone(), destination_index, self, window, cx);
|
||||||
|
new_item
|
||||||
}
|
}
|
||||||
self.add_item_inner(
|
|
||||||
new_item.clone(),
|
|
||||||
true,
|
|
||||||
focus_item,
|
|
||||||
activate,
|
|
||||||
destination_index,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
new_item
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod dock;
|
pub mod dock;
|
||||||
pub mod history_manager;
|
pub mod history_manager;
|
||||||
|
pub mod invalid_buffer_view;
|
||||||
pub mod item;
|
pub mod item;
|
||||||
mod modal_layer;
|
mod modal_layer;
|
||||||
pub mod notifications;
|
pub mod notifications;
|
||||||
|
@ -612,21 +613,49 @@ impl ProjectItemRegistry {
|
||||||
);
|
);
|
||||||
self.build_project_item_for_path_fns
|
self.build_project_item_for_path_fns
|
||||||
.push(|project, project_path, window, cx| {
|
.push(|project, project_path, window, cx| {
|
||||||
|
let project_path = project_path.clone();
|
||||||
|
let abs_path = project.read(cx).absolute_path(&project_path, cx);
|
||||||
|
let is_local = project.read(cx).is_local();
|
||||||
let project_item =
|
let project_item =
|
||||||
<T::Item as project::ProjectItem>::try_open(project, project_path, cx)?;
|
<T::Item as project::ProjectItem>::try_open(project, &project_path, cx)?;
|
||||||
let project = project.clone();
|
let project = project.clone();
|
||||||
Some(window.spawn(cx, async move |cx| {
|
Some(window.spawn(cx, async move |cx| match project_item.await {
|
||||||
let project_item = project_item.await?;
|
Ok(project_item) => {
|
||||||
let project_entry_id: Option<ProjectEntryId> =
|
let project_item = project_item;
|
||||||
project_item.read_with(cx, project::ProjectItem::entry_id)?;
|
let project_entry_id: Option<ProjectEntryId> =
|
||||||
let build_workspace_item = Box::new(
|
project_item.read_with(cx, project::ProjectItem::entry_id)?;
|
||||||
|pane: &mut Pane, window: &mut Window, cx: &mut Context<Pane>| {
|
let build_workspace_item = Box::new(
|
||||||
Box::new(cx.new(|cx| {
|
|pane: &mut Pane, window: &mut Window, cx: &mut Context<Pane>| {
|
||||||
T::for_project_item(project, Some(pane), project_item, window, cx)
|
Box::new(cx.new(|cx| {
|
||||||
})) as Box<dyn ItemHandle>
|
T::for_project_item(
|
||||||
|
project,
|
||||||
|
Some(pane),
|
||||||
|
project_item,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})) as Box<dyn ItemHandle>
|
||||||
|
},
|
||||||
|
) as Box<_>;
|
||||||
|
Ok((project_entry_id, build_workspace_item))
|
||||||
|
}
|
||||||
|
Err(e) => match abs_path {
|
||||||
|
Some(abs_path) => match cx.update(|window, cx| {
|
||||||
|
T::for_broken_project_item(abs_path, is_local, &e, window, cx)
|
||||||
|
})? {
|
||||||
|
Some(broken_project_item_view) => {
|
||||||
|
let build_workspace_item = Box::new(
|
||||||
|
move |_: &mut Pane, _: &mut Window, cx: &mut Context<Pane>| {
|
||||||
|
cx.new(|_| broken_project_item_view).boxed_clone()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
as Box<_>;
|
||||||
|
Ok((None, build_workspace_item))
|
||||||
|
}
|
||||||
|
None => Err(e)?,
|
||||||
},
|
},
|
||||||
) as Box<_>;
|
None => Err(e)?,
|
||||||
Ok((project_entry_id, build_workspace_item))
|
},
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3379,9 +3408,8 @@ impl Workspace {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
|
) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
|
||||||
let project = self.project().clone();
|
|
||||||
let registry = cx.default_global::<ProjectItemRegistry>().clone();
|
let registry = cx.default_global::<ProjectItemRegistry>().clone();
|
||||||
registry.open_path(&project, &path, window, cx)
|
registry.open_path(self.project(), &path, window, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_project_item<T>(
|
pub fn find_project_item<T>(
|
||||||
|
|
|
@ -156,7 +156,10 @@ pub mod workspace {
|
||||||
#[action(deprecated_aliases = ["editor::CopyPath", "outline_panel::CopyPath", "project_panel::CopyPath"])]
|
#[action(deprecated_aliases = ["editor::CopyPath", "outline_panel::CopyPath", "project_panel::CopyPath"])]
|
||||||
CopyPath,
|
CopyPath,
|
||||||
#[action(deprecated_aliases = ["editor::CopyRelativePath", "outline_panel::CopyRelativePath", "project_panel::CopyRelativePath"])]
|
#[action(deprecated_aliases = ["editor::CopyRelativePath", "outline_panel::CopyRelativePath", "project_panel::CopyRelativePath"])]
|
||||||
CopyRelativePath
|
CopyRelativePath,
|
||||||
|
/// Opens the selected file with the system's default application.
|
||||||
|
#[action(deprecated_aliases = ["project_panel::OpenWithSystem"])]
|
||||||
|
OpenWithSystem,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue