From cc3b0d419856eb30eca7d8f85c7166085a2aeaf3 Mon Sep 17 00:00:00 2001
From: Andrew Farkas <6060305+HactarCE@users.noreply.github.com>
Date: Wed, 8 Oct 2025 18:47:25 -0400
Subject: [PATCH] Onboarding refactor (#39724)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fixes #39347
Release Notes:
- Improved onboarding UI by collapsing it to a single page
---------
Co-authored-by: dino
Co-authored-by: Lukas Wirth
Co-authored-by: Mikayla Maki
Co-authored-by: Anthony Eid
Co-authored-by: Mikayla Maki
---
Cargo.lock | 5 -
assets/keymaps/default-linux.json | 3 -
assets/keymaps/default-macos.json | 5 +-
assets/keymaps/default-windows.json | 3 -
crates/agent_ui/src/agent_configuration.rs | 8 +-
crates/gpui/src/window.rs | 2 +-
crates/onboarding/Cargo.toml | 5 -
crates/onboarding/src/ai_setup_page.rs | 427 ------------
crates/onboarding/src/basics_page.rs | 244 ++++---
crates/onboarding/src/editing_page.rs | 611 ------------------
crates/onboarding/src/onboarding.rs | 436 +++----------
crates/onboarding/src/welcome.rs | 10 +-
.../ui/src/components/button/button_like.rs | 89 ++-
.../ui/src/components/button/toggle_button.rs | 64 +-
crates/ui/src/components/toggle.rs | 46 +-
15 files changed, 419 insertions(+), 1539 deletions(-)
delete mode 100644 crates/onboarding/src/ai_setup_page.rs
delete mode 100644 crates/onboarding/src/editing_page.rs
diff --git a/Cargo.lock b/Cargo.lock
index b908039e5356768fab720780b52451561629216e..32441f83ae6107627b79db07b536ec9943b8c7d2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -10535,20 +10535,15 @@ dependencies = [
name = "onboarding"
version = "0.1.0"
dependencies = [
- "ai_onboarding",
"anyhow",
"client",
"component",
"db",
"documented",
- "editor",
"fs",
"fuzzy",
"git",
"gpui",
- "itertools 0.14.0",
- "language",
- "language_model",
"menu",
"notifications",
"picker",
diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json
index 2611078df14cc618390e6f04432dd0bf113f3885..dfe9920f7136d1410aaa67079f1f45700425d6a5 100644
--- a/assets/keymaps/default-linux.json
+++ b/assets/keymaps/default-linux.json
@@ -1229,9 +1229,6 @@
"context": "Onboarding",
"use_key_equivalents": true,
"bindings": {
- "ctrl-1": "onboarding::ActivateBasicsPage",
- "ctrl-2": "onboarding::ActivateEditingPage",
- "ctrl-3": "onboarding::ActivateAISetupPage",
"ctrl-enter": "onboarding::Finish",
"alt-shift-l": "onboarding::SignIn",
"alt-shift-a": "onboarding::OpenAccount"
diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json
index f2528766c064ed74207cef9ee27d22cd8d5d8cbd..d5649d9d6c29118113191f689edee6584f26f777 100644
--- a/assets/keymaps/default-macos.json
+++ b/assets/keymaps/default-macos.json
@@ -1334,10 +1334,7 @@
"context": "Onboarding",
"use_key_equivalents": true,
"bindings": {
- "cmd-1": "onboarding::ActivateBasicsPage",
- "cmd-2": "onboarding::ActivateEditingPage",
- "cmd-3": "onboarding::ActivateAISetupPage",
- "cmd-escape": "onboarding::Finish",
+ "cmd-enter": "onboarding::Finish",
"alt-tab": "onboarding::SignIn",
"alt-shift-a": "onboarding::OpenAccount"
}
diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json
index a923457b5d73270ffab84fc3a3f5b3cc92ce4e1a..56517b34567db7ab2d35984cb78fa9424c188301 100644
--- a/assets/keymaps/default-windows.json
+++ b/assets/keymaps/default-windows.json
@@ -1257,9 +1257,6 @@
"context": "Onboarding",
"use_key_equivalents": true,
"bindings": {
- "ctrl-1": "onboarding::ActivateBasicsPage",
- "ctrl-2": "onboarding::ActivateEditingPage",
- "ctrl-3": "onboarding::ActivateAISetupPage",
"ctrl-enter": "onboarding::Finish",
"alt-shift-l": "onboarding::SignIn",
"shift-alt-a": "onboarding::OpenAccount"
diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs
index 3581baf4ec62b746a27bd78bade2d9e85ade069a..bd68271c3cc042f98c205148095124cbf9fab89a 100644
--- a/crates/agent_ui/src/agent_configuration.rs
+++ b/crates/agent_ui/src/agent_configuration.rs
@@ -409,7 +409,7 @@ impl AgentConfiguration {
SwitchField::new(
"always-allow-tool-actions-switch",
- "Allow running commands without asking for confirmation",
+ Some("Allow running commands without asking for confirmation"),
Some(
"The agent can perform potentially destructive actions without asking for your confirmation.".into(),
),
@@ -429,7 +429,7 @@ impl AgentConfiguration {
SwitchField::new(
"single-file-review",
- "Enable single-file agent reviews",
+ Some("Enable single-file agent reviews"),
Some("Agent edits are also displayed in single-file editors for review.".into()),
single_file_review,
move |state, _window, cx| {
@@ -450,7 +450,7 @@ impl AgentConfiguration {
SwitchField::new(
"sound-notification",
- "Play sound when finished generating",
+ Some("Play sound when finished generating"),
Some(
"Hear a notification sound when the agent is done generating changes or needs your input.".into(),
),
@@ -470,7 +470,7 @@ impl AgentConfiguration {
SwitchField::new(
"modifier-send",
- "Use modifier to submit a message",
+ Some("Use modifier to submit a message"),
Some(
"Make a modifier (cmd-enter on macOS, ctrl-enter on Linux or Windows) required to send messages.".into(),
),
diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs
index 855759279b7c1af177bd445950960b4ee8f8bf2d..aecac1fc770e56990dbf6ac4118d835f25d5766e 100644
--- a/crates/gpui/src/window.rs
+++ b/crates/gpui/src/window.rs
@@ -58,7 +58,7 @@ mod prompts;
use crate::util::atomic_incr_if_not_zero;
pub use prompts::*;
-pub(crate) const DEFAULT_WINDOW_SIZE: Size = size(px(1024.), px(700.));
+pub(crate) const DEFAULT_WINDOW_SIZE: Size = size(px(1536.), px(864.));
/// Represents the two different phases when dispatching events.
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
diff --git a/crates/onboarding/Cargo.toml b/crates/onboarding/Cargo.toml
index f51a04cc7452ec1616401218e01e904039183751..2e9797f717b446177efa08713489aed49892c8c8 100644
--- a/crates/onboarding/Cargo.toml
+++ b/crates/onboarding/Cargo.toml
@@ -15,20 +15,15 @@ path = "src/onboarding.rs"
default = []
[dependencies]
-ai_onboarding.workspace = true
anyhow.workspace = true
client.workspace = true
component.workspace = true
db.workspace = true
documented.workspace = true
-editor.workspace = true
fs.workspace = true
fuzzy.workspace = true
git.workspace = true
gpui.workspace = true
-itertools.workspace = true
-language.workspace = true
-language_model.workspace = true
menu.workspace = true
notifications.workspace = true
picker.workspace = true
diff --git a/crates/onboarding/src/ai_setup_page.rs b/crates/onboarding/src/ai_setup_page.rs
deleted file mode 100644
index 6acc8aab389c4563f2302ae3a71934676669c130..0000000000000000000000000000000000000000
--- a/crates/onboarding/src/ai_setup_page.rs
+++ /dev/null
@@ -1,427 +0,0 @@
-use std::sync::Arc;
-
-use ai_onboarding::AiUpsellCard;
-use client::{Client, UserStore, zed_urls};
-use fs::Fs;
-use gpui::{
- Action, AnyView, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity,
- Window, prelude::*,
-};
-use itertools;
-use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
-use project::DisableAiSettings;
-use settings::{Settings, update_settings_file};
-use ui::{
- Badge, ButtonLike, Divider, KeyBinding, Modal, ModalFooter, ModalHeader, Section, SwitchField,
- ToggleState, prelude::*, tooltip_container,
-};
-use util::ResultExt;
-use workspace::{ModalView, Workspace};
-use zed_actions::agent::OpenSettings;
-
-const FEATURED_PROVIDERS: [&str; 4] = ["anthropic", "google", "openai", "ollama"];
-
-fn render_llm_provider_section(
- tab_index: &mut isize,
- workspace: WeakEntity,
- disabled: bool,
- window: &mut Window,
- cx: &mut App,
-) -> impl IntoElement {
- v_flex()
- .gap_4()
- .child(
- v_flex()
- .child(Label::new("Or use other LLM providers").size(LabelSize::Large))
- .child(
- Label::new("Bring your API keys to use the available providers with Zed's UI for free.")
- .color(Color::Muted),
- ),
- )
- .child(render_llm_provider_card(tab_index, workspace, disabled, window, cx))
-}
-
-fn render_privacy_card(tab_index: &mut isize, disabled: bool, cx: &mut App) -> impl IntoElement {
- let (title, description) = if disabled {
- (
- "AI is disabled across Zed",
- "Re-enable it any time in Settings.",
- )
- } else {
- (
- "Privacy is the default for Zed",
- "Any use or storage of your data is with your explicit, single-use, opt-in consent.",
- )
- };
-
- v_flex()
- .relative()
- .pt_2()
- .pb_2p5()
- .pl_3()
- .pr_2()
- .border_1()
- .border_dashed()
- .border_color(cx.theme().colors().border.opacity(0.5))
- .bg(cx.theme().colors().surface_background.opacity(0.3))
- .rounded_lg()
- .overflow_hidden()
- .child(
- h_flex()
- .gap_2()
- .justify_between()
- .child(Label::new(title))
- .child(
- h_flex()
- .gap_1()
- .child(
- Badge::new("Privacy")
- .icon(IconName::ShieldCheck)
- .tooltip(move |_, cx| cx.new(|_| AiPrivacyTooltip::new()).into()),
- )
- .child(
- Button::new("learn_more", "Learn More")
- .style(ButtonStyle::Outlined)
- .label_size(LabelSize::Small)
- .icon(IconName::ArrowUpRight)
- .icon_size(IconSize::XSmall)
- .icon_color(Color::Muted)
- .on_click(|_, _, cx| {
- cx.open_url(&zed_urls::ai_privacy_and_security(cx))
- })
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- }),
- ),
- ),
- )
- .child(
- Label::new(description)
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
-}
-
-fn render_llm_provider_card(
- tab_index: &mut isize,
- workspace: WeakEntity,
- disabled: bool,
- _: &mut Window,
- cx: &mut App,
-) -> impl IntoElement {
- let registry = LanguageModelRegistry::read_global(cx);
-
- v_flex()
- .border_1()
- .border_color(cx.theme().colors().border)
- .bg(cx.theme().colors().surface_background.opacity(0.5))
- .rounded_lg()
- .overflow_hidden()
- .children(itertools::intersperse_with(
- FEATURED_PROVIDERS
- .into_iter()
- .flat_map(|provider_name| {
- registry.provider(&LanguageModelProviderId::new(provider_name))
- })
- .enumerate()
- .map(|(index, provider)| {
- let group_name = SharedString::new(format!("onboarding-hover-group-{}", index));
- let is_authenticated = provider.is_authenticated(cx);
-
- ButtonLike::new(("onboarding-ai-setup-buttons", index))
- .size(ButtonSize::Large)
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- })
- .child(
- h_flex()
- .group(&group_name)
- .px_0p5()
- .w_full()
- .gap_2()
- .justify_between()
- .child(
- h_flex()
- .gap_1()
- .child(
- Icon::new(provider.icon())
- .color(Color::Muted)
- .size(IconSize::XSmall),
- )
- .child(Label::new(provider.name().0)),
- )
- .child(
- h_flex()
- .gap_1()
- .when(!is_authenticated, |el| {
- el.visible_on_hover(group_name.clone())
- .child(
- Icon::new(IconName::Settings)
- .color(Color::Muted)
- .size(IconSize::XSmall),
- )
- .child(
- Label::new("Configure")
- .color(Color::Muted)
- .size(LabelSize::Small),
- )
- })
- .when(is_authenticated && !disabled, |el| {
- el.child(
- Icon::new(IconName::Check)
- .color(Color::Success)
- .size(IconSize::XSmall),
- )
- .child(
- Label::new("Configured")
- .color(Color::Muted)
- .size(LabelSize::Small),
- )
- }),
- ),
- )
- .on_click({
- let workspace = workspace.clone();
- move |_, window, cx| {
- workspace
- .update(cx, |workspace, cx| {
- workspace.toggle_modal(window, cx, |window, cx| {
- telemetry::event!(
- "Welcome AI Modal Opened",
- provider = provider.name().0,
- );
-
- let modal = AiConfigurationModal::new(
- provider.clone(),
- window,
- cx,
- );
- window.focus(&modal.focus_handle(cx));
- modal
- });
- })
- .log_err();
- }
- })
- .into_any_element()
- }),
- || Divider::horizontal().into_any_element(),
- ))
- .child(Divider::horizontal())
- .child(
- Button::new("agent_settings", "Add Many Others")
- .size(ButtonSize::Large)
- .icon(IconName::Plus)
- .icon_position(IconPosition::Start)
- .icon_color(Color::Muted)
- .icon_size(IconSize::XSmall)
- .on_click(|_event, window, cx| {
- window.dispatch_action(OpenSettings.boxed_clone(), cx)
- })
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- }),
- )
-}
-
-pub(crate) fn render_ai_setup_page(
- workspace: WeakEntity,
- user_store: Entity,
- client: Arc,
- window: &mut Window,
- cx: &mut App,
-) -> impl IntoElement {
- let mut tab_index = 0;
- let is_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
-
- v_flex()
- .gap_2()
- .child(
- SwitchField::new(
- "enable_ai",
- "Enable AI features",
- None,
- if is_ai_disabled {
- ToggleState::Unselected
- } else {
- ToggleState::Selected
- },
- |&toggle_state, _, cx| {
- let enabled = match toggle_state {
- ToggleState::Indeterminate => {
- return;
- }
- ToggleState::Unselected => true,
- ToggleState::Selected => false,
- };
-
- telemetry::event!(
- "Welcome AI Enabled",
- toggle = if enabled { "on" } else { "off" },
- );
-
- let fs = ::global(cx);
- update_settings_file(fs, cx, move |settings, _| {
- settings.disable_ai = Some(enabled.into());
- });
- },
- )
- .tab_index({
- tab_index += 1;
- tab_index - 1
- }),
- )
- .child(render_privacy_card(&mut tab_index, is_ai_disabled, cx))
- .child(
- v_flex()
- .mt_2()
- .gap_6()
- .child(
- AiUpsellCard::new(client, &user_store, user_store.read(cx).plan(), cx)
- .tab_index(Some({
- tab_index += 1;
- tab_index - 1
- })),
- )
- .child(render_llm_provider_section(
- &mut tab_index,
- workspace,
- is_ai_disabled,
- window,
- cx,
- ))
- .when(is_ai_disabled, |this| {
- this.child(
- div()
- .id("backdrop")
- .size_full()
- .absolute()
- .inset_0()
- .bg(cx.theme().colors().editor_background)
- .opacity(0.8)
- .block_mouse_except_scroll(),
- )
- }),
- )
-}
-
-struct AiConfigurationModal {
- focus_handle: FocusHandle,
- selected_provider: Arc,
- configuration_view: AnyView,
-}
-
-impl AiConfigurationModal {
- fn new(
- selected_provider: Arc,
- window: &mut Window,
- cx: &mut Context,
- ) -> Self {
- let focus_handle = cx.focus_handle();
- let configuration_view = selected_provider.configuration_view(
- language_model::ConfigurationViewTargetAgent::ZedAgent,
- window,
- cx,
- );
-
- Self {
- focus_handle,
- configuration_view,
- selected_provider,
- }
- }
-
- fn cancel(&mut self, _: &menu::Cancel, cx: &mut Context) {
- cx.emit(DismissEvent);
- }
-}
-
-impl ModalView for AiConfigurationModal {}
-
-impl EventEmitter for AiConfigurationModal {}
-
-impl Focusable for AiConfigurationModal {
- fn focus_handle(&self, _cx: &App) -> FocusHandle {
- self.focus_handle.clone()
- }
-}
-
-impl Render for AiConfigurationModal {
- fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement {
- v_flex()
- .key_context("OnboardingAiConfigurationModal")
- .w(rems(34.))
- .elevation_3(cx)
- .track_focus(&self.focus_handle)
- .on_action(
- cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(&menu::Cancel, cx)),
- )
- .child(
- Modal::new("onboarding-ai-setup-modal", None)
- .header(
- ModalHeader::new()
- .icon(
- Icon::new(self.selected_provider.icon())
- .color(Color::Muted)
- .size(IconSize::Small),
- )
- .headline(self.selected_provider.name().0),
- )
- .section(Section::new().child(self.configuration_view.clone()))
- .footer(
- ModalFooter::new().end_slot(
- Button::new("ai-onb-modal-Done", "Done")
- .key_binding(
- KeyBinding::for_action_in(
- &menu::Cancel,
- &self.focus_handle.clone(),
- window,
- cx,
- )
- .map(|kb| kb.size(rems_from_px(12.))),
- )
- .on_click(cx.listener(|this, _event, _window, cx| {
- this.cancel(&menu::Cancel, cx)
- })),
- ),
- ),
- )
- }
-}
-
-pub struct AiPrivacyTooltip {}
-
-impl AiPrivacyTooltip {
- pub fn new() -> Self {
- Self {}
- }
-}
-
-impl Render for AiPrivacyTooltip {
- fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement {
- const DESCRIPTION: &str = "We believe in opt-in data sharing as the default for building AI products, rather than opt-out. We'll only use or store your data if you affirmatively send it to us. ";
-
- tooltip_container(cx, move |this, _| {
- this.child(
- h_flex()
- .gap_1()
- .child(
- Icon::new(IconName::ShieldCheck)
- .size(IconSize::Small)
- .color(Color::Muted),
- )
- .child(Label::new("Privacy First")),
- )
- .child(
- div().max_w_64().child(
- Label::new(DESCRIPTION)
- .size(LabelSize::Small)
- .color(Color::Muted),
- ),
- )
- })
- }
-}
diff --git a/crates/onboarding/src/basics_page.rs b/crates/onboarding/src/basics_page.rs
index 99af251dfefcab5fd4dba6f82e02531d3c849433..31547115bd547bbf945f466de232cb9e7dd7724e 100644
--- a/crates/onboarding/src/basics_page.rs
+++ b/crates/onboarding/src/basics_page.rs
@@ -2,19 +2,23 @@ use std::sync::Arc;
use client::TelemetrySettings;
use fs::Fs;
-use gpui::{App, IntoElement};
+use gpui::{Action, App, IntoElement};
use settings::{BaseKeymap, Settings, update_settings_file};
use theme::{
Appearance, SystemAppearance, ThemeMode, ThemeName, ThemeRegistry, ThemeSelection,
ThemeSettings,
};
use ui::{
- ParentElement as _, StatefulInteractiveElement, SwitchField, ToggleButtonGroup,
- ToggleButtonSimple, ToggleButtonWithIcon, prelude::*, rems_from_px,
+ ButtonLike, ParentElement as _, StatefulInteractiveElement, SwitchField, TintColor,
+ ToggleButtonGroup, ToggleButtonGroupSize, ToggleButtonSimple, ToggleButtonWithIcon, prelude::*,
+ rems_from_px,
};
use vim_mode_setting::VimModeSetting;
-use crate::theme_preview::{ThemePreviewStyle, ThemePreviewTile};
+use crate::{
+ ImportCursorSettings, ImportVsCodeSettings, SettingsImportState,
+ theme_preview::{ThemePreviewStyle, ThemePreviewTile},
+};
const LIGHT_THEMES: [&str; 3] = ["One Light", "Ayu Light", "Gruvbox Light"];
const DARK_THEMES: [&str; 3] = ["One Dark", "Ayu Dark", "Gruvbox Dark"];
@@ -78,6 +82,7 @@ fn render_theme_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement
)
}),
)
+ .size(ToggleButtonGroupSize::Medium)
.tab_index(tab_index)
.selected_index(theme_mode as usize)
.style(ui::ToggleButtonGroupStyle::Outlined)
@@ -228,91 +233,87 @@ fn render_telemetry_section(tab_index: &mut isize, cx: &App) -> impl IntoElement
.gap_4()
.border_t_1()
.border_color(cx.theme().colors().border_variant.opacity(0.5))
- .child(Label::new("Telemetry").size(LabelSize::Large))
- .child(SwitchField::new(
- "onboarding-telemetry-metrics",
- "Help Improve Zed",
- Some("Anonymous usage data helps us build the right features and improve your experience.".into()),
- if TelemetrySettings::get_global(cx).metrics {
- ui::ToggleState::Selected
- } else {
- ui::ToggleState::Unselected
- },
- {
- let fs = fs.clone();
- move |selection, _, cx| {
- let enabled = match selection {
- ToggleState::Selected => true,
- ToggleState::Unselected => false,
- ToggleState::Indeterminate => { return; },
- };
-
- update_settings_file(
- fs.clone(),
- cx,
- move |setting, _| {
- setting.telemetry.get_or_insert_default().metrics = Some(enabled);
- }
- ,
- );
-
- // This telemetry event shouldn't fire when it's off. If it does we'll be alerted
- // and can fix it in a timely manner to respect a user's choice.
- telemetry::event!("Welcome Page Telemetry Metrics Toggled",
- options = if enabled {
- "on"
- } else {
- "off"
+ .child(
+ SwitchField::new(
+ "onboarding-telemetry-metrics",
+ None::<&str>,
+ Some("Help improve Zed by sending anonymous usage data".into()),
+ if TelemetrySettings::get_global(cx).metrics {
+ ui::ToggleState::Selected
+ } else {
+ ui::ToggleState::Unselected
+ },
+ {
+ let fs = fs.clone();
+ move |selection, _, cx| {
+ let enabled = match selection {
+ ToggleState::Selected => true,
+ ToggleState::Unselected => false,
+ ToggleState::Indeterminate => {
+ return;
+ }
+ };
+
+ update_settings_file(fs.clone(), cx, move |setting, _| {
+ setting.telemetry.get_or_insert_default().metrics = Some(enabled);
+ });
+
+ // This telemetry event shouldn't fire when it's off. If it does we'll be alerted
+ // and can fix it in a timely manner to respect a user's choice.
+ telemetry::event!(
+ "Welcome Page Telemetry Metrics Toggled",
+ options = if enabled { "on" } else { "off" }
+ );
}
- );
+ },
+ )
+ .tab_index({
+ *tab_index += 1;
+ *tab_index
+ }),
+ )
+ .child(
+ SwitchField::new(
+ "onboarding-telemetry-crash-reports",
+ None::<&str>,
+ Some(
+ "Help fix Zed by sending crash reports so we can fix critical issues fast"
+ .into(),
+ ),
+ if TelemetrySettings::get_global(cx).diagnostics {
+ ui::ToggleState::Selected
+ } else {
+ ui::ToggleState::Unselected
+ },
+ {
+ let fs = fs.clone();
+ move |selection, _, cx| {
+ let enabled = match selection {
+ ToggleState::Selected => true,
+ ToggleState::Unselected => false,
+ ToggleState::Indeterminate => {
+ return;
+ }
+ };
- }},
- ).tab_index({
- *tab_index += 1;
- *tab_index
- }))
- .child(SwitchField::new(
- "onboarding-telemetry-crash-reports",
- "Help Fix Zed",
- Some("Send crash reports so we can fix critical issues fast.".into()),
- if TelemetrySettings::get_global(cx).diagnostics {
- ui::ToggleState::Selected
- } else {
- ui::ToggleState::Unselected
- },
- {
- let fs = fs.clone();
- move |selection, _, cx| {
- let enabled = match selection {
- ToggleState::Selected => true,
- ToggleState::Unselected => false,
- ToggleState::Indeterminate => { return; },
- };
-
- update_settings_file(
- fs.clone(),
- cx,
- move |setting, _| {
+ update_settings_file(fs.clone(), cx, move |setting, _| {
setting.telemetry.get_or_insert_default().diagnostics = Some(enabled);
- },
-
- );
-
- // This telemetry event shouldn't fire when it's off. If it does we'll be alerted
- // and can fix it in a timely manner to respect a user's choice.
- telemetry::event!("Welcome Page Telemetry Diagnostics Toggled",
- options = if enabled {
- "on"
- } else {
- "off"
- }
- );
- }
- }
- ).tab_index({
- *tab_index += 1;
- *tab_index
- }))
+ });
+
+ // This telemetry event shouldn't fire when it's off. If it does we'll be alerted
+ // and can fix it in a timely manner to respect a user's choice.
+ telemetry::event!(
+ "Welcome Page Telemetry Diagnostics Toggled",
+ options = if enabled { "on" } else { "off" }
+ );
+ }
+ },
+ )
+ .tab_index({
+ *tab_index += 1;
+ *tab_index
+ }),
+ )
}
fn render_base_keymap_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement {
@@ -380,8 +381,8 @@ fn render_vim_mode_switch(tab_index: &mut isize, cx: &mut App) -> impl IntoEleme
};
SwitchField::new(
"onboarding-vim-mode",
- "Vim Mode",
- Some("Coming from Neovim? Use our first-class implementation of Vim Mode.".into()),
+ Some("Vim Mode"),
+ Some("Coming from Neovim? Use our first-class implementation of Vim Mode".into()),
toggle_state,
{
let fs = ::global(cx);
@@ -410,12 +411,79 @@ fn render_vim_mode_switch(tab_index: &mut isize, cx: &mut App) -> impl IntoEleme
})
}
+fn render_setting_import_button(
+ tab_index: isize,
+ label: SharedString,
+ action: &dyn Action,
+ imported: bool,
+) -> impl IntoElement + 'static {
+ let action = action.boxed_clone();
+ h_flex().w_full().child(
+ ButtonLike::new(label.clone())
+ .style(ButtonStyle::OutlinedTransparent)
+ .selected_style(ButtonStyle::Tinted(TintColor::Accent))
+ .toggle_state(imported)
+ .size(ButtonSize::Medium)
+ .tab_index(tab_index)
+ .child(
+ h_flex()
+ .w_full()
+ .justify_between()
+ .when(imported, |this| {
+ this.child(Icon::new(IconName::Check).color(Color::Success))
+ })
+ .child(Label::new(label.clone()).mx_2().size(LabelSize::Small)),
+ )
+ .on_click(move |_, window, cx| {
+ telemetry::event!("Welcome Import Settings", import_source = label,);
+ window.dispatch_action(action.boxed_clone(), cx);
+ }),
+ )
+}
+
+fn render_import_settings_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement {
+ let import_state = SettingsImportState::global(cx);
+ let imports: [(SharedString, &dyn Action, bool); 2] = [
+ (
+ "VS Code".into(),
+ &ImportVsCodeSettings { skip_prompt: false },
+ import_state.vscode,
+ ),
+ (
+ "Cursor".into(),
+ &ImportCursorSettings { skip_prompt: false },
+ import_state.cursor,
+ ),
+ ];
+
+ let [vscode, cursor] = imports.map(|(label, action, imported)| {
+ *tab_index += 1;
+ render_setting_import_button(*tab_index - 1, label, action, imported)
+ });
+
+ h_flex()
+ .child(
+ v_flex()
+ .gap_0p5()
+ .max_w_5_6()
+ .child(Label::new("Import Settings"))
+ .child(
+ Label::new("Automatically pull your settings from other editors")
+ .color(Color::Muted),
+ ),
+ )
+ .child(div().w_full())
+ .child(h_flex().gap_1().child(vscode).child(cursor))
+}
+
pub(crate) fn render_basics_page(cx: &mut App) -> impl IntoElement {
let mut tab_index = 0;
v_flex()
+ .id("basics-page")
.gap_6()
.child(render_theme_section(&mut tab_index, cx))
.child(render_base_keymap_section(&mut tab_index, cx))
+ .child(render_import_settings_section(&mut tab_index, cx))
.child(render_vim_mode_switch(&mut tab_index, cx))
.child(render_telemetry_section(&mut tab_index, cx))
}
diff --git a/crates/onboarding/src/editing_page.rs b/crates/onboarding/src/editing_page.rs
deleted file mode 100644
index 4fd968faaff8ae60ca5f862dfd13d8a3562f4c89..0000000000000000000000000000000000000000
--- a/crates/onboarding/src/editing_page.rs
+++ /dev/null
@@ -1,611 +0,0 @@
-use std::sync::Arc;
-
-use editor::{EditorSettings, ShowMinimap};
-use fs::Fs;
-use gpui::{Action, App, FontFeatures, IntoElement, Pixels, SharedString, Window};
-use language::language_settings::{AllLanguageSettings, FormatOnSave};
-use project::project_settings::ProjectSettings;
-use settings::{Settings as _, update_settings_file};
-use theme::{FontFamilyName, ThemeSettings};
-use ui::{
- ButtonLike, PopoverMenu, SwitchField, ToggleButtonGroup, ToggleButtonGroupStyle,
- ToggleButtonSimple, ToggleState, Tooltip, prelude::*,
-};
-use ui_input::{NumberField, font_picker};
-
-use crate::{ImportCursorSettings, ImportVsCodeSettings, SettingsImportState};
-
-fn read_show_mini_map(cx: &App) -> ShowMinimap {
- editor::EditorSettings::get_global(cx).minimap.show
-}
-
-fn write_show_mini_map(show: ShowMinimap, cx: &mut App) {
- let fs = ::global(cx);
-
- // This is used to speed up the UI
- // the UI reads the current values to get what toggle state to show on buttons
- // there's a slight delay if we just call update_settings_file so we manually set
- // the value here then call update_settings file to get around the delay
- let mut curr_settings = EditorSettings::get_global(cx).clone();
- curr_settings.minimap.show = show;
- EditorSettings::override_global(curr_settings, cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- telemetry::event!(
- "Welcome Minimap Clicked",
- from = settings.editor.minimap.clone().unwrap_or_default(),
- to = show
- );
- settings.editor.minimap.get_or_insert_default().show = Some(show);
- });
-}
-
-fn read_inlay_hints(cx: &App) -> bool {
- AllLanguageSettings::get_global(cx)
- .defaults
- .inlay_hints
- .enabled
-}
-
-fn write_inlay_hints(enabled: bool, cx: &mut App) {
- let fs = ::global(cx);
-
- let mut curr_settings = AllLanguageSettings::get_global(cx).clone();
- curr_settings.defaults.inlay_hints.enabled = enabled;
- AllLanguageSettings::override_global(curr_settings, cx);
-
- update_settings_file(fs, cx, move |settings, _cx| {
- settings
- .project
- .all_languages
- .defaults
- .inlay_hints
- .get_or_insert_default()
- .enabled = Some(enabled);
- });
-}
-
-fn read_git_blame(cx: &App) -> bool {
- ProjectSettings::get_global(cx).git.inline_blame.enabled
-}
-
-fn write_git_blame(enabled: bool, cx: &mut App) {
- let fs = ::global(cx);
-
- let mut curr_settings = ProjectSettings::get_global(cx).clone();
- curr_settings.git.inline_blame.enabled = enabled;
- ProjectSettings::override_global(curr_settings, cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- settings
- .git
- .get_or_insert_default()
- .inline_blame
- .get_or_insert_default()
- .enabled = Some(enabled);
- });
-}
-
-fn write_ui_font_family(font: SharedString, cx: &mut App) {
- let fs = ::global(cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- telemetry::event!(
- "Welcome Font Changed",
- type = "ui font",
- old = settings.theme.ui_font_family,
- new = font
- );
- settings.theme.ui_font_family = Some(FontFamilyName(font.into()));
- });
-}
-
-fn write_ui_font_size(size: Pixels, cx: &mut App) {
- let fs = ::global(cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- settings.theme.ui_font_size = Some(size.into());
- });
-}
-
-fn write_buffer_font_size(size: Pixels, cx: &mut App) {
- let fs = ::global(cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- settings.theme.buffer_font_size = Some(size.into());
- });
-}
-
-fn write_buffer_font_family(font_family: SharedString, cx: &mut App) {
- let fs = ::global(cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- telemetry::event!(
- "Welcome Font Changed",
- type = "editor font",
- old = settings.theme.buffer_font_family,
- new = font_family
- );
-
- settings.theme.buffer_font_family = Some(FontFamilyName(font_family.into()));
- });
-}
-
-fn read_font_ligatures(cx: &App) -> bool {
- ThemeSettings::get_global(cx)
- .buffer_font
- .features
- .is_calt_enabled()
- .unwrap_or(true)
-}
-
-fn write_font_ligatures(enabled: bool, cx: &mut App) {
- let fs = ::global(cx);
- let bit = if enabled { 1 } else { 0 };
-
- update_settings_file(fs, cx, move |settings, _| {
- let mut features = settings
- .theme
- .buffer_font_features
- .as_mut()
- .map(|features| features.tag_value_list().to_vec())
- .unwrap_or_default();
-
- if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") {
- features[calt_index].1 = bit;
- } else {
- features.push(("calt".into(), bit));
- }
-
- settings.theme.buffer_font_features = Some(FontFeatures(Arc::new(features)));
- });
-}
-
-fn read_format_on_save(cx: &App) -> bool {
- match AllLanguageSettings::get_global(cx).defaults.format_on_save {
- FormatOnSave::On => true,
- FormatOnSave::Off => false,
- }
-}
-
-fn write_format_on_save(format_on_save: bool, cx: &mut App) {
- let fs = ::global(cx);
-
- update_settings_file(fs, cx, move |settings, _| {
- settings.project.all_languages.defaults.format_on_save = Some(match format_on_save {
- true => FormatOnSave::On,
- false => FormatOnSave::Off,
- });
- });
-}
-
-fn render_setting_import_button(
- tab_index: isize,
- label: SharedString,
- icon_name: IconName,
- action: &dyn Action,
- imported: bool,
-) -> impl IntoElement {
- let action = action.boxed_clone();
- h_flex().w_full().child(
- ButtonLike::new(label.clone())
- .full_width()
- .style(ButtonStyle::Outlined)
- .size(ButtonSize::Large)
- .tab_index(tab_index)
- .child(
- h_flex()
- .w_full()
- .justify_between()
- .child(
- h_flex()
- .gap_1p5()
- .px_1()
- .child(
- Icon::new(icon_name)
- .color(Color::Muted)
- .size(IconSize::XSmall),
- )
- .child(Label::new(label.clone())),
- )
- .when(imported, |this| {
- this.child(
- h_flex()
- .gap_1p5()
- .child(
- Icon::new(IconName::Check)
- .color(Color::Success)
- .size(IconSize::XSmall),
- )
- .child(Label::new("Imported").size(LabelSize::Small)),
- )
- }),
- )
- .on_click(move |_, window, cx| {
- telemetry::event!("Welcome Import Settings", import_source = label,);
- window.dispatch_action(action.boxed_clone(), cx);
- }),
- )
-}
-
-fn render_import_settings_section(tab_index: &mut isize, cx: &App) -> impl IntoElement {
- let import_state = SettingsImportState::global(cx);
- let imports: [(SharedString, IconName, &dyn Action, bool); 2] = [
- (
- "VS Code".into(),
- IconName::EditorVsCode,
- &ImportVsCodeSettings { skip_prompt: false },
- import_state.vscode,
- ),
- (
- "Cursor".into(),
- IconName::EditorCursor,
- &ImportCursorSettings { skip_prompt: false },
- import_state.cursor,
- ),
- ];
-
- let [vscode, cursor] = imports.map(|(label, icon_name, action, imported)| {
- *tab_index += 1;
- render_setting_import_button(*tab_index - 1, label, icon_name, action, imported)
- });
-
- v_flex()
- .gap_4()
- .child(
- v_flex()
- .child(Label::new("Import Settings").size(LabelSize::Large))
- .child(
- Label::new("Automatically pull your settings from other editors.")
- .color(Color::Muted),
- ),
- )
- .child(h_flex().w_full().gap_4().child(vscode).child(cursor))
-}
-
-fn render_font_customization_section(
- tab_index: &mut isize,
- window: &mut Window,
- cx: &mut App,
-) -> impl IntoElement {
- let theme_settings = ThemeSettings::get_global(cx);
- let ui_font_size = theme_settings.ui_font_size(cx);
- let ui_font_family = theme_settings.ui_font.family.clone();
- let buffer_font_family = theme_settings.buffer_font.family.clone();
- let buffer_font_size = theme_settings.buffer_font_size(cx);
-
- let ui_font_picker =
- cx.new(|cx| font_picker(ui_font_family.clone(), write_ui_font_family, window, cx));
-
- let buffer_font_picker = cx.new(|cx| {
- font_picker(
- buffer_font_family.clone(),
- write_buffer_font_family,
- window,
- cx,
- )
- });
-
- let ui_font_handle = ui::PopoverMenuHandle::default();
- let buffer_font_handle = ui::PopoverMenuHandle::default();
-
- h_flex()
- .w_full()
- .gap_4()
- .child(
- v_flex()
- .w_full()
- .gap_1()
- .child(Label::new("UI Font"))
- .child(
- h_flex()
- .w_full()
- .justify_between()
- .gap_2()
- .child(
- PopoverMenu::new("ui-font-picker")
- .menu({
- let ui_font_picker = ui_font_picker;
- move |_window, _cx| Some(ui_font_picker.clone())
- })
- .trigger(
- ButtonLike::new("ui-font-family-button")
- .style(ButtonStyle::Outlined)
- .size(ButtonSize::Medium)
- .full_width()
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- })
- .child(
- h_flex()
- .w_full()
- .justify_between()
- .child(Label::new(ui_font_family))
- .child(
- Icon::new(IconName::ChevronUpDown)
- .color(Color::Muted)
- .size(IconSize::XSmall),
- ),
- ),
- )
- .full_width(true)
- .anchor(gpui::Corner::TopLeft)
- .offset(gpui::Point {
- x: px(0.0),
- y: px(4.0),
- })
- .with_handle(ui_font_handle),
- )
- .child(font_picker_stepper(
- "ui-font-size",
- &ui_font_size,
- tab_index,
- write_ui_font_size,
- window,
- cx,
- )),
- ),
- )
- .child(
- v_flex()
- .w_full()
- .gap_1()
- .child(Label::new("Editor Font"))
- .child(
- h_flex()
- .w_full()
- .justify_between()
- .gap_2()
- .child(
- PopoverMenu::new("buffer-font-picker")
- .menu({
- let buffer_font_picker = buffer_font_picker;
- move |_window, _cx| Some(buffer_font_picker.clone())
- })
- .trigger(
- ButtonLike::new("buffer-font-family-button")
- .style(ButtonStyle::Outlined)
- .size(ButtonSize::Medium)
- .full_width()
- .tab_index({
- *tab_index += 1;
- *tab_index - 1
- })
- .child(
- h_flex()
- .w_full()
- .justify_between()
- .child(Label::new(buffer_font_family))
- .child(
- Icon::new(IconName::ChevronUpDown)
- .color(Color::Muted)
- .size(IconSize::XSmall),
- ),
- ),
- )
- .full_width(true)
- .anchor(gpui::Corner::TopLeft)
- .offset(gpui::Point {
- x: px(0.0),
- y: px(4.0),
- })
- .with_handle(buffer_font_handle),
- )
- .child(font_picker_stepper(
- "buffer-font-size",
- &buffer_font_size,
- tab_index,
- write_buffer_font_size,
- window,
- cx,
- )),
- ),
- )
-}
-
-fn font_picker_stepper(
- id: &'static str,
- font_size: &Pixels,
- tab_index: &mut isize,
- write_font_size: fn(Pixels, &mut App),
- window: &mut Window,
- cx: &mut App,
-) -> NumberField {
- window.with_id(id, |window| {
- let optimistic_font_size: gpui::Entity