From afcb8f2a3fd618fe8238737b7f673103dfcb9c73 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Wed, 30 Jul 2025 15:09:11 -0500 Subject: [PATCH] onboarding: Wire up settings import (#35366) Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ... --- Cargo.lock | 5 +- crates/onboarding/Cargo.toml | 4 + crates/onboarding/src/editing_page.rs | 35 +++++--- crates/onboarding/src/onboarding.rs | 115 +++++++++++++++++++++++++- crates/settings/src/settings_store.rs | 2 + crates/settings_ui/Cargo.toml | 1 - crates/settings_ui/src/settings_ui.rs | 114 +------------------------ 7 files changed, 147 insertions(+), 129 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61553e7799..ca9ecd0685 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10982,12 +10982,16 @@ dependencies = [ "gpui", "language", "project", + "schemars", + "serde", "settings", "theme", "ui", + "util", "workspace", "workspace-hack", "zed_actions", + "zlog", ] [[package]] @@ -14773,7 +14777,6 @@ dependencies = [ "notifications", "paths", "project", - "schemars", "search", "serde", "serde_json", diff --git a/crates/onboarding/Cargo.toml b/crates/onboarding/Cargo.toml index da009b4e4e..04c9fce1dc 100644 --- a/crates/onboarding/Cargo.toml +++ b/crates/onboarding/Cargo.toml @@ -24,9 +24,13 @@ fs.workspace = true gpui.workspace = true language.workspace = true project.workspace = true +schemars.workspace = true +serde.workspace = true settings.workspace = true theme.workspace = true ui.workspace = true +util.workspace = true workspace-hack.workspace = true workspace.workspace = true zed_actions.workspace = true +zlog.workspace = true diff --git a/crates/onboarding/src/editing_page.rs b/crates/onboarding/src/editing_page.rs index c07d8fef4d..c6f7928397 100644 --- a/crates/onboarding/src/editing_page.rs +++ b/crates/onboarding/src/editing_page.rs @@ -1,16 +1,19 @@ use editor::{EditorSettings, ShowMinimap}; use fs::Fs; -use gpui::{App, IntoElement, Pixels, Window}; +use gpui::{Action, App, IntoElement, Pixels, Window}; use language::language_settings::AllLanguageSettings; use project::project_settings::ProjectSettings; use settings::{Settings as _, update_settings_file}; use theme::{FontFamilyCache, FontFamilyName, ThemeSettings}; use ui::{ - ContextMenu, DropdownMenu, IconButton, Label, LabelCommon, LabelSize, NumericStepper, - ParentElement, SharedString, Styled, SwitchColor, SwitchField, ToggleButtonGroup, - ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, div, h_flex, px, v_flex, + Clickable, ContextMenu, DropdownMenu, IconButton, Label, LabelCommon, LabelSize, + NumericStepper, ParentElement, SharedString, Styled, SwitchColor, SwitchField, + ToggleButtonGroup, ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, div, h_flex, px, + v_flex, }; +use crate::{ImportCursorSettings, ImportVsCodeSettings}; + fn read_show_mini_map(cx: &App) -> ShowMinimap { editor::EditorSettings::get_global(cx).minimap.show } @@ -110,14 +113,22 @@ pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl Int ) .child( h_flex() - .child(IconButton::new( - "import-vs-code-settings", - ui::IconName::Code, - )) - .child(IconButton::new( - "import-cursor-settings", - ui::IconName::CursorIBeam, - )), + .child( + IconButton::new("import-vs-code-settings", ui::IconName::Code).on_click( + |_, window, cx| { + window + .dispatch_action(ImportVsCodeSettings::default().boxed_clone(), cx) + }, + ), + ) + .child( + IconButton::new("import-cursor-settings", ui::IconName::CursorIBeam).on_click( + |_, window, cx| { + window + .dispatch_action(ImportCursorSettings::default().boxed_clone(), cx) + }, + ), + ), ) .child(Label::new("Popular Settings").size(LabelSize::Large)) .child( diff --git a/crates/onboarding/src/onboarding.rs b/crates/onboarding/src/onboarding.rs index f1b29c205c..e886177854 100644 --- a/crates/onboarding/src/onboarding.rs +++ b/crates/onboarding/src/onboarding.rs @@ -4,10 +4,13 @@ use db::kvp::KEY_VALUE_STORE; use feature_flags::{FeatureFlag, FeatureFlagViewExt as _}; use fs::Fs; use gpui::{ - AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, - IntoElement, Render, SharedString, Subscription, Task, WeakEntity, Window, actions, + Action, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity, EventEmitter, + FocusHandle, Focusable, IntoElement, Render, SharedString, Subscription, Task, WeakEntity, + Window, actions, }; -use settings::{Settings, SettingsStore, update_settings_file}; +use schemars::JsonSchema; +use serde::Deserialize; +use settings::{Settings, SettingsStore, VsCodeSettingsSource, update_settings_file}; use std::sync::Arc; use theme::{ThemeMode, ThemeSettings}; use ui::{ @@ -30,6 +33,24 @@ impl FeatureFlag for OnBoardingFeatureFlag { const NAME: &'static str = "onboarding"; } +/// Imports settings from Visual Studio Code. +#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] +#[serde(deny_unknown_fields)] +pub struct ImportVsCodeSettings { + #[serde(default)] + pub skip_prompt: bool, +} + +/// Imports settings from Cursor editor. +#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] +#[serde(deny_unknown_fields)] +pub struct ImportCursorSettings { + #[serde(default)] + pub skip_prompt: bool, +} + pub const FIRST_OPEN: &str = "first_open"; actions!( @@ -95,6 +116,43 @@ pub fn init(cx: &mut App) { }); }); + cx.observe_new(|workspace: &mut Workspace, _window, _cx| { + workspace.register_action(|_workspace, action: &ImportVsCodeSettings, window, cx| { + let fs = ::global(cx); + let action = *action; + + window + .spawn(cx, async move |cx: &mut AsyncWindowContext| { + handle_import_vscode_settings( + VsCodeSettingsSource::VsCode, + action.skip_prompt, + fs, + cx, + ) + .await + }) + .detach(); + }); + + workspace.register_action(|_workspace, action: &ImportCursorSettings, window, cx| { + let fs = ::global(cx); + let action = *action; + + window + .spawn(cx, async move |cx: &mut AsyncWindowContext| { + handle_import_vscode_settings( + VsCodeSettingsSource::Cursor, + action.skip_prompt, + fs, + cx, + ) + .await + }) + .detach(); + }); + }) + .detach(); + cx.observe_new::(|_, window, cx| { let Some(window) = window else { return; @@ -371,3 +429,54 @@ impl Item for Onboarding { f(*event) } } + +pub async fn handle_import_vscode_settings( + source: VsCodeSettingsSource, + skip_prompt: bool, + fs: Arc, + cx: &mut AsyncWindowContext, +) { + use util::truncate_and_remove_front; + + let vscode_settings = + match settings::VsCodeSettings::load_user_settings(source, fs.clone()).await { + Ok(vscode_settings) => vscode_settings, + Err(err) => { + zlog::error!("{err}"); + let _ = cx.prompt( + gpui::PromptLevel::Info, + &format!("Could not find or load a {source} settings file"), + None, + &["Ok"], + ); + return; + } + }; + + if !skip_prompt { + let prompt = cx.prompt( + gpui::PromptLevel::Warning, + &format!( + "Importing {} settings may overwrite your existing settings. \ + Will import settings from {}", + vscode_settings.source, + truncate_and_remove_front(&vscode_settings.path.to_string_lossy(), 128), + ), + None, + &["Ok", "Cancel"], + ); + let result = cx.spawn(async move |_| prompt.await.ok()).await; + if result != Some(0) { + return; + } + }; + + cx.update(|_, cx| { + let source = vscode_settings.source; + let path = vscode_settings.path.clone(); + cx.global::() + .import_vscode_settings(fs, vscode_settings); + zlog::info!("Imported {source} settings from {}", path.display()); + }) + .ok(); +} diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 0d23385a68..7eb46f03f5 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -532,7 +532,9 @@ impl SettingsStore { })) .ok(); } +} +impl SettingsStore { /// Updates the value of a setting in a JSON file, returning the new text /// for that JSON file. pub fn new_text_for_update( diff --git a/crates/settings_ui/Cargo.toml b/crates/settings_ui/Cargo.toml index e8434c1a32..a4c47081c6 100644 --- a/crates/settings_ui/Cargo.toml +++ b/crates/settings_ui/Cargo.toml @@ -30,7 +30,6 @@ menu.workspace = true notifications.workspace = true paths.workspace = true project.workspace = true -schemars.workspace = true search.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 2f0abb4789..3022cc7142 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -1,20 +1,12 @@ mod appearance_settings_controls; use std::any::TypeId; -use std::sync::Arc; use command_palette_hooks::CommandPaletteFilter; use editor::EditorSettingsControls; use feature_flags::{FeatureFlag, FeatureFlagViewExt}; -use fs::Fs; -use gpui::{ - Action, App, AsyncWindowContext, Entity, EventEmitter, FocusHandle, Focusable, Task, actions, -}; -use schemars::JsonSchema; -use serde::Deserialize; -use settings::{SettingsStore, VsCodeSettingsSource}; +use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, actions}; use ui::prelude::*; -use util::truncate_and_remove_front; use workspace::item::{Item, ItemEvent}; use workspace::{Workspace, with_active_or_new_workspace}; @@ -29,23 +21,6 @@ impl FeatureFlag for SettingsUiFeatureFlag { const NAME: &'static str = "settings-ui"; } -/// Imports settings from Visual Studio Code. -#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)] -#[action(namespace = zed)] -#[serde(deny_unknown_fields)] -pub struct ImportVsCodeSettings { - #[serde(default)] - pub skip_prompt: bool, -} - -/// Imports settings from Cursor editor. -#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)] -#[action(namespace = zed)] -#[serde(deny_unknown_fields)] -pub struct ImportCursorSettings { - #[serde(default)] - pub skip_prompt: bool, -} actions!( zed, [ @@ -72,45 +47,11 @@ pub fn init(cx: &mut App) { }); }); - cx.observe_new(|workspace: &mut Workspace, window, cx| { + cx.observe_new(|_workspace: &mut Workspace, window, cx| { let Some(window) = window else { return; }; - workspace.register_action(|_workspace, action: &ImportVsCodeSettings, window, cx| { - let fs = ::global(cx); - let action = *action; - - window - .spawn(cx, async move |cx: &mut AsyncWindowContext| { - handle_import_vscode_settings( - VsCodeSettingsSource::VsCode, - action.skip_prompt, - fs, - cx, - ) - .await - }) - .detach(); - }); - - workspace.register_action(|_workspace, action: &ImportCursorSettings, window, cx| { - let fs = ::global(cx); - let action = *action; - - window - .spawn(cx, async move |cx: &mut AsyncWindowContext| { - handle_import_vscode_settings( - VsCodeSettingsSource::Cursor, - action.skip_prompt, - fs, - cx, - ) - .await - }) - .detach(); - }); - let settings_ui_actions = [TypeId::of::()]; CommandPaletteFilter::update_global(cx, |filter, _cx| { @@ -138,57 +79,6 @@ pub fn init(cx: &mut App) { keybindings::init(cx); } -async fn handle_import_vscode_settings( - source: VsCodeSettingsSource, - skip_prompt: bool, - fs: Arc, - cx: &mut AsyncWindowContext, -) { - let vscode_settings = - match settings::VsCodeSettings::load_user_settings(source, fs.clone()).await { - Ok(vscode_settings) => vscode_settings, - Err(err) => { - log::error!("{err}"); - let _ = cx.prompt( - gpui::PromptLevel::Info, - &format!("Could not find or load a {source} settings file"), - None, - &["Ok"], - ); - return; - } - }; - - let prompt = if skip_prompt { - Task::ready(Some(0)) - } else { - let prompt = cx.prompt( - gpui::PromptLevel::Warning, - &format!( - "Importing {} settings may overwrite your existing settings. \ - Will import settings from {}", - vscode_settings.source, - truncate_and_remove_front(&vscode_settings.path.to_string_lossy(), 128), - ), - None, - &["Ok", "Cancel"], - ); - cx.spawn(async move |_| prompt.await.ok()) - }; - if prompt.await != Some(0) { - return; - } - - cx.update(|_, cx| { - let source = vscode_settings.source; - let path = vscode_settings.path.clone(); - cx.global::() - .import_vscode_settings(fs, vscode_settings); - log::info!("Imported {source} settings from {}", path.display()); - }) - .ok(); -} - pub struct SettingsPage { focus_handle: FocusHandle, }