onboarding: Wire up settings import (#35366)

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
This commit is contained in:
Ben Kunkle 2025-07-30 15:09:11 -05:00 committed by GitHub
parent cdce3b3620
commit afcb8f2a3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 147 additions and 129 deletions

5
Cargo.lock generated
View file

@ -10982,12 +10982,16 @@ dependencies = [
"gpui", "gpui",
"language", "language",
"project", "project",
"schemars",
"serde",
"settings", "settings",
"theme", "theme",
"ui", "ui",
"util",
"workspace", "workspace",
"workspace-hack", "workspace-hack",
"zed_actions", "zed_actions",
"zlog",
] ]
[[package]] [[package]]
@ -14773,7 +14777,6 @@ dependencies = [
"notifications", "notifications",
"paths", "paths",
"project", "project",
"schemars",
"search", "search",
"serde", "serde",
"serde_json", "serde_json",

View file

@ -24,9 +24,13 @@ fs.workspace = true
gpui.workspace = true gpui.workspace = true
language.workspace = true language.workspace = true
project.workspace = true project.workspace = true
schemars.workspace = true
serde.workspace = true
settings.workspace = true settings.workspace = true
theme.workspace = true theme.workspace = true
ui.workspace = true ui.workspace = true
util.workspace = true
workspace-hack.workspace = true workspace-hack.workspace = true
workspace.workspace = true workspace.workspace = true
zed_actions.workspace = true zed_actions.workspace = true
zlog.workspace = true

View file

@ -1,16 +1,19 @@
use editor::{EditorSettings, ShowMinimap}; use editor::{EditorSettings, ShowMinimap};
use fs::Fs; use fs::Fs;
use gpui::{App, IntoElement, Pixels, Window}; use gpui::{Action, App, IntoElement, Pixels, Window};
use language::language_settings::AllLanguageSettings; use language::language_settings::AllLanguageSettings;
use project::project_settings::ProjectSettings; use project::project_settings::ProjectSettings;
use settings::{Settings as _, update_settings_file}; use settings::{Settings as _, update_settings_file};
use theme::{FontFamilyCache, FontFamilyName, ThemeSettings}; use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
use ui::{ use ui::{
ContextMenu, DropdownMenu, IconButton, Label, LabelCommon, LabelSize, NumericStepper, Clickable, ContextMenu, DropdownMenu, IconButton, Label, LabelCommon, LabelSize,
ParentElement, SharedString, Styled, SwitchColor, SwitchField, ToggleButtonGroup, NumericStepper, ParentElement, SharedString, Styled, SwitchColor, SwitchField,
ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, div, h_flex, px, v_flex, ToggleButtonGroup, ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, div, h_flex, px,
v_flex,
}; };
use crate::{ImportCursorSettings, ImportVsCodeSettings};
fn read_show_mini_map(cx: &App) -> ShowMinimap { fn read_show_mini_map(cx: &App) -> ShowMinimap {
editor::EditorSettings::get_global(cx).minimap.show 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( .child(
h_flex() h_flex()
.child(IconButton::new( .child(
"import-vs-code-settings", IconButton::new("import-vs-code-settings", ui::IconName::Code).on_click(
ui::IconName::Code, |_, window, cx| {
)) window
.child(IconButton::new( .dispatch_action(ImportVsCodeSettings::default().boxed_clone(), cx)
"import-cursor-settings", },
ui::IconName::CursorIBeam, ),
)), )
.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(Label::new("Popular Settings").size(LabelSize::Large))
.child( .child(

View file

@ -4,10 +4,13 @@ use db::kvp::KEY_VALUE_STORE;
use feature_flags::{FeatureFlag, FeatureFlagViewExt as _}; use feature_flags::{FeatureFlag, FeatureFlagViewExt as _};
use fs::Fs; use fs::Fs;
use gpui::{ use gpui::{
AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, Action, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity, EventEmitter,
IntoElement, Render, SharedString, Subscription, Task, WeakEntity, Window, actions, 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 std::sync::Arc;
use theme::{ThemeMode, ThemeSettings}; use theme::{ThemeMode, ThemeSettings};
use ui::{ use ui::{
@ -30,6 +33,24 @@ impl FeatureFlag for OnBoardingFeatureFlag {
const NAME: &'static str = "onboarding"; 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"; pub const FIRST_OPEN: &str = "first_open";
actions!( 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 = <dyn 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 = <dyn 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::<Workspace>(|_, window, cx| { cx.observe_new::<Workspace>(|_, window, cx| {
let Some(window) = window else { let Some(window) = window else {
return; return;
@ -371,3 +429,54 @@ impl Item for Onboarding {
f(*event) f(*event)
} }
} }
pub async fn handle_import_vscode_settings(
source: VsCodeSettingsSource,
skip_prompt: bool,
fs: Arc<dyn Fs>,
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::<SettingsStore>()
.import_vscode_settings(fs, vscode_settings);
zlog::info!("Imported {source} settings from {}", path.display());
})
.ok();
}

View file

@ -532,7 +532,9 @@ impl SettingsStore {
})) }))
.ok(); .ok();
} }
}
impl SettingsStore {
/// Updates the value of a setting in a JSON file, returning the new text /// Updates the value of a setting in a JSON file, returning the new text
/// for that JSON file. /// for that JSON file.
pub fn new_text_for_update<T: Settings>( pub fn new_text_for_update<T: Settings>(

View file

@ -30,7 +30,6 @@ menu.workspace = true
notifications.workspace = true notifications.workspace = true
paths.workspace = true paths.workspace = true
project.workspace = true project.workspace = true
schemars.workspace = true
search.workspace = true search.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true

View file

@ -1,20 +1,12 @@
mod appearance_settings_controls; mod appearance_settings_controls;
use std::any::TypeId; use std::any::TypeId;
use std::sync::Arc;
use command_palette_hooks::CommandPaletteFilter; use command_palette_hooks::CommandPaletteFilter;
use editor::EditorSettingsControls; use editor::EditorSettingsControls;
use feature_flags::{FeatureFlag, FeatureFlagViewExt}; use feature_flags::{FeatureFlag, FeatureFlagViewExt};
use fs::Fs; use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, actions};
use gpui::{
Action, App, AsyncWindowContext, Entity, EventEmitter, FocusHandle, Focusable, Task, actions,
};
use schemars::JsonSchema;
use serde::Deserialize;
use settings::{SettingsStore, VsCodeSettingsSource};
use ui::prelude::*; use ui::prelude::*;
use util::truncate_and_remove_front;
use workspace::item::{Item, ItemEvent}; use workspace::item::{Item, ItemEvent};
use workspace::{Workspace, with_active_or_new_workspace}; use workspace::{Workspace, with_active_or_new_workspace};
@ -29,23 +21,6 @@ impl FeatureFlag for SettingsUiFeatureFlag {
const NAME: &'static str = "settings-ui"; 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!( actions!(
zed, 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 { let Some(window) = window else {
return; return;
}; };
workspace.register_action(|_workspace, action: &ImportVsCodeSettings, window, cx| {
let fs = <dyn 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 = <dyn 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::<OpenSettingsEditor>()]; let settings_ui_actions = [TypeId::of::<OpenSettingsEditor>()];
CommandPaletteFilter::update_global(cx, |filter, _cx| { CommandPaletteFilter::update_global(cx, |filter, _cx| {
@ -138,57 +79,6 @@ pub fn init(cx: &mut App) {
keybindings::init(cx); keybindings::init(cx);
} }
async fn handle_import_vscode_settings(
source: VsCodeSettingsSource,
skip_prompt: bool,
fs: Arc<dyn Fs>,
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::<SettingsStore>()
.import_vscode_settings(fs, vscode_settings);
log::info!("Imported {source} settings from {}", path.display());
})
.ok();
}
pub struct SettingsPage { pub struct SettingsPage {
focus_handle: FocusHandle, focus_handle: FocusHandle,
} }