Files
oak-gpui/crates/welcome/src/welcome.rs
T
Bennet Bo FennerandConrad Irwin ea4419076e 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>
2024-04-11 23:09:12 +02:00

322 lines
13 KiB
Rust

mod base_keymap_picker;
mod base_keymap_setting;
use client::{telemetry::Telemetry, TelemetrySettings};
use copilot_ui;
use db::kvp::KEY_VALUE_STORE;
use gpui::{
svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
ParentElement, Render, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
WindowContext,
};
use settings::{Settings, SettingsStore};
use std::sync::Arc;
use ui::{prelude::*, CheckboxWithLabel};
use vim::VimModeSetting;
use workspace::{
dock::DockPosition,
item::{Item, ItemEvent, TabContentParams},
open_new, AppState, Welcome, Workspace, WorkspaceId,
};
pub use base_keymap_setting::BaseKeymap;
pub const FIRST_OPEN: &str = "first_open";
pub fn init(cx: &mut AppContext) {
BaseKeymap::register(cx);
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace.register_action(|workspace, _: &Welcome, cx| {
let welcome_page = WelcomePage::new(workspace, cx);
workspace.add_item_to_active_pane(Box::new(welcome_page), cx)
});
})
.detach();
base_keymap_picker::init(cx);
}
pub fn show_welcome_view(app_state: Arc<AppState>, cx: &mut AppContext) {
open_new(app_state, cx, |workspace, cx| {
workspace.toggle_dock(DockPosition::Left, cx);
let welcome_page = WelcomePage::new(workspace, cx);
workspace.add_item_to_center(Box::new(welcome_page.clone()), cx);
cx.focus_view(&welcome_page);
cx.notify();
})
.detach();
db::write_and_log(cx, || {
KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string())
});
}
pub struct WelcomePage {
workspace: WeakView<Workspace>,
focus_handle: FocusHandle,
telemetry: Arc<Telemetry>,
_settings_subscription: Subscription,
}
impl Render for WelcomePage {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
h_flex()
.size_full()
.bg(cx.theme().colors().editor_background)
.track_focus(&self.focus_handle)
.child(
v_flex()
.w_96()
.gap_4()
.mx_auto()
.child(
svg()
.path("icons/logo_96.svg")
.text_color(gpui::white())
.w(px(96.))
.h(px(96.))
.mx_auto(),
)
.child(
h_flex()
.justify_center()
.child(Label::new("Code at the speed of thought")),
)
.child(
v_flex()
.gap_2()
.child(
Button::new("choose-theme", "Choose a theme")
.full_width()
.on_click(cx.listener(|this, _, cx| {
this.telemetry.report_app_event(
"welcome page: change theme".to_string(),
);
this.workspace
.update(cx, |workspace, cx| {
theme_selector::toggle(
workspace,
&Default::default(),
cx,
)
})
.ok();
})),
)
.child(
Button::new("choose-keymap", "Choose a keymap")
.full_width()
.on_click(cx.listener(|this, _, cx| {
this.telemetry.report_app_event(
"welcome page: change keymap".to_string(),
);
this.workspace
.update(cx, |workspace, cx| {
base_keymap_picker::toggle(
workspace,
&Default::default(),
cx,
)
})
.ok();
})),
)
.child(
Button::new("install-cli", "Install the CLI")
.full_width()
.on_click(cx.listener(|this, _, cx| {
this.telemetry.report_app_event(
"welcome page: install cli".to_string(),
);
cx.app_mut()
.spawn(|cx| async move {
install_cli::install_cli(&cx).await
})
.detach_and_log_err(cx);
})),
)
.child(
Button::new("sign-in-to-copilot", "Sign in to GitHub Copilot")
.full_width()
.on_click(cx.listener(|this, _, cx| {
this.telemetry.report_app_event(
"welcome page: sign in to copilot".to_string(),
);
copilot_ui::initiate_sign_in(cx);
})),
),
)
.child(
v_flex()
.p_3()
.gap_2()
.bg(cx.theme().colors().elevated_surface_background)
.border_1()
.border_color(cx.theme().colors().border)
.rounded_md()
.child(CheckboxWithLabel::new(
"enable-vim",
Label::new("Enable vim mode"),
if VimModeSetting::get_global(cx).0 {
ui::Selection::Selected
} else {
ui::Selection::Unselected
},
cx.listener(move |this, selection, cx| {
this.telemetry
.report_app_event("welcome page: toggle vim".to_string());
this.update_settings::<VimModeSetting>(
selection,
cx,
|setting, value| *setting = Some(value),
);
}),
))
.child(CheckboxWithLabel::new(
"enable-telemetry",
Label::new("Send anonymous usage data"),
if TelemetrySettings::get_global(cx).metrics {
ui::Selection::Selected
} else {
ui::Selection::Unselected
},
cx.listener(move |this, selection, cx| {
this.telemetry.report_app_event(
"welcome page: toggle metric telemetry".to_string(),
);
this.update_settings::<TelemetrySettings>(selection, cx, {
let telemetry = this.telemetry.clone();
move |settings, value| {
settings.metrics = Some(value);
telemetry.report_setting_event(
"metric telemetry",
value.to_string(),
);
}
});
}),
))
.child(CheckboxWithLabel::new(
"enable-crash",
Label::new("Send crash reports"),
if TelemetrySettings::get_global(cx).diagnostics {
ui::Selection::Selected
} else {
ui::Selection::Unselected
},
cx.listener(move |this, selection, cx| {
this.telemetry.report_app_event(
"welcome page: toggle diagnostic telemetry".to_string(),
);
this.update_settings::<TelemetrySettings>(selection, cx, {
let telemetry = this.telemetry.clone();
move |settings, value| {
settings.diagnostics = Some(value);
telemetry.report_setting_event(
"diagnostic telemetry",
value.to_string(),
);
}
});
}),
)),
),
)
}
}
impl WelcomePage {
pub fn new(workspace: &Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
let this = cx.new_view(|cx| {
cx.on_release(|this: &mut Self, _, _| {
this.telemetry
.report_app_event("welcome page: close".to_string());
})
.detach();
WelcomePage {
focus_handle: cx.focus_handle(),
workspace: workspace.weak_handle(),
telemetry: workspace.client().telemetry().clone(),
_settings_subscription: cx
.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
}
});
this
}
fn update_settings<T: Settings>(
&mut self,
selection: &Selection,
cx: &mut ViewContext<Self>,
callback: impl 'static + Send + Fn(&mut T::FileContent, bool),
) {
if let Some(workspace) = self.workspace.upgrade() {
let fs = workspace.read(cx).app_state().fs.clone();
let selection = *selection;
settings::update_settings_file::<T>(fs, cx, move |settings| {
let value = match selection {
Selection::Unselected => false,
Selection::Selected => true,
_ => return,
};
callback(settings, value)
});
}
}
}
impl EventEmitter<ItemEvent> for WelcomePage {}
impl FocusableView for WelcomePage {
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Item for WelcomePage {
type Event = ItemEvent;
fn tab_content(&self, params: TabContentParams, _: &WindowContext) -> AnyElement {
Label::new("Welcome to Zed!")
.color(if params.selected {
Color::Default
} else {
Color::Muted
})
.into_any_element()
}
fn telemetry_event_text(&self) -> Option<&'static str> {
Some("welcome page")
}
fn show_toolbar(&self) -> bool {
false
}
fn clone_on_split(
&self,
_workspace_id: WorkspaceId,
cx: &mut ViewContext<Self>,
) -> Option<View<Self>> {
Some(cx.new_view(|cx| WelcomePage {
focus_handle: cx.focus_handle(),
workspace: self.workspace.clone(),
telemetry: self.telemetry.clone(),
_settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
}))
}
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
f(*event)
}
}