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:
Kirill Bulatov 2025-08-22 20:04:39 +03:00 committed by GitHub
parent eb0f9ddcdc
commit 42ae3301d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 316 additions and 45 deletions

View file

@ -2,6 +2,7 @@ use crate::{
CloseWindow, NewFile, NewTerminal, OpenInTerminal, OpenOptions, OpenTerminal, OpenVisible,
SplitDirection, ToggleFileFinder, ToggleProjectSymbols, ToggleZoom, Workspace,
WorkspaceItemBuilder,
invalid_buffer_view::InvalidBufferView,
item::{
ActivateOnClose, ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
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 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) = 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);
}
set_up_existing_item(index, self, window, cx);
existing_item
} else {
// 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);
// 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 {
self.set_preview_item_id(Some(new_item.item_id()), cx);
let resulting_item = match already_open_view {
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
}
}