1mod appearance_settings_controls;
2
3use std::any::TypeId;
4use std::sync::Arc;
5
6use command_palette_hooks::CommandPaletteFilter;
7use editor::EditorSettingsControls;
8use feature_flags::{FeatureFlag, FeatureFlagViewExt};
9use fs::Fs;
10use gpui::{
11 Action, App, AsyncWindowContext, Entity, EventEmitter, FocusHandle, Focusable, Task, actions,
12};
13use schemars::JsonSchema;
14use serde::Deserialize;
15use settings::{SettingsStore, VsCodeSettingsSource};
16use ui::prelude::*;
17use util::truncate_and_remove_front;
18use workspace::item::{Item, ItemEvent};
19use workspace::{Workspace, with_active_or_new_workspace};
20
21use crate::appearance_settings_controls::AppearanceSettingsControls;
22
23pub mod keybindings;
24pub mod ui_components;
25
26pub struct SettingsUiFeatureFlag;
27
28impl FeatureFlag for SettingsUiFeatureFlag {
29 const NAME: &'static str = "settings-ui";
30}
31
32#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)]
33#[action(namespace = zed)]
34pub struct ImportVsCodeSettings {
35 #[serde(default)]
36 pub skip_prompt: bool,
37}
38
39#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)]
40#[action(namespace = zed)]
41pub struct ImportCursorSettings {
42 #[serde(default)]
43 pub skip_prompt: bool,
44}
45actions!(zed, [OpenSettingsEditor]);
46
47pub fn init(cx: &mut App) {
48 cx.on_action(|_: &OpenSettingsEditor, cx| {
49 with_active_or_new_workspace(cx, move |workspace, window, cx| {
50 let existing = workspace
51 .active_pane()
52 .read(cx)
53 .items()
54 .find_map(|item| item.downcast::<SettingsPage>());
55
56 if let Some(existing) = existing {
57 workspace.activate_item(&existing, true, true, window, cx);
58 } else {
59 let settings_page = SettingsPage::new(workspace, cx);
60 workspace.add_item_to_active_pane(Box::new(settings_page), None, true, window, cx)
61 }
62 });
63 });
64
65 cx.observe_new(|workspace: &mut Workspace, window, cx| {
66 let Some(window) = window else {
67 return;
68 };
69
70 workspace.register_action(|_workspace, action: &ImportVsCodeSettings, window, cx| {
71 let fs = <dyn Fs>::global(cx);
72 let action = *action;
73
74 window
75 .spawn(cx, async move |cx: &mut AsyncWindowContext| {
76 handle_import_vscode_settings(
77 VsCodeSettingsSource::VsCode,
78 action.skip_prompt,
79 fs,
80 cx,
81 )
82 .await
83 })
84 .detach();
85 });
86
87 workspace.register_action(|_workspace, action: &ImportCursorSettings, window, cx| {
88 let fs = <dyn Fs>::global(cx);
89 let action = *action;
90
91 window
92 .spawn(cx, async move |cx: &mut AsyncWindowContext| {
93 handle_import_vscode_settings(
94 VsCodeSettingsSource::Cursor,
95 action.skip_prompt,
96 fs,
97 cx,
98 )
99 .await
100 })
101 .detach();
102 });
103
104 let settings_ui_actions = [TypeId::of::<OpenSettingsEditor>()];
105
106 CommandPaletteFilter::update_global(cx, |filter, _cx| {
107 filter.hide_action_types(&settings_ui_actions);
108 });
109
110 cx.observe_flag::<SettingsUiFeatureFlag, _>(
111 window,
112 move |is_enabled, _workspace, _, cx| {
113 if is_enabled {
114 CommandPaletteFilter::update_global(cx, |filter, _cx| {
115 filter.show_action_types(settings_ui_actions.iter());
116 });
117 } else {
118 CommandPaletteFilter::update_global(cx, |filter, _cx| {
119 filter.hide_action_types(&settings_ui_actions);
120 });
121 }
122 },
123 )
124 .detach();
125 })
126 .detach();
127
128 keybindings::init(cx);
129}
130
131async fn handle_import_vscode_settings(
132 source: VsCodeSettingsSource,
133 skip_prompt: bool,
134 fs: Arc<dyn Fs>,
135 cx: &mut AsyncWindowContext,
136) {
137 let vscode_settings =
138 match settings::VsCodeSettings::load_user_settings(source, fs.clone()).await {
139 Ok(vscode_settings) => vscode_settings,
140 Err(err) => {
141 log::error!("{err}");
142 let _ = cx.prompt(
143 gpui::PromptLevel::Info,
144 &format!("Could not find or load a {source} settings file"),
145 None,
146 &["Ok"],
147 );
148 return;
149 }
150 };
151
152 let prompt = if skip_prompt {
153 Task::ready(Some(0))
154 } else {
155 let prompt = cx.prompt(
156 gpui::PromptLevel::Warning,
157 &format!(
158 "Importing {} settings may overwrite your existing settings. \
159 Will import settings from {}",
160 vscode_settings.source,
161 truncate_and_remove_front(&vscode_settings.path.to_string_lossy(), 128),
162 ),
163 None,
164 &["Ok", "Cancel"],
165 );
166 cx.spawn(async move |_| prompt.await.ok())
167 };
168 if prompt.await != Some(0) {
169 return;
170 }
171
172 cx.update(|_, cx| {
173 let source = vscode_settings.source;
174 let path = vscode_settings.path.clone();
175 cx.global::<SettingsStore>()
176 .import_vscode_settings(fs, vscode_settings);
177 log::info!("Imported {source} settings from {}", path.display());
178 })
179 .ok();
180}
181
182pub struct SettingsPage {
183 focus_handle: FocusHandle,
184}
185
186impl SettingsPage {
187 pub fn new(_workspace: &Workspace, cx: &mut Context<Workspace>) -> Entity<Self> {
188 cx.new(|cx| Self {
189 focus_handle: cx.focus_handle(),
190 })
191 }
192}
193
194impl EventEmitter<ItemEvent> for SettingsPage {}
195
196impl Focusable for SettingsPage {
197 fn focus_handle(&self, _cx: &App) -> FocusHandle {
198 self.focus_handle.clone()
199 }
200}
201
202impl Item for SettingsPage {
203 type Event = ItemEvent;
204
205 fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
206 Some(Icon::new(IconName::Settings))
207 }
208
209 fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
210 "Settings".into()
211 }
212
213 fn show_toolbar(&self) -> bool {
214 false
215 }
216
217 fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
218 f(*event)
219 }
220}
221
222impl Render for SettingsPage {
223 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
224 v_flex()
225 .p_4()
226 .size_full()
227 .gap_4()
228 .child(Label::new("Settings").size(LabelSize::Large))
229 .child(
230 v_flex().gap_1().child(Label::new("Appearance")).child(
231 v_flex()
232 .elevation_2(cx)
233 .child(AppearanceSettingsControls::new()),
234 ),
235 )
236 .child(
237 v_flex().gap_1().child(Label::new("Editor")).child(
238 v_flex()
239 .elevation_2(cx)
240 .child(EditorSettingsControls::new()),
241 ),
242 )
243 }
244}