Add preview tabs (#9125)

This PR implements the preview tabs feature from VSCode.
More details and thanks for the head start of the implementation here
#6782.

Here is what I have observed from using the vscode implementation ([x]
-> already implemented):
- [x] Single click on project file opens tab as preview
- [x] Double click on item in project panel opens tab as permanent
- [x] Double click on the tab makes it permanent
- [x] Navigating away from the tab makes the tab permanent and the new
tab is shown as preview (e.g. GoToReference)
- [x] Existing preview tab is reused when opening a new tab
- [x] Dragging tab to the same/another panel makes the tab permanent
- [x] Opening a tab from the file finder makes the tab permanent
- [x] Editing a preview tab will make the tab permanent
- [x] Using the space key in the project panel opens the tab as preview
- [x] Handle navigation history correctly (restore a preview tab as
preview as well)
- [x] Restore preview tabs after restarting
- [x] Support opening files from file finder in preview mode (vscode:
"Enable Preview From Quick Open")
 
I need to do some more testing of the vscode implementation, there might
be other behaviors/workflows which im not aware of that open an item as
preview/make them permanent.

Showcase:


https://github.com/zed-industries/zed/assets/53836821/9be16515-c740-4905-bea1-88871112ef86


TODOs
- [x] Provide `enable_preview_tabs` setting
- [x] Write some tests
- [x] How should we handle this in collaboration mode (have not tested
the behavior so far)
- [x] Keyboard driven usage (probably need workspace commands)
- [x] Register `TogglePreviewTab` only when setting enabled?
- [x] Render preview tabs in tab switcher as italic
- [x] Render preview tabs in image viewer as italic
- [x] Should this be enabled by default (it is the default behavior in
VSCode)?
- [x] Docs

Future improvements (out of scope for now):
- Support preview mode for find all references and possibly other
multibuffers (VSCode: "Enable Preview From Code Navigation")


Release Notes:

- Added preview tabs
([#4922](https://github.com/zed-industries/zed/issues/4922)).

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Bennet Bo Fenner
2024-04-11 23:09:12 +02:00
committed by GitHub
co-authored by Conrad Irwin
parent edb1ea2433
commit ea4419076e
29 changed files with 783 additions and 152 deletions
+53 -16
View File
@@ -42,6 +42,12 @@ pub struct ItemSettings {
pub close_position: ClosePosition,
}
#[derive(Deserialize)]
pub struct PreviewTabsSettings {
pub enabled: bool,
pub enable_preview_from_file_finder: bool,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum ClosePosition {
@@ -71,6 +77,19 @@ pub struct ItemSettingsContent {
close_position: Option<ClosePosition>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct PreviewTabsSettingsContent {
/// Whether to show opened editors as preview editors.
/// Preview editors do not stay open, are reused until explicitly set to be kept open opened (via double-click or editing) and show file names in italic.
///
/// Default: true
enabled: Option<bool>,
/// Whether to open a preview editor when opening a file using the file finder.
///
/// Default: false
enable_preview_from_file_finder: Option<bool>,
}
impl Settings for ItemSettings {
const KEY: Option<&'static str> = Some("tabs");
@@ -81,6 +100,16 @@ impl Settings for ItemSettings {
}
}
impl Settings for PreviewTabsSettings {
const KEY: Option<&'static str> = Some("preview_tabs");
type FileContent = PreviewTabsSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
sources.json_merge()
}
}
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
pub enum ItemEvent {
CloseItem,
@@ -95,14 +124,16 @@ pub struct BreadcrumbText {
pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
}
#[derive(Debug, Clone, Copy)]
pub struct TabContentParams {
pub detail: Option<usize>,
pub selected: bool,
pub preview: bool,
}
pub trait Item: FocusableView + EventEmitter<Self::Event> {
type Event;
fn tab_content(
&self,
_detail: Option<usize>,
_selected: bool,
_cx: &WindowContext,
) -> AnyElement {
fn tab_content(&self, _params: TabContentParams, _cx: &WindowContext) -> AnyElement {
gpui::Empty.into_any()
}
fn to_item_events(_event: &Self::Event, _f: impl FnMut(ItemEvent)) {}
@@ -236,9 +267,9 @@ pub trait ItemHandle: 'static + Send {
fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
fn tab_content(&self, detail: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement;
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement;
fn telemetry_event_text(&self, cx: &WindowContext) -> Option<&'static str>;
fn dragged_tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement;
fn dragged_tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]>;
@@ -339,12 +370,18 @@ impl<T: Item> ItemHandle for View<T> {
self.read(cx).tab_description(detail, cx)
}
fn tab_content(&self, detail: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement {
self.read(cx).tab_content(detail, selected, cx)
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
self.read(cx).tab_content(params, cx)
}
fn dragged_tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement {
self.read(cx).tab_content(detail, true, cx)
fn dragged_tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
self.read(cx).tab_content(
TabContentParams {
selected: true,
..params
},
cx,
)
}
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
@@ -532,6 +569,7 @@ impl<T: Item> ItemHandle for View<T> {
Pane::autosave_item(&item, workspace.project().clone(), cx)
});
}
pane.update(cx, |pane, cx| pane.handle_item_edit(item.item_id(), cx));
}
_ => {}
@@ -817,7 +855,7 @@ impl<T: FollowableItem> WeakFollowableItemHandle for WeakView<T> {
#[cfg(any(test, feature = "test-support"))]
pub mod test {
use super::{Item, ItemEvent};
use super::{Item, ItemEvent, TabContentParams};
use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
use gpui::{
AnyElement, AppContext, Context as _, EntityId, EventEmitter, FocusableView,
@@ -990,11 +1028,10 @@ pub mod test {
fn tab_content(
&self,
detail: Option<usize>,
_selected: bool,
params: TabContentParams,
_cx: &ui::prelude::WindowContext,
) -> AnyElement {
self.tab_detail.set(detail);
self.tab_detail.set(params.detail);
gpui::div().into_any_element()
}