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:
co-authored by
Conrad Irwin
parent
edb1ea2433
commit
ea4419076e
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user