Add more preview tab settings and fix janky behavior (#43921)

Closes #41495

Known issues:
- File path links always open as non-preview tabs. Fixing this is not
technically too difficult but requires more invasive changes and so
should be done in a future PR.

Release Notes:

- Fixed strange behavior when reopening closed preview tabs
- Overhauled preview tabs settings:
- Added setting `preview_tabs.enable_preview_from_project_panel`
(default `true`)
- Kept setting `preview_tabs.enable_preview_from_file_finder` (default
`false`)
- Added setting `preview_tabs.enable_preview_from_multibuffer` (default
`true`)
- Added setting
`preview_tabs.enable_preview_multibuffer_from_code_navigation` (default
`false`)
- Added setting `preview_tabs.enable_preview_file_from_code_navigation`
(default `true`)
- Renamed setting `preview_tabs.enable_preview_from_code_navigation` to
`preview_tabs.enable_keep_preview_on_code_navigation` (default `false`)

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
Andrew Farkas
2025-12-03 21:56:39 +00:00
committed by GitHub
co-authored by Smit Barmase Cole Miller
parent 290a1550aa
commit 87976e91cf
14 changed files with 495 additions and 152 deletions
+17 -3
View File
@@ -64,8 +64,12 @@ pub struct ItemSettings {
#[derive(RegisterSetting)]
pub struct PreviewTabsSettings {
pub enabled: bool,
pub enable_preview_from_project_panel: bool,
pub enable_preview_from_file_finder: bool,
pub enable_preview_from_code_navigation: bool,
pub enable_preview_from_multibuffer: bool,
pub enable_preview_multibuffer_from_code_navigation: bool,
pub enable_preview_file_from_code_navigation: bool,
pub enable_keep_preview_on_code_navigation: bool,
}
impl Settings for ItemSettings {
@@ -87,9 +91,19 @@ impl Settings for PreviewTabsSettings {
let preview_tabs = content.preview_tabs.as_ref().unwrap();
Self {
enabled: preview_tabs.enabled.unwrap(),
enable_preview_from_project_panel: preview_tabs
.enable_preview_from_project_panel
.unwrap(),
enable_preview_from_file_finder: preview_tabs.enable_preview_from_file_finder.unwrap(),
enable_preview_from_code_navigation: preview_tabs
.enable_preview_from_code_navigation
enable_preview_from_multibuffer: preview_tabs.enable_preview_from_multibuffer.unwrap(),
enable_preview_multibuffer_from_code_navigation: preview_tabs
.enable_preview_multibuffer_from_code_navigation
.unwrap(),
enable_preview_file_from_code_navigation: preview_tabs
.enable_preview_file_from_code_navigation
.unwrap(),
enable_keep_preview_on_code_navigation: preview_tabs
.enable_keep_preview_on_code_navigation
.unwrap(),
}
}
+50 -39
View File
@@ -873,10 +873,35 @@ impl Pane {
self.preview_item_id == Some(item_id)
}
/// Promotes the item with the given ID to not be a preview item.
/// This does nothing if it wasn't already a preview item.
pub fn unpreview_item_if_preview(&mut self, item_id: EntityId) {
if self.is_active_preview_item(item_id) {
self.preview_item_id = None;
}
}
/// Marks the item with the given ID as the preview item.
/// This will be ignored if the global setting `preview_tabs` is disabled.
pub fn set_preview_item_id(&mut self, item_id: Option<EntityId>, cx: &App) {
if PreviewTabsSettings::get_global(cx).enabled {
///
/// The old preview item (if there was one) is closed and its index is returned.
pub fn replace_preview_item_id(
&mut self,
item_id: EntityId,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<usize> {
let idx = self.close_current_preview_item(window, cx);
self.set_preview_item_id(Some(item_id), cx);
idx
}
/// Marks the item with the given ID as the preview item.
/// This will be ignored if the global setting `preview_tabs` is disabled.
///
/// This is a low-level method. Prefer `unpreview_item_if_preview()` or `set_new_preview_item()`.
pub(crate) fn set_preview_item_id(&mut self, item_id: Option<EntityId>, cx: &App) {
if item_id.is_none() || PreviewTabsSettings::get_global(cx).enabled {
self.preview_item_id = item_id;
}
}
@@ -895,7 +920,7 @@ impl Pane {
&& preview_item.item_id() == item_id
&& !preview_item.preserve_preview(cx)
{
self.set_preview_item_id(None, cx);
self.unpreview_item_if_preview(item_id);
}
}
@@ -936,14 +961,8 @@ 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 !allow_preview && let Some(item) = pane.items.get(index) {
pane.unpreview_item_if_preview(item.item_id());
}
if activate {
pane.activate_item(index, focus_item, focus_item, window, cx);
@@ -955,7 +974,7 @@ impl Pane {
window: &mut Window,
cx: &mut Context<Self>| {
if allow_preview {
pane.set_preview_item_id(Some(new_item.item_id()), cx);
pane.replace_preview_item_id(new_item.item_id(), window, cx);
}
if let Some(text) = new_item.telemetry_event_text(cx) {
@@ -1036,6 +1055,7 @@ impl Pane {
) -> Option<usize> {
let item_idx = self.preview_item_idx()?;
let id = self.preview_item_id()?;
self.set_preview_item_id(None, cx);
let prev_active_item_index = self.active_item_index;
self.remove_item(id, false, false, window, cx);
@@ -1981,9 +2001,7 @@ impl Pane {
item.on_removed(cx);
self.nav_history.set_mode(mode);
if self.is_active_preview_item(item.item_id()) {
self.set_preview_item_id(None, cx);
}
self.unpreview_item_if_preview(item.item_id());
if let Some(path) = item.project_path(cx) {
let abs_path = self
@@ -2194,9 +2212,7 @@ impl Pane {
if can_save {
pane.update_in(cx, |pane, window, cx| {
if pane.is_active_preview_item(item.item_id()) {
pane.set_preview_item_id(None, cx);
}
pane.unpreview_item_if_preview(item.item_id());
item.save(
SaveOptions {
format: should_format,
@@ -2450,8 +2466,8 @@ impl Pane {
let id = self.item_for_index(ix)?.item_id();
let should_activate = ix == self.active_item_index;
if matches!(operation, PinOperation::Pin) && self.is_active_preview_item(id) {
self.set_preview_item_id(None, cx);
if matches!(operation, PinOperation::Pin) {
self.unpreview_item_if_preview(id);
}
match operation {
@@ -2624,12 +2640,9 @@ impl Pane {
)
.on_mouse_down(
MouseButton::Left,
cx.listener(move |pane, event: &MouseDownEvent, _, cx| {
if let Some(id) = pane.preview_item_id
&& id == item_id
&& event.click_count > 1
{
pane.set_preview_item_id(None, cx);
cx.listener(move |pane, event: &MouseDownEvent, _, _| {
if event.click_count > 1 {
pane.unpreview_item_if_preview(item_id);
}
}),
)
@@ -3272,11 +3285,7 @@ impl Pane {
let mut to_pane = cx.entity();
let split_direction = self.drag_split_direction;
let item_id = dragged_tab.item.item_id();
if let Some(preview_item_id) = self.preview_item_id
&& item_id == preview_item_id
{
self.set_preview_item_id(None, cx);
}
self.unpreview_item_if_preview(item_id);
let is_clone = cfg!(target_os = "macos") && window.modifiers().alt
|| cfg!(not(target_os = "macos")) && window.modifiers().control;
@@ -3788,15 +3797,17 @@ impl Render for Pane {
.on_action(cx.listener(Self::toggle_pin_tab))
.on_action(cx.listener(Self::unpin_all_tabs))
.when(PreviewTabsSettings::get_global(cx).enabled, |this| {
this.on_action(cx.listener(|pane: &mut Pane, _: &TogglePreviewTab, _, cx| {
if let Some(active_item_id) = pane.active_item().map(|i| i.item_id()) {
if pane.is_active_preview_item(active_item_id) {
pane.set_preview_item_id(None, cx);
} else {
pane.set_preview_item_id(Some(active_item_id), cx);
this.on_action(
cx.listener(|pane: &mut Pane, _: &TogglePreviewTab, window, cx| {
if let Some(active_item_id) = pane.active_item().map(|i| i.item_id()) {
if pane.is_active_preview_item(active_item_id) {
pane.unpreview_item_if_preview(active_item_id);
} else {
pane.replace_preview_item_id(active_item_id, window, cx);
}
}
}
}))
}),
)
})
.on_action(
cx.listener(|pane: &mut Self, action: &CloseActiveItem, window, cx| {
+24 -7
View File
@@ -3636,14 +3636,33 @@ impl Workspace {
project_item: Entity<T::Item>,
activate_pane: bool,
focus_item: bool,
keep_old_preview: bool,
allow_new_preview: bool,
window: &mut Window,
cx: &mut Context<Self>,
) -> Entity<T>
where
T: ProjectItem,
{
let old_item_id = pane.read(cx).active_item().map(|item| item.item_id());
if let Some(item) = self.find_project_item(&pane, &project_item, cx) {
if !keep_old_preview
&& let Some(old_id) = old_item_id
&& old_id != item.item_id()
{
// switching to a different item, so unpreview old active item
pane.update(cx, |pane, _| {
pane.unpreview_item_if_preview(old_id);
});
}
self.activate_item(&item, activate_pane, focus_item, window, cx);
if !allow_new_preview {
pane.update(cx, |pane, _| {
pane.unpreview_item_if_preview(item.item_id());
});
}
return item;
}
@@ -3652,16 +3671,14 @@ impl Workspace {
T::for_project_item(self.project().clone(), Some(pane), project_item, window, cx)
})
});
let item_id = item.item_id();
let mut destination_index = None;
pane.update(cx, |pane, cx| {
if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation
&& let Some(preview_item_id) = pane.preview_item_id()
&& preview_item_id != item_id
{
destination_index = pane.close_current_preview_item(window, cx);
if !keep_old_preview && let Some(old_id) = old_item_id {
pane.unpreview_item_if_preview(old_id);
}
if allow_new_preview {
destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
}
pane.set_preview_item_id(Some(item.item_id()), cx)
});
self.add_item(