1use anyhow::{Context as _, Result};
2use collections::{BTreeMap, HashMap, btree_map, hash_map};
3use ec4rs::{ConfigParser, PropertiesSource, Section};
4use fs::Fs;
5use futures::{
6 FutureExt, StreamExt,
7 channel::{mpsc, oneshot},
8 future::LocalBoxFuture,
9};
10use gpui::{App, AsyncApp, BorrowAppContext, Global, SharedString, Task, UpdateGlobal};
11
12use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name};
13use schemars::JsonSchema;
14use serde::{Serialize, de::DeserializeOwned};
15use serde_json::{Value, json};
16use smallvec::SmallVec;
17use std::{
18 any::{Any, TypeId, type_name},
19 env,
20 fmt::Debug,
21 ops::Range,
22 path::{Path, PathBuf},
23 str::{self, FromStr},
24 sync::Arc,
25};
26use util::{
27 ResultExt as _, merge_non_null_json_value_into,
28 schemars::{DefaultDenyUnknownFields, add_new_subschema},
29};
30
31pub type EditorconfigProperties = ec4rs::Properties;
32
33use crate::{
34 ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, SettingsUiEntry,
35 VsCodeSettings, WorktreeId, parse_json_with_comments, replace_value_in_json_text,
36 settings_ui_core::SettingsUi, update_value_in_json_text,
37};
38
39/// A value that can be defined as a user setting.
40///
41/// Settings can be loaded from a combination of multiple JSON files.
42pub trait Settings: 'static + Send + Sync {
43 /// The name of a key within the JSON file from which this setting should
44 /// be deserialized. If this is `None`, then the setting will be deserialized
45 /// from the root object.
46 const KEY: Option<&'static str>;
47
48 const FALLBACK_KEY: Option<&'static str> = None;
49
50 /// The name of the keys in the [`FileContent`](Self::FileContent) that should
51 /// always be written to a settings file, even if their value matches the default
52 /// value.
53 ///
54 /// This is useful for tagged [`FileContent`](Self::FileContent)s where the tag
55 /// is a "version" field that should always be persisted, even if the current
56 /// user settings match the current version of the settings.
57 const PRESERVED_KEYS: Option<&'static [&'static str]> = None;
58
59 /// The type that is stored in an individual JSON file.
60 type FileContent: Clone + Default + Serialize + DeserializeOwned + JsonSchema + SettingsUi;
61
62 /// The logic for combining together values from one or more JSON files into the
63 /// final value for this setting.
64 ///
65 /// # Warning
66 /// `Self::FileContent` deserialized field names should match with `Self` deserialized field names
67 /// otherwise the field won't be deserialized properly and you will get the error:
68 /// "A default setting must be added to the `default.json` file"
69 fn load(sources: SettingsSources<Self::FileContent>, cx: &mut App) -> Result<Self>
70 where
71 Self: Sized;
72
73 fn missing_default() -> anyhow::Error {
74 anyhow::anyhow!("missing default")
75 }
76
77 /// Use [the helpers in the vscode_import module](crate::vscode_import) to apply known
78 /// equivalent settings from a vscode config to our config
79 fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent);
80
81 #[track_caller]
82 fn register(cx: &mut App)
83 where
84 Self: Sized,
85 {
86 SettingsStore::update_global(cx, |store, cx| {
87 store.register_setting::<Self>(cx);
88 });
89 }
90
91 #[track_caller]
92 fn get<'a>(path: Option<SettingsLocation>, cx: &'a App) -> &'a Self
93 where
94 Self: Sized,
95 {
96 cx.global::<SettingsStore>().get(path)
97 }
98
99 #[track_caller]
100 fn get_global(cx: &App) -> &Self
101 where
102 Self: Sized,
103 {
104 cx.global::<SettingsStore>().get(None)
105 }
106
107 #[track_caller]
108 fn try_get(cx: &App) -> Option<&Self>
109 where
110 Self: Sized,
111 {
112 if cx.has_global::<SettingsStore>() {
113 cx.global::<SettingsStore>().try_get(None)
114 } else {
115 None
116 }
117 }
118
119 #[track_caller]
120 fn try_read_global<R>(cx: &AsyncApp, f: impl FnOnce(&Self) -> R) -> Option<R>
121 where
122 Self: Sized,
123 {
124 cx.try_read_global(|s: &SettingsStore, _| f(s.get(None)))
125 }
126
127 #[track_caller]
128 fn override_global(settings: Self, cx: &mut App)
129 where
130 Self: Sized,
131 {
132 cx.global_mut::<SettingsStore>().override_global(settings)
133 }
134}
135
136#[derive(Clone, Copy, Debug)]
137pub struct SettingsSources<'a, T> {
138 /// The default Zed settings.
139 pub default: &'a T,
140 /// Global settings (loaded before user settings).
141 pub global: Option<&'a T>,
142 /// Settings provided by extensions.
143 pub extensions: Option<&'a T>,
144 /// The user settings.
145 pub user: Option<&'a T>,
146 /// The user settings for the current release channel.
147 pub release_channel: Option<&'a T>,
148 /// The user settings for the current operating system.
149 pub operating_system: Option<&'a T>,
150 /// The settings associated with an enabled settings profile
151 pub profile: Option<&'a T>,
152 /// The server's settings.
153 pub server: Option<&'a T>,
154 /// The project settings, ordered from least specific to most specific.
155 pub project: &'a [&'a T],
156}
157
158impl<'a, T: Serialize> SettingsSources<'a, T> {
159 /// Returns an iterator over the default settings as well as all settings customizations.
160 pub fn defaults_and_customizations(&self) -> impl Iterator<Item = &T> {
161 [self.default].into_iter().chain(self.customizations())
162 }
163
164 /// Returns an iterator over all of the settings customizations.
165 pub fn customizations(&self) -> impl Iterator<Item = &T> {
166 self.global
167 .into_iter()
168 .chain(self.extensions)
169 .chain(self.user)
170 .chain(self.release_channel)
171 .chain(self.operating_system)
172 .chain(self.profile)
173 .chain(self.server)
174 .chain(self.project.iter().copied())
175 }
176
177 /// Returns the settings after performing a JSON merge of the provided customizations.
178 ///
179 /// Customizations later in the iterator win out over the earlier ones.
180 pub fn json_merge_with<O: DeserializeOwned>(
181 customizations: impl Iterator<Item = &'a T>,
182 ) -> Result<O> {
183 let mut merged = Value::Null;
184 for value in customizations {
185 merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged);
186 }
187 Ok(serde_json::from_value(merged)?)
188 }
189
190 /// Returns the settings after performing a JSON merge of the customizations into the
191 /// default settings.
192 ///
193 /// More-specific customizations win out over the less-specific ones.
194 pub fn json_merge<O: DeserializeOwned>(&'a self) -> Result<O> {
195 Self::json_merge_with(self.defaults_and_customizations())
196 }
197}
198
199#[derive(Clone, Copy, Debug)]
200pub struct SettingsLocation<'a> {
201 pub worktree_id: WorktreeId,
202 pub path: &'a Path,
203}
204
205/// A set of strongly-typed setting values defined via multiple config files.
206pub struct SettingsStore {
207 setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
208 raw_default_settings: Value,
209 raw_global_settings: Option<Value>,
210 raw_user_settings: Value,
211 raw_server_settings: Option<Value>,
212 raw_extension_settings: Value,
213 raw_local_settings: BTreeMap<(WorktreeId, Arc<Path>), Value>,
214 raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc<Path>), (String, Option<Editorconfig>)>,
215 tab_size_callback: Option<(
216 TypeId,
217 Box<dyn Fn(&dyn Any) -> Option<usize> + Send + Sync + 'static>,
218 )>,
219 _setting_file_updates: Task<()>,
220 setting_file_updates_tx:
221 mpsc::UnboundedSender<Box<dyn FnOnce(AsyncApp) -> LocalBoxFuture<'static, Result<()>>>>,
222}
223
224#[derive(Clone)]
225pub struct Editorconfig {
226 pub is_root: bool,
227 pub sections: SmallVec<[Section; 5]>,
228}
229
230impl FromStr for Editorconfig {
231 type Err = anyhow::Error;
232
233 fn from_str(contents: &str) -> Result<Self, Self::Err> {
234 let parser = ConfigParser::new_buffered(contents.as_bytes())
235 .context("creating editorconfig parser")?;
236 let is_root = parser.is_root;
237 let sections = parser
238 .collect::<Result<SmallVec<_>, _>>()
239 .context("parsing editorconfig sections")?;
240 Ok(Self { is_root, sections })
241 }
242}
243
244#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
245pub enum LocalSettingsKind {
246 Settings,
247 Tasks,
248 Editorconfig,
249 Debug,
250}
251
252impl Global for SettingsStore {}
253
254#[derive(Debug)]
255struct SettingValue<T> {
256 global_value: Option<T>,
257 local_values: Vec<(WorktreeId, Arc<Path>, T)>,
258}
259
260trait AnySettingValue: 'static + Send + Sync {
261 fn key(&self) -> Option<&'static str>;
262 fn setting_type_name(&self) -> &'static str;
263 fn deserialize_setting(&self, json: &Value) -> Result<DeserializedSetting> {
264 self.deserialize_setting_with_key(json).1
265 }
266 fn deserialize_setting_with_key(
267 &self,
268 json: &Value,
269 ) -> (Option<&'static str>, Result<DeserializedSetting>);
270 fn load_setting(
271 &self,
272 sources: SettingsSources<DeserializedSetting>,
273 cx: &mut App,
274 ) -> Result<Box<dyn Any>>;
275 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any;
276 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)>;
277 fn set_global_value(&mut self, value: Box<dyn Any>);
278 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>);
279 fn json_schema(&self, generator: &mut schemars::SchemaGenerator) -> schemars::Schema;
280 fn edits_for_update(
281 &self,
282 raw_settings: &serde_json::Value,
283 tab_size: usize,
284 vscode_settings: &VsCodeSettings,
285 text: &mut String,
286 edits: &mut Vec<(Range<usize>, String)>,
287 );
288 fn settings_ui_item(&self) -> SettingsUiEntry;
289}
290
291struct DeserializedSetting(Box<dyn Any>);
292
293impl SettingsStore {
294 pub fn new(cx: &App) -> Self {
295 let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();
296 Self {
297 setting_values: Default::default(),
298 raw_default_settings: json!({}),
299 raw_global_settings: None,
300 raw_user_settings: json!({}),
301 raw_server_settings: None,
302 raw_extension_settings: json!({}),
303 raw_local_settings: Default::default(),
304 raw_editorconfig_settings: BTreeMap::default(),
305 tab_size_callback: Default::default(),
306 setting_file_updates_tx,
307 _setting_file_updates: cx.spawn(async move |cx| {
308 while let Some(setting_file_update) = setting_file_updates_rx.next().await {
309 (setting_file_update)(cx.clone()).await.log_err();
310 }
311 }),
312 }
313 }
314
315 pub fn observe_active_settings_profile_name(cx: &mut App) -> gpui::Subscription {
316 cx.observe_global::<ActiveSettingsProfileName>(|cx| {
317 Self::update_global(cx, |store, cx| {
318 store.recompute_values(None, cx).log_err();
319 });
320 })
321 }
322
323 pub fn update<C, R>(cx: &mut C, f: impl FnOnce(&mut Self, &mut C) -> R) -> R
324 where
325 C: BorrowAppContext,
326 {
327 cx.update_global(f)
328 }
329
330 /// Add a new type of setting to the store.
331 pub fn register_setting<T: Settings>(&mut self, cx: &mut App) {
332 let setting_type_id = TypeId::of::<T>();
333 let entry = self.setting_values.entry(setting_type_id);
334
335 if matches!(entry, hash_map::Entry::Occupied(_)) {
336 return;
337 }
338
339 let setting_value = entry.or_insert(Box::new(SettingValue::<T> {
340 global_value: None,
341 local_values: Vec::new(),
342 }));
343
344 if let Some(default_settings) = setting_value
345 .deserialize_setting(&self.raw_default_settings)
346 .log_err()
347 {
348 let user_value = setting_value
349 .deserialize_setting(&self.raw_user_settings)
350 .log_err();
351
352 let mut release_channel_value = None;
353 if let Some(release_settings) = &self
354 .raw_user_settings
355 .get(release_channel::RELEASE_CHANNEL.dev_name())
356 {
357 release_channel_value = setting_value
358 .deserialize_setting(release_settings)
359 .log_err();
360 }
361
362 let mut os_settings_value = None;
363 if let Some(os_settings) = &self.raw_user_settings.get(env::consts::OS) {
364 os_settings_value = setting_value.deserialize_setting(os_settings).log_err();
365 }
366
367 let mut profile_value = None;
368 if let Some(active_profile) = cx.try_global::<ActiveSettingsProfileName>()
369 && let Some(profiles) = self.raw_user_settings.get("profiles")
370 && let Some(profile_settings) = profiles.get(&active_profile.0)
371 {
372 profile_value = setting_value
373 .deserialize_setting(profile_settings)
374 .log_err();
375 }
376
377 let server_value = self
378 .raw_server_settings
379 .as_ref()
380 .and_then(|server_setting| {
381 setting_value.deserialize_setting(server_setting).log_err()
382 });
383
384 let extension_value = setting_value
385 .deserialize_setting(&self.raw_extension_settings)
386 .log_err();
387
388 if let Some(setting) = setting_value
389 .load_setting(
390 SettingsSources {
391 default: &default_settings,
392 global: None,
393 extensions: extension_value.as_ref(),
394 user: user_value.as_ref(),
395 release_channel: release_channel_value.as_ref(),
396 operating_system: os_settings_value.as_ref(),
397 profile: profile_value.as_ref(),
398 server: server_value.as_ref(),
399 project: &[],
400 },
401 cx,
402 )
403 .context("A default setting must be added to the `default.json` file")
404 .log_err()
405 {
406 setting_value.set_global_value(setting);
407 }
408 }
409 }
410
411 /// Get the value of a setting.
412 ///
413 /// Panics if the given setting type has not been registered, or if there is no
414 /// value for this setting.
415 pub fn get<T: Settings>(&self, path: Option<SettingsLocation>) -> &T {
416 self.setting_values
417 .get(&TypeId::of::<T>())
418 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
419 .value_for_path(path)
420 .downcast_ref::<T>()
421 .expect("no default value for setting type")
422 }
423
424 /// Get the value of a setting.
425 ///
426 /// Does not panic
427 pub fn try_get<T: Settings>(&self, path: Option<SettingsLocation>) -> Option<&T> {
428 self.setting_values
429 .get(&TypeId::of::<T>())
430 .map(|value| value.value_for_path(path))
431 .and_then(|value| value.downcast_ref::<T>())
432 }
433
434 /// Get all values from project specific settings
435 pub fn get_all_locals<T: Settings>(&self) -> Vec<(WorktreeId, Arc<Path>, &T)> {
436 self.setting_values
437 .get(&TypeId::of::<T>())
438 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
439 .all_local_values()
440 .into_iter()
441 .map(|(id, path, any)| {
442 (
443 id,
444 path,
445 any.downcast_ref::<T>()
446 .expect("wrong value type for setting"),
447 )
448 })
449 .collect()
450 }
451
452 /// Override the global value for a setting.
453 ///
454 /// The given value will be overwritten if the user settings file changes.
455 pub fn override_global<T: Settings>(&mut self, value: T) {
456 self.setting_values
457 .get_mut(&TypeId::of::<T>())
458 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
459 .set_global_value(Box::new(value))
460 }
461
462 /// Get the user's settings as a raw JSON value.
463 ///
464 /// For user-facing functionality use the typed setting interface.
465 /// (e.g. ProjectSettings::get_global(cx))
466 pub fn raw_user_settings(&self) -> &Value {
467 &self.raw_user_settings
468 }
469
470 /// Replaces current settings with the values from the given JSON.
471 pub fn set_raw_user_settings(&mut self, new_settings: Value, cx: &mut App) -> Result<()> {
472 self.raw_user_settings = new_settings;
473 self.recompute_values(None, cx)?;
474 Ok(())
475 }
476
477 /// Get the configured settings profile names.
478 pub fn configured_settings_profiles(&self) -> impl Iterator<Item = &str> {
479 self.raw_user_settings
480 .get("profiles")
481 .and_then(|v| v.as_object())
482 .into_iter()
483 .flat_map(|obj| obj.keys())
484 .map(|s| s.as_str())
485 }
486
487 /// Access the raw JSON value of the global settings.
488 pub fn raw_global_settings(&self) -> Option<&Value> {
489 self.raw_global_settings.as_ref()
490 }
491
492 /// Access the raw JSON value of the default settings.
493 pub fn raw_default_settings(&self) -> &Value {
494 &self.raw_default_settings
495 }
496
497 #[cfg(any(test, feature = "test-support"))]
498 pub fn test(cx: &mut App) -> Self {
499 let mut this = Self::new(cx);
500 this.set_default_settings(&crate::test_settings(), cx)
501 .unwrap();
502 this.set_user_settings("{}", cx).unwrap();
503 this
504 }
505
506 /// Updates the value of a setting in the user's global configuration.
507 ///
508 /// This is only for tests. Normally, settings are only loaded from
509 /// JSON files.
510 #[cfg(any(test, feature = "test-support"))]
511 pub fn update_user_settings<T: Settings>(
512 &mut self,
513 cx: &mut App,
514 update: impl FnOnce(&mut T::FileContent),
515 ) {
516 let old_text = serde_json::to_string(&self.raw_user_settings).unwrap();
517 let new_text = self.new_text_for_update::<T>(old_text, update);
518 self.set_user_settings(&new_text, cx).unwrap();
519 }
520
521 pub async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
522 match fs.load(paths::settings_file()).await {
523 result @ Ok(_) => result,
524 Err(err) => {
525 if let Some(e) = err.downcast_ref::<std::io::Error>()
526 && e.kind() == std::io::ErrorKind::NotFound
527 {
528 return Ok(crate::initial_user_settings_content().to_string());
529 }
530 Err(err)
531 }
532 }
533 }
534
535 fn update_settings_file_inner(
536 &self,
537 fs: Arc<dyn Fs>,
538 update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>,
539 ) -> oneshot::Receiver<Result<()>> {
540 let (tx, rx) = oneshot::channel::<Result<()>>();
541 self.setting_file_updates_tx
542 .unbounded_send(Box::new(move |cx: AsyncApp| {
543 async move {
544 let res = async move {
545 let old_text = Self::load_settings(&fs).await?;
546 let new_text = update(old_text, cx)?;
547 let settings_path = paths::settings_file().as_path();
548 if fs.is_file(settings_path).await {
549 let resolved_path =
550 fs.canonicalize(settings_path).await.with_context(|| {
551 format!(
552 "Failed to canonicalize settings path {:?}",
553 settings_path
554 )
555 })?;
556
557 fs.atomic_write(resolved_path.clone(), new_text)
558 .await
559 .with_context(|| {
560 format!("Failed to write settings to file {:?}", resolved_path)
561 })?;
562 } else {
563 fs.atomic_write(settings_path.to_path_buf(), new_text)
564 .await
565 .with_context(|| {
566 format!("Failed to write settings to file {:?}", settings_path)
567 })?;
568 }
569 anyhow::Ok(())
570 }
571 .await;
572
573 let new_res = match &res {
574 Ok(_) => anyhow::Ok(()),
575 Err(e) => Err(anyhow::anyhow!("Failed to write settings to file {:?}", e)),
576 };
577
578 _ = tx.send(new_res);
579 res
580 }
581 .boxed_local()
582 }))
583 .map_err(|err| anyhow::format_err!("Failed to update settings file: {}", err))
584 .log_with_level(log::Level::Warn);
585 return rx;
586 }
587
588 pub fn update_settings_file_at_path(
589 &self,
590 fs: Arc<dyn Fs>,
591 path: &[&str],
592 new_value: serde_json::Value,
593 ) -> oneshot::Receiver<Result<()>> {
594 let key_path = path
595 .into_iter()
596 .cloned()
597 .map(SharedString::new)
598 .collect::<Vec<_>>();
599 let update = move |mut old_text: String, cx: AsyncApp| {
600 cx.read_global(|store: &SettingsStore, _cx| {
601 // todo(settings_ui) use `update_value_in_json_text` for merging new and old objects with comment preservation, needs old value though...
602 let (range, replacement) = replace_value_in_json_text(
603 &old_text,
604 key_path.as_slice(),
605 store.json_tab_size(),
606 Some(&new_value),
607 None,
608 );
609 old_text.replace_range(range, &replacement);
610 old_text
611 })
612 };
613 self.update_settings_file_inner(fs, update)
614 }
615
616 pub fn update_settings_file<T: Settings>(
617 &self,
618 fs: Arc<dyn Fs>,
619 update: impl 'static + Send + FnOnce(&mut T::FileContent, &App),
620 ) {
621 _ = self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
622 cx.read_global(|store: &SettingsStore, cx| {
623 store.new_text_for_update::<T>(old_text, |content| update(content, cx))
624 })
625 });
626 }
627
628 pub fn import_vscode_settings(
629 &self,
630 fs: Arc<dyn Fs>,
631 vscode_settings: VsCodeSettings,
632 ) -> oneshot::Receiver<Result<()>> {
633 self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
634 cx.read_global(|store: &SettingsStore, _cx| {
635 store.get_vscode_edits(old_text, &vscode_settings)
636 })
637 })
638 }
639
640 pub fn settings_ui_items(&self) -> impl IntoIterator<Item = SettingsUiEntry> {
641 self.setting_values
642 .values()
643 .map(|item| item.settings_ui_item())
644 }
645}
646
647impl SettingsStore {
648 /// Updates the value of a setting in a JSON file, returning the new text
649 /// for that JSON file.
650 pub fn new_text_for_update<T: Settings>(
651 &self,
652 old_text: String,
653 update: impl FnOnce(&mut T::FileContent),
654 ) -> String {
655 let edits = self.edits_for_update::<T>(&old_text, update);
656 let mut new_text = old_text;
657 for (range, replacement) in edits.into_iter() {
658 new_text.replace_range(range, &replacement);
659 }
660 new_text
661 }
662
663 pub fn get_vscode_edits(&self, mut old_text: String, vscode: &VsCodeSettings) -> String {
664 let mut new_text = old_text.clone();
665 let mut edits: Vec<(Range<usize>, String)> = Vec::new();
666 let raw_settings = parse_json_with_comments::<Value>(&old_text).unwrap_or_default();
667 let tab_size = self.json_tab_size();
668 for v in self.setting_values.values() {
669 v.edits_for_update(&raw_settings, tab_size, vscode, &mut old_text, &mut edits);
670 }
671 for (range, replacement) in edits.into_iter() {
672 new_text.replace_range(range, &replacement);
673 }
674 new_text
675 }
676
677 /// Updates the value of a setting in a JSON file, returning a list
678 /// of edits to apply to the JSON file.
679 pub fn edits_for_update<T: Settings>(
680 &self,
681 text: &str,
682 update: impl FnOnce(&mut T::FileContent),
683 ) -> Vec<(Range<usize>, String)> {
684 let setting_type_id = TypeId::of::<T>();
685
686 let preserved_keys = T::PRESERVED_KEYS.unwrap_or_default();
687
688 let setting = self
689 .setting_values
690 .get(&setting_type_id)
691 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()));
692 let raw_settings = parse_json_with_comments::<Value>(text).unwrap_or_default();
693 let (key, deserialized_setting) = setting.deserialize_setting_with_key(&raw_settings);
694 let old_content = match deserialized_setting {
695 Ok(content) => content.0.downcast::<T::FileContent>().unwrap(),
696 Err(_) => Box::<<T as Settings>::FileContent>::default(),
697 };
698 let mut new_content = old_content.clone();
699 update(&mut new_content);
700
701 let old_value = serde_json::to_value(&old_content).unwrap();
702 let new_value = serde_json::to_value(new_content).unwrap();
703
704 let mut key_path = Vec::new();
705 if let Some(key) = key {
706 key_path.push(key);
707 }
708
709 let mut edits = Vec::new();
710 let tab_size = self.json_tab_size();
711 let mut text = text.to_string();
712 update_value_in_json_text(
713 &mut text,
714 &mut key_path,
715 tab_size,
716 &old_value,
717 &new_value,
718 preserved_keys,
719 &mut edits,
720 );
721 edits
722 }
723
724 /// Configure the tab sized when updating JSON files.
725 pub fn set_json_tab_size_callback<T: Settings>(
726 &mut self,
727 get_tab_size: fn(&T) -> Option<usize>,
728 ) {
729 self.tab_size_callback = Some((
730 TypeId::of::<T>(),
731 Box::new(move |value| get_tab_size(value.downcast_ref::<T>().unwrap())),
732 ));
733 }
734
735 pub fn json_tab_size(&self) -> usize {
736 const DEFAULT_JSON_TAB_SIZE: usize = 2;
737
738 if let Some((setting_type_id, callback)) = &self.tab_size_callback {
739 let setting_value = self.setting_values.get(setting_type_id).unwrap();
740 let value = setting_value.value_for_path(None);
741 if let Some(value) = callback(value) {
742 return value;
743 }
744 }
745
746 DEFAULT_JSON_TAB_SIZE
747 }
748
749 /// Sets the default settings via a JSON string.
750 ///
751 /// The string should contain a JSON object with a default value for every setting.
752 pub fn set_default_settings(
753 &mut self,
754 default_settings_content: &str,
755 cx: &mut App,
756 ) -> Result<()> {
757 let settings: Value = parse_json_with_comments(default_settings_content)?;
758 anyhow::ensure!(settings.is_object(), "settings must be an object");
759 self.raw_default_settings = settings;
760 self.recompute_values(None, cx)?;
761 Ok(())
762 }
763
764 /// Sets the user settings via a JSON string.
765 pub fn set_user_settings(
766 &mut self,
767 user_settings_content: &str,
768 cx: &mut App,
769 ) -> Result<Value> {
770 let settings: Value = if user_settings_content.is_empty() {
771 parse_json_with_comments("{}")?
772 } else {
773 parse_json_with_comments(user_settings_content)?
774 };
775
776 anyhow::ensure!(settings.is_object(), "settings must be an object");
777 self.raw_user_settings = settings.clone();
778 self.recompute_values(None, cx)?;
779 Ok(settings)
780 }
781
782 /// Sets the global settings via a JSON string.
783 pub fn set_global_settings(
784 &mut self,
785 global_settings_content: &str,
786 cx: &mut App,
787 ) -> Result<Value> {
788 let settings: Value = if global_settings_content.is_empty() {
789 parse_json_with_comments("{}")?
790 } else {
791 parse_json_with_comments(global_settings_content)?
792 };
793
794 anyhow::ensure!(settings.is_object(), "settings must be an object");
795 self.raw_global_settings = Some(settings.clone());
796 self.recompute_values(None, cx)?;
797 Ok(settings)
798 }
799
800 pub fn set_server_settings(
801 &mut self,
802 server_settings_content: &str,
803 cx: &mut App,
804 ) -> Result<()> {
805 let settings: Option<Value> = if server_settings_content.is_empty() {
806 None
807 } else {
808 parse_json_with_comments(server_settings_content)?
809 };
810
811 anyhow::ensure!(
812 settings
813 .as_ref()
814 .map(|value| value.is_object())
815 .unwrap_or(true),
816 "settings must be an object"
817 );
818 self.raw_server_settings = settings;
819 self.recompute_values(None, cx)?;
820 Ok(())
821 }
822
823 /// Add or remove a set of local settings via a JSON string.
824 pub fn set_local_settings(
825 &mut self,
826 root_id: WorktreeId,
827 directory_path: Arc<Path>,
828 kind: LocalSettingsKind,
829 settings_content: Option<&str>,
830 cx: &mut App,
831 ) -> std::result::Result<(), InvalidSettingsError> {
832 let mut zed_settings_changed = false;
833 match (
834 kind,
835 settings_content
836 .map(|content| content.trim())
837 .filter(|content| !content.is_empty()),
838 ) {
839 (LocalSettingsKind::Tasks, _) => {
840 return Err(InvalidSettingsError::Tasks {
841 message: "Attempted to submit tasks into the settings store".to_string(),
842 path: directory_path.join(task_file_name()),
843 });
844 }
845 (LocalSettingsKind::Debug, _) => {
846 return Err(InvalidSettingsError::Debug {
847 message: "Attempted to submit debugger config into the settings store"
848 .to_string(),
849 path: directory_path.join(task_file_name()),
850 });
851 }
852 (LocalSettingsKind::Settings, None) => {
853 zed_settings_changed = self
854 .raw_local_settings
855 .remove(&(root_id, directory_path.clone()))
856 .is_some()
857 }
858 (LocalSettingsKind::Editorconfig, None) => {
859 self.raw_editorconfig_settings
860 .remove(&(root_id, directory_path.clone()));
861 }
862 (LocalSettingsKind::Settings, Some(settings_contents)) => {
863 let new_settings =
864 parse_json_with_comments::<Value>(settings_contents).map_err(|e| {
865 InvalidSettingsError::LocalSettings {
866 path: directory_path.join(local_settings_file_relative_path()),
867 message: e.to_string(),
868 }
869 })?;
870 match self
871 .raw_local_settings
872 .entry((root_id, directory_path.clone()))
873 {
874 btree_map::Entry::Vacant(v) => {
875 v.insert(new_settings);
876 zed_settings_changed = true;
877 }
878 btree_map::Entry::Occupied(mut o) => {
879 if o.get() != &new_settings {
880 o.insert(new_settings);
881 zed_settings_changed = true;
882 }
883 }
884 }
885 }
886 (LocalSettingsKind::Editorconfig, Some(editorconfig_contents)) => {
887 match self
888 .raw_editorconfig_settings
889 .entry((root_id, directory_path.clone()))
890 {
891 btree_map::Entry::Vacant(v) => match editorconfig_contents.parse() {
892 Ok(new_contents) => {
893 v.insert((editorconfig_contents.to_owned(), Some(new_contents)));
894 }
895 Err(e) => {
896 v.insert((editorconfig_contents.to_owned(), None));
897 return Err(InvalidSettingsError::Editorconfig {
898 message: e.to_string(),
899 path: directory_path.join(EDITORCONFIG_NAME),
900 });
901 }
902 },
903 btree_map::Entry::Occupied(mut o) => {
904 if o.get().0 != editorconfig_contents {
905 match editorconfig_contents.parse() {
906 Ok(new_contents) => {
907 o.insert((
908 editorconfig_contents.to_owned(),
909 Some(new_contents),
910 ));
911 }
912 Err(e) => {
913 o.insert((editorconfig_contents.to_owned(), None));
914 return Err(InvalidSettingsError::Editorconfig {
915 message: e.to_string(),
916 path: directory_path.join(EDITORCONFIG_NAME),
917 });
918 }
919 }
920 }
921 }
922 }
923 }
924 };
925
926 if zed_settings_changed {
927 self.recompute_values(Some((root_id, &directory_path)), cx)?;
928 }
929 Ok(())
930 }
931
932 pub fn set_extension_settings<T: Serialize>(&mut self, content: T, cx: &mut App) -> Result<()> {
933 let settings: Value = serde_json::to_value(content)?;
934 anyhow::ensure!(settings.is_object(), "settings must be an object");
935 self.raw_extension_settings = settings;
936 self.recompute_values(None, cx)?;
937 Ok(())
938 }
939
940 /// Add or remove a set of local settings via a JSON string.
941 pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut App) -> Result<()> {
942 self.raw_local_settings
943 .retain(|(worktree_id, _), _| worktree_id != &root_id);
944 self.recompute_values(Some((root_id, "".as_ref())), cx)?;
945 Ok(())
946 }
947
948 pub fn local_settings(
949 &self,
950 root_id: WorktreeId,
951 ) -> impl '_ + Iterator<Item = (Arc<Path>, String)> {
952 self.raw_local_settings
953 .range(
954 (root_id, Path::new("").into())
955 ..(
956 WorktreeId::from_usize(root_id.to_usize() + 1),
957 Path::new("").into(),
958 ),
959 )
960 .map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap()))
961 }
962
963 pub fn local_editorconfig_settings(
964 &self,
965 root_id: WorktreeId,
966 ) -> impl '_ + Iterator<Item = (Arc<Path>, String, Option<Editorconfig>)> {
967 self.raw_editorconfig_settings
968 .range(
969 (root_id, Path::new("").into())
970 ..(
971 WorktreeId::from_usize(root_id.to_usize() + 1),
972 Path::new("").into(),
973 ),
974 )
975 .map(|((_, path), (content, parsed_content))| {
976 (path.clone(), content.clone(), parsed_content.clone())
977 })
978 }
979
980 pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value {
981 let mut generator = schemars::generate::SchemaSettings::draft2019_09()
982 .with_transform(DefaultDenyUnknownFields)
983 .into_generator();
984 let mut combined_schema = json!({
985 "type": "object",
986 "properties": {}
987 });
988
989 // Merge together settings schemas, similarly to json schema's "allOf". This merging is
990 // recursive, though at time of writing this recursive nature isn't used very much. An
991 // example of it is the schema for `jupyter` having contribution from both `EditorSettings`
992 // and `JupyterSettings`.
993 //
994 // This logic could be removed in favor of "allOf", but then there isn't the opportunity to
995 // validate and fully control the merge.
996 for setting_value in self.setting_values.values() {
997 let mut setting_schema = setting_value.json_schema(&mut generator);
998
999 if let Some(key) = setting_value.key() {
1000 if let Some(properties) = combined_schema.get_mut("properties")
1001 && let Some(properties_obj) = properties.as_object_mut()
1002 {
1003 if let Some(target) = properties_obj.get_mut(key) {
1004 merge_schema(target, setting_schema.to_value());
1005 } else {
1006 properties_obj.insert(key.to_string(), setting_schema.to_value());
1007 }
1008 }
1009 } else {
1010 setting_schema.remove("description");
1011 setting_schema.remove("additionalProperties");
1012 merge_schema(&mut combined_schema, setting_schema.to_value());
1013 }
1014 }
1015
1016 fn merge_schema(target: &mut serde_json::Value, source: serde_json::Value) {
1017 let (Some(target_obj), serde_json::Value::Object(source_obj)) =
1018 (target.as_object_mut(), source)
1019 else {
1020 return;
1021 };
1022
1023 for (source_key, source_value) in source_obj {
1024 match source_key.as_str() {
1025 "properties" => {
1026 let serde_json::Value::Object(source_properties) = source_value else {
1027 log::error!(
1028 "bug: expected object for `{}` json schema field, but got: {}",
1029 source_key,
1030 source_value
1031 );
1032 continue;
1033 };
1034 let target_properties =
1035 target_obj.entry(source_key.clone()).or_insert(json!({}));
1036 let Some(target_properties) = target_properties.as_object_mut() else {
1037 log::error!(
1038 "bug: expected object for `{}` json schema field, but got: {}",
1039 source_key,
1040 target_properties
1041 );
1042 continue;
1043 };
1044 for (key, value) in source_properties {
1045 if let Some(existing) = target_properties.get_mut(&key) {
1046 merge_schema(existing, value);
1047 } else {
1048 target_properties.insert(key, value);
1049 }
1050 }
1051 }
1052 "allOf" | "anyOf" | "oneOf" => {
1053 let serde_json::Value::Array(source_array) = source_value else {
1054 log::error!(
1055 "bug: expected array for `{}` json schema field, but got: {}",
1056 source_key,
1057 source_value,
1058 );
1059 continue;
1060 };
1061 let target_array =
1062 target_obj.entry(source_key.clone()).or_insert(json!([]));
1063 let Some(target_array) = target_array.as_array_mut() else {
1064 log::error!(
1065 "bug: expected array for `{}` json schema field, but got: {}",
1066 source_key,
1067 target_array,
1068 );
1069 continue;
1070 };
1071 target_array.extend(source_array);
1072 }
1073 "type"
1074 | "$ref"
1075 | "enum"
1076 | "minimum"
1077 | "maximum"
1078 | "pattern"
1079 | "description"
1080 | "additionalProperties" => {
1081 if let Some(old_value) =
1082 target_obj.insert(source_key.clone(), source_value.clone())
1083 && old_value != source_value
1084 {
1085 log::error!(
1086 "bug: while merging JSON schemas, \
1087 mismatch `\"{}\": {}` (before was `{}`)",
1088 source_key,
1089 old_value,
1090 source_value
1091 );
1092 }
1093 }
1094 _ => {
1095 log::error!(
1096 "bug: while merging settings JSON schemas, \
1097 encountered unexpected `\"{}\": {}`",
1098 source_key,
1099 source_value
1100 );
1101 }
1102 }
1103 }
1104 }
1105
1106 // add schemas which are determined at runtime
1107 for parameterized_json_schema in inventory::iter::<ParameterizedJsonSchema>() {
1108 (parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx);
1109 }
1110
1111 // add merged settings schema to the definitions
1112 const ZED_SETTINGS: &str = "ZedSettings";
1113 let zed_settings_ref = add_new_subschema(&mut generator, ZED_SETTINGS, combined_schema);
1114
1115 // add `ZedSettingsOverride` which is the same as `ZedSettings` except that unknown
1116 // fields are rejected. This is used for release stage settings and profiles.
1117 let mut zed_settings_override = zed_settings_ref.clone();
1118 zed_settings_override.insert("unevaluatedProperties".to_string(), false.into());
1119 let zed_settings_override_ref = add_new_subschema(
1120 &mut generator,
1121 "ZedSettingsOverride",
1122 zed_settings_override.to_value(),
1123 );
1124
1125 // Remove `"additionalProperties": false` added by `DefaultDenyUnknownFields` so that
1126 // unknown fields can be handled by the root schema and `ZedSettingsOverride`.
1127 let mut definitions = generator.take_definitions(true);
1128 definitions
1129 .get_mut(ZED_SETTINGS)
1130 .unwrap()
1131 .as_object_mut()
1132 .unwrap()
1133 .remove("additionalProperties");
1134
1135 let meta_schema = generator
1136 .settings()
1137 .meta_schema
1138 .as_ref()
1139 .expect("meta_schema should be present in schemars settings")
1140 .to_string();
1141
1142 json!({
1143 "$schema": meta_schema,
1144 "title": "Zed Settings",
1145 "unevaluatedProperties": false,
1146 // ZedSettings + settings overrides for each release stage / OS / profiles
1147 "allOf": [
1148 zed_settings_ref,
1149 {
1150 "properties": {
1151 "dev": zed_settings_override_ref,
1152 "nightly": zed_settings_override_ref,
1153 "stable": zed_settings_override_ref,
1154 "preview": zed_settings_override_ref,
1155 "linux": zed_settings_override_ref,
1156 "macos": zed_settings_override_ref,
1157 "windows": zed_settings_override_ref,
1158 "profiles": {
1159 "type": "object",
1160 "description": "Configures any number of settings profiles.",
1161 "additionalProperties": zed_settings_override_ref
1162 }
1163 }
1164 }
1165 ],
1166 "$defs": definitions,
1167 })
1168 }
1169
1170 fn recompute_values(
1171 &mut self,
1172 changed_local_path: Option<(WorktreeId, &Path)>,
1173 cx: &mut App,
1174 ) -> std::result::Result<(), InvalidSettingsError> {
1175 // Reload the global and local values for every setting.
1176 let mut project_settings_stack = Vec::<DeserializedSetting>::new();
1177 let mut paths_stack = Vec::<Option<(WorktreeId, &Path)>>::new();
1178 for setting_value in self.setting_values.values_mut() {
1179 let default_settings = setting_value
1180 .deserialize_setting(&self.raw_default_settings)
1181 .map_err(|e| InvalidSettingsError::DefaultSettings {
1182 message: e.to_string(),
1183 })?;
1184
1185 let global_settings = self
1186 .raw_global_settings
1187 .as_ref()
1188 .and_then(|setting| setting_value.deserialize_setting(setting).log_err());
1189
1190 let extension_settings = setting_value
1191 .deserialize_setting(&self.raw_extension_settings)
1192 .log_err();
1193
1194 let user_settings = match setting_value.deserialize_setting(&self.raw_user_settings) {
1195 Ok(settings) => Some(settings),
1196 Err(error) => {
1197 return Err(InvalidSettingsError::UserSettings {
1198 message: error.to_string(),
1199 });
1200 }
1201 };
1202
1203 let server_settings = self
1204 .raw_server_settings
1205 .as_ref()
1206 .and_then(|setting| setting_value.deserialize_setting(setting).log_err());
1207
1208 let mut release_channel_settings = None;
1209 if let Some(release_settings) = &self
1210 .raw_user_settings
1211 .get(release_channel::RELEASE_CHANNEL.dev_name())
1212 && let Some(release_settings) = setting_value
1213 .deserialize_setting(release_settings)
1214 .log_err()
1215 {
1216 release_channel_settings = Some(release_settings);
1217 }
1218
1219 let mut os_settings = None;
1220 if let Some(settings) = &self.raw_user_settings.get(env::consts::OS)
1221 && let Some(settings) = setting_value.deserialize_setting(settings).log_err()
1222 {
1223 os_settings = Some(settings);
1224 }
1225
1226 let mut profile_settings = None;
1227 if let Some(active_profile) = cx.try_global::<ActiveSettingsProfileName>()
1228 && let Some(profiles) = self.raw_user_settings.get("profiles")
1229 && let Some(profile_json) = profiles.get(&active_profile.0)
1230 {
1231 profile_settings = setting_value.deserialize_setting(profile_json).log_err();
1232 }
1233
1234 // If the global settings file changed, reload the global value for the field.
1235 if changed_local_path.is_none()
1236 && let Some(value) = setting_value
1237 .load_setting(
1238 SettingsSources {
1239 default: &default_settings,
1240 global: global_settings.as_ref(),
1241 extensions: extension_settings.as_ref(),
1242 user: user_settings.as_ref(),
1243 release_channel: release_channel_settings.as_ref(),
1244 operating_system: os_settings.as_ref(),
1245 profile: profile_settings.as_ref(),
1246 server: server_settings.as_ref(),
1247 project: &[],
1248 },
1249 cx,
1250 )
1251 .log_err()
1252 {
1253 setting_value.set_global_value(value);
1254 }
1255
1256 // Reload the local values for the setting.
1257 paths_stack.clear();
1258 project_settings_stack.clear();
1259 for ((root_id, directory_path), local_settings) in &self.raw_local_settings {
1260 // Build a stack of all of the local values for that setting.
1261 while let Some(prev_entry) = paths_stack.last() {
1262 if let Some((prev_root_id, prev_path)) = prev_entry
1263 && (root_id != prev_root_id || !directory_path.starts_with(prev_path))
1264 {
1265 paths_stack.pop();
1266 project_settings_stack.pop();
1267 continue;
1268 }
1269 break;
1270 }
1271
1272 match setting_value.deserialize_setting(local_settings) {
1273 Ok(local_settings) => {
1274 paths_stack.push(Some((*root_id, directory_path.as_ref())));
1275 project_settings_stack.push(local_settings);
1276
1277 // If a local settings file changed, then avoid recomputing local
1278 // settings for any path outside of that directory.
1279 if changed_local_path.is_some_and(
1280 |(changed_root_id, changed_local_path)| {
1281 *root_id != changed_root_id
1282 || !directory_path.starts_with(changed_local_path)
1283 },
1284 ) {
1285 continue;
1286 }
1287
1288 if let Some(value) = setting_value
1289 .load_setting(
1290 SettingsSources {
1291 default: &default_settings,
1292 global: global_settings.as_ref(),
1293 extensions: extension_settings.as_ref(),
1294 user: user_settings.as_ref(),
1295 release_channel: release_channel_settings.as_ref(),
1296 operating_system: os_settings.as_ref(),
1297 profile: profile_settings.as_ref(),
1298 server: server_settings.as_ref(),
1299 project: &project_settings_stack.iter().collect::<Vec<_>>(),
1300 },
1301 cx,
1302 )
1303 .log_err()
1304 {
1305 setting_value.set_local_value(*root_id, directory_path.clone(), value);
1306 }
1307 }
1308 Err(error) => {
1309 return Err(InvalidSettingsError::LocalSettings {
1310 path: directory_path.join(local_settings_file_relative_path()),
1311 message: error.to_string(),
1312 });
1313 }
1314 }
1315 }
1316 }
1317 Ok(())
1318 }
1319
1320 pub fn editorconfig_properties(
1321 &self,
1322 for_worktree: WorktreeId,
1323 for_path: &Path,
1324 ) -> Option<EditorconfigProperties> {
1325 let mut properties = EditorconfigProperties::new();
1326
1327 for (directory_with_config, _, parsed_editorconfig) in
1328 self.local_editorconfig_settings(for_worktree)
1329 {
1330 if !for_path.starts_with(&directory_with_config) {
1331 properties.use_fallbacks();
1332 return Some(properties);
1333 }
1334 let parsed_editorconfig = parsed_editorconfig?;
1335 if parsed_editorconfig.is_root {
1336 properties = EditorconfigProperties::new();
1337 }
1338 for section in parsed_editorconfig.sections {
1339 section.apply_to(&mut properties, for_path).log_err()?;
1340 }
1341 }
1342
1343 properties.use_fallbacks();
1344 Some(properties)
1345 }
1346}
1347
1348#[derive(Debug, Clone, PartialEq)]
1349pub enum InvalidSettingsError {
1350 LocalSettings { path: PathBuf, message: String },
1351 UserSettings { message: String },
1352 ServerSettings { message: String },
1353 DefaultSettings { message: String },
1354 Editorconfig { path: PathBuf, message: String },
1355 Tasks { path: PathBuf, message: String },
1356 Debug { path: PathBuf, message: String },
1357}
1358
1359impl std::fmt::Display for InvalidSettingsError {
1360 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1361 match self {
1362 InvalidSettingsError::LocalSettings { message, .. }
1363 | InvalidSettingsError::UserSettings { message }
1364 | InvalidSettingsError::ServerSettings { message }
1365 | InvalidSettingsError::DefaultSettings { message }
1366 | InvalidSettingsError::Tasks { message, .. }
1367 | InvalidSettingsError::Editorconfig { message, .. }
1368 | InvalidSettingsError::Debug { message, .. } => {
1369 write!(f, "{message}")
1370 }
1371 }
1372 }
1373}
1374impl std::error::Error for InvalidSettingsError {}
1375
1376impl Debug for SettingsStore {
1377 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1378 f.debug_struct("SettingsStore")
1379 .field(
1380 "types",
1381 &self
1382 .setting_values
1383 .values()
1384 .map(|value| value.setting_type_name())
1385 .collect::<Vec<_>>(),
1386 )
1387 .field("default_settings", &self.raw_default_settings)
1388 .field("user_settings", &self.raw_user_settings)
1389 .field("local_settings", &self.raw_local_settings)
1390 .finish_non_exhaustive()
1391 }
1392}
1393
1394impl<T: Settings> AnySettingValue for SettingValue<T> {
1395 fn key(&self) -> Option<&'static str> {
1396 T::KEY
1397 }
1398
1399 fn setting_type_name(&self) -> &'static str {
1400 type_name::<T>()
1401 }
1402
1403 fn load_setting(
1404 &self,
1405 values: SettingsSources<DeserializedSetting>,
1406 cx: &mut App,
1407 ) -> Result<Box<dyn Any>> {
1408 Ok(Box::new(T::load(
1409 SettingsSources {
1410 default: values.default.0.downcast_ref::<T::FileContent>().unwrap(),
1411 global: values
1412 .global
1413 .map(|value| value.0.downcast_ref::<T::FileContent>().unwrap()),
1414 extensions: values
1415 .extensions
1416 .map(|value| value.0.downcast_ref::<T::FileContent>().unwrap()),
1417 user: values
1418 .user
1419 .map(|value| value.0.downcast_ref::<T::FileContent>().unwrap()),
1420 release_channel: values
1421 .release_channel
1422 .map(|value| value.0.downcast_ref::<T::FileContent>().unwrap()),
1423 operating_system: values
1424 .operating_system
1425 .map(|value| value.0.downcast_ref::<T::FileContent>().unwrap()),
1426 profile: values
1427 .profile
1428 .map(|value| value.0.downcast_ref::<T::FileContent>().unwrap()),
1429 server: values
1430 .server
1431 .map(|value| value.0.downcast_ref::<T::FileContent>().unwrap()),
1432 project: values
1433 .project
1434 .iter()
1435 .map(|value| value.0.downcast_ref().unwrap())
1436 .collect::<SmallVec<[_; 3]>>()
1437 .as_slice(),
1438 },
1439 cx,
1440 )?))
1441 }
1442
1443 fn deserialize_setting_with_key(
1444 &self,
1445 mut json: &Value,
1446 ) -> (Option<&'static str>, Result<DeserializedSetting>) {
1447 let mut key = None;
1448 if let Some(k) = T::KEY {
1449 if let Some(value) = json.get(k) {
1450 json = value;
1451 key = Some(k);
1452 } else if let Some((k, value)) = T::FALLBACK_KEY.and_then(|k| Some((k, json.get(k)?))) {
1453 json = value;
1454 key = Some(k);
1455 } else {
1456 let value = T::FileContent::default();
1457 return (T::KEY, Ok(DeserializedSetting(Box::new(value))));
1458 }
1459 }
1460 let value = serde_path_to_error::deserialize::<_, T::FileContent>(json)
1461 .map(|value| DeserializedSetting(Box::new(value)))
1462 .map_err(|err| {
1463 // construct a path using the key and reported error path if possible.
1464 // Unfortunately, serde_path_to_error does not expose the necessary
1465 // methods and data to simply add the key to the path
1466 let mut path = String::new();
1467 if let Some(key) = key {
1468 path.push_str(key);
1469 }
1470 let err_path = err.path().to_string();
1471 // when the path is empty, serde_path_to_error stringifies the path as ".",
1472 // when the path is unknown, serde_path_to_error stringifies the path as an empty string
1473 if !err_path.is_empty() && !err_path.starts_with(".") {
1474 path.push('.');
1475 path.push_str(&err_path);
1476 }
1477 if path.is_empty() {
1478 anyhow::Error::from(err.into_inner())
1479 } else {
1480 anyhow::anyhow!("'{}': {}", err.into_inner(), path)
1481 }
1482 });
1483 (key, value)
1484 }
1485
1486 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)> {
1487 self.local_values
1488 .iter()
1489 .map(|(id, path, value)| (*id, path.clone(), value as _))
1490 .collect()
1491 }
1492
1493 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
1494 if let Some(SettingsLocation { worktree_id, path }) = path {
1495 for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
1496 if worktree_id == *settings_root_id && path.starts_with(settings_path) {
1497 return value;
1498 }
1499 }
1500 }
1501 self.global_value
1502 .as_ref()
1503 .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
1504 }
1505
1506 fn set_global_value(&mut self, value: Box<dyn Any>) {
1507 self.global_value = Some(*value.downcast().unwrap());
1508 }
1509
1510 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>) {
1511 let value = *value.downcast().unwrap();
1512 match self
1513 .local_values
1514 .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
1515 {
1516 Ok(ix) => self.local_values[ix].2 = value,
1517 Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
1518 }
1519 }
1520
1521 fn json_schema(&self, generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1522 T::FileContent::json_schema(generator)
1523 }
1524
1525 fn edits_for_update(
1526 &self,
1527 raw_settings: &serde_json::Value,
1528 tab_size: usize,
1529 vscode_settings: &VsCodeSettings,
1530 text: &mut String,
1531 edits: &mut Vec<(Range<usize>, String)>,
1532 ) {
1533 let (key, deserialized_setting) = self.deserialize_setting_with_key(raw_settings);
1534 let old_content = match deserialized_setting {
1535 Ok(content) => content.0.downcast::<T::FileContent>().unwrap(),
1536 Err(_) => Box::<<T as Settings>::FileContent>::default(),
1537 };
1538 let mut new_content = old_content.clone();
1539 T::import_from_vscode(vscode_settings, &mut new_content);
1540
1541 let old_value = serde_json::to_value(&old_content).unwrap();
1542 let new_value = serde_json::to_value(new_content).unwrap();
1543
1544 let mut key_path = Vec::new();
1545 if let Some(key) = key {
1546 key_path.push(key);
1547 }
1548
1549 update_value_in_json_text(
1550 text,
1551 &mut key_path,
1552 tab_size,
1553 &old_value,
1554 &new_value,
1555 T::PRESERVED_KEYS.unwrap_or_default(),
1556 edits,
1557 );
1558 }
1559
1560 fn settings_ui_item(&self) -> SettingsUiEntry {
1561 <<T as Settings>::FileContent as SettingsUi>::settings_ui_entry()
1562 }
1563}
1564
1565#[cfg(test)]
1566mod tests {
1567 use crate::VsCodeSettingsSource;
1568
1569 use super::*;
1570 // This is so the SettingsUi macro can still work properly
1571 use crate as settings;
1572 use serde_derive::Deserialize;
1573 use settings_ui_macros::SettingsUi;
1574 use unindent::Unindent;
1575
1576 #[gpui::test]
1577 fn test_settings_store_basic(cx: &mut App) {
1578 let mut store = SettingsStore::new(cx);
1579 store.register_setting::<UserSettings>(cx);
1580 store.register_setting::<TurboSetting>(cx);
1581 store.register_setting::<MultiKeySettings>(cx);
1582 store
1583 .set_default_settings(
1584 r#"{
1585 "turbo": false,
1586 "user": {
1587 "name": "John Doe",
1588 "age": 30,
1589 "staff": false
1590 }
1591 }"#,
1592 cx,
1593 )
1594 .unwrap();
1595
1596 assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
1597 assert_eq!(
1598 store.get::<UserSettings>(None),
1599 &UserSettings {
1600 name: "John Doe".to_string(),
1601 age: 30,
1602 staff: false,
1603 }
1604 );
1605 assert_eq!(
1606 store.get::<MultiKeySettings>(None),
1607 &MultiKeySettings {
1608 key1: String::new(),
1609 key2: String::new(),
1610 }
1611 );
1612
1613 store
1614 .set_user_settings(
1615 r#"{
1616 "turbo": true,
1617 "user": { "age": 31 },
1618 "key1": "a"
1619 }"#,
1620 cx,
1621 )
1622 .unwrap();
1623
1624 assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(true));
1625 assert_eq!(
1626 store.get::<UserSettings>(None),
1627 &UserSettings {
1628 name: "John Doe".to_string(),
1629 age: 31,
1630 staff: false
1631 }
1632 );
1633
1634 store
1635 .set_local_settings(
1636 WorktreeId::from_usize(1),
1637 Path::new("/root1").into(),
1638 LocalSettingsKind::Settings,
1639 Some(r#"{ "user": { "staff": true } }"#),
1640 cx,
1641 )
1642 .unwrap();
1643 store
1644 .set_local_settings(
1645 WorktreeId::from_usize(1),
1646 Path::new("/root1/subdir").into(),
1647 LocalSettingsKind::Settings,
1648 Some(r#"{ "user": { "name": "Jane Doe" } }"#),
1649 cx,
1650 )
1651 .unwrap();
1652
1653 store
1654 .set_local_settings(
1655 WorktreeId::from_usize(1),
1656 Path::new("/root2").into(),
1657 LocalSettingsKind::Settings,
1658 Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
1659 cx,
1660 )
1661 .unwrap();
1662
1663 assert_eq!(
1664 store.get::<UserSettings>(Some(SettingsLocation {
1665 worktree_id: WorktreeId::from_usize(1),
1666 path: Path::new("/root1/something"),
1667 })),
1668 &UserSettings {
1669 name: "John Doe".to_string(),
1670 age: 31,
1671 staff: true
1672 }
1673 );
1674 assert_eq!(
1675 store.get::<UserSettings>(Some(SettingsLocation {
1676 worktree_id: WorktreeId::from_usize(1),
1677 path: Path::new("/root1/subdir/something")
1678 })),
1679 &UserSettings {
1680 name: "Jane Doe".to_string(),
1681 age: 31,
1682 staff: true
1683 }
1684 );
1685 assert_eq!(
1686 store.get::<UserSettings>(Some(SettingsLocation {
1687 worktree_id: WorktreeId::from_usize(1),
1688 path: Path::new("/root2/something")
1689 })),
1690 &UserSettings {
1691 name: "John Doe".to_string(),
1692 age: 42,
1693 staff: false
1694 }
1695 );
1696 assert_eq!(
1697 store.get::<MultiKeySettings>(Some(SettingsLocation {
1698 worktree_id: WorktreeId::from_usize(1),
1699 path: Path::new("/root2/something")
1700 })),
1701 &MultiKeySettings {
1702 key1: "a".to_string(),
1703 key2: "b".to_string(),
1704 }
1705 );
1706 }
1707
1708 #[gpui::test]
1709 fn test_setting_store_assign_json_before_register(cx: &mut App) {
1710 let mut store = SettingsStore::new(cx);
1711 store
1712 .set_default_settings(
1713 r#"{
1714 "turbo": true,
1715 "user": {
1716 "name": "John Doe",
1717 "age": 30,
1718 "staff": false
1719 },
1720 "key1": "x"
1721 }"#,
1722 cx,
1723 )
1724 .unwrap();
1725 store
1726 .set_user_settings(r#"{ "turbo": false }"#, cx)
1727 .unwrap();
1728 store.register_setting::<UserSettings>(cx);
1729 store.register_setting::<TurboSetting>(cx);
1730
1731 assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
1732 assert_eq!(
1733 store.get::<UserSettings>(None),
1734 &UserSettings {
1735 name: "John Doe".to_string(),
1736 age: 30,
1737 staff: false,
1738 }
1739 );
1740
1741 store.register_setting::<MultiKeySettings>(cx);
1742 assert_eq!(
1743 store.get::<MultiKeySettings>(None),
1744 &MultiKeySettings {
1745 key1: "x".into(),
1746 key2: String::new(),
1747 }
1748 );
1749 }
1750
1751 fn check_settings_update<T: Settings>(
1752 store: &mut SettingsStore,
1753 old_json: String,
1754 update: fn(&mut T::FileContent),
1755 expected_new_json: String,
1756 cx: &mut App,
1757 ) {
1758 store.set_user_settings(&old_json, cx).ok();
1759 let edits = store.edits_for_update::<T>(&old_json, update);
1760 let mut new_json = old_json;
1761 for (range, replacement) in edits.into_iter() {
1762 new_json.replace_range(range, &replacement);
1763 }
1764 pretty_assertions::assert_eq!(new_json, expected_new_json);
1765 }
1766
1767 #[gpui::test]
1768 fn test_setting_store_update(cx: &mut App) {
1769 let mut store = SettingsStore::new(cx);
1770 store.register_setting::<MultiKeySettings>(cx);
1771 store.register_setting::<UserSettings>(cx);
1772 store.register_setting::<LanguageSettings>(cx);
1773
1774 // entries added and updated
1775 check_settings_update::<LanguageSettings>(
1776 &mut store,
1777 r#"{
1778 "languages": {
1779 "JSON": {
1780 "language_setting_1": true
1781 }
1782 }
1783 }"#
1784 .unindent(),
1785 |settings| {
1786 settings
1787 .languages
1788 .get_mut("JSON")
1789 .unwrap()
1790 .language_setting_1 = Some(false);
1791 settings.languages.insert(
1792 "Rust".into(),
1793 LanguageSettingEntry {
1794 language_setting_2: Some(true),
1795 ..Default::default()
1796 },
1797 );
1798 },
1799 r#"{
1800 "languages": {
1801 "Rust": {
1802 "language_setting_2": true
1803 },
1804 "JSON": {
1805 "language_setting_1": false
1806 }
1807 }
1808 }"#
1809 .unindent(),
1810 cx,
1811 );
1812
1813 // entries removed
1814 check_settings_update::<LanguageSettings>(
1815 &mut store,
1816 r#"{
1817 "languages": {
1818 "Rust": {
1819 "language_setting_2": true
1820 },
1821 "JSON": {
1822 "language_setting_1": false
1823 }
1824 }
1825 }"#
1826 .unindent(),
1827 |settings| {
1828 settings.languages.remove("JSON").unwrap();
1829 },
1830 r#"{
1831 "languages": {
1832 "Rust": {
1833 "language_setting_2": true
1834 }
1835 }
1836 }"#
1837 .unindent(),
1838 cx,
1839 );
1840
1841 check_settings_update::<LanguageSettings>(
1842 &mut store,
1843 r#"{
1844 "languages": {
1845 "Rust": {
1846 "language_setting_2": true
1847 },
1848 "JSON": {
1849 "language_setting_1": false
1850 }
1851 }
1852 }"#
1853 .unindent(),
1854 |settings| {
1855 settings.languages.remove("Rust").unwrap();
1856 },
1857 r#"{
1858 "languages": {
1859 "JSON": {
1860 "language_setting_1": false
1861 }
1862 }
1863 }"#
1864 .unindent(),
1865 cx,
1866 );
1867
1868 // weird formatting
1869 check_settings_update::<UserSettings>(
1870 &mut store,
1871 r#"{
1872 "user": { "age": 36, "name": "Max", "staff": true }
1873 }"#
1874 .unindent(),
1875 |settings| settings.age = Some(37),
1876 r#"{
1877 "user": { "age": 37, "name": "Max", "staff": true }
1878 }"#
1879 .unindent(),
1880 cx,
1881 );
1882
1883 // single-line formatting, other keys
1884 check_settings_update::<MultiKeySettings>(
1885 &mut store,
1886 r#"{ "one": 1, "two": 2 }"#.unindent(),
1887 |settings| settings.key1 = Some("x".into()),
1888 r#"{ "key1": "x", "one": 1, "two": 2 }"#.unindent(),
1889 cx,
1890 );
1891
1892 // empty object
1893 check_settings_update::<UserSettings>(
1894 &mut store,
1895 r#"{
1896 "user": {}
1897 }"#
1898 .unindent(),
1899 |settings| settings.age = Some(37),
1900 r#"{
1901 "user": {
1902 "age": 37
1903 }
1904 }"#
1905 .unindent(),
1906 cx,
1907 );
1908
1909 // no content
1910 check_settings_update::<UserSettings>(
1911 &mut store,
1912 r#""#.unindent(),
1913 |settings| settings.age = Some(37),
1914 r#"{
1915 "user": {
1916 "age": 37
1917 }
1918 }
1919 "#
1920 .unindent(),
1921 cx,
1922 );
1923
1924 check_settings_update::<UserSettings>(
1925 &mut store,
1926 r#"{
1927 }
1928 "#
1929 .unindent(),
1930 |settings| settings.age = Some(37),
1931 r#"{
1932 "user": {
1933 "age": 37
1934 }
1935 }
1936 "#
1937 .unindent(),
1938 cx,
1939 );
1940 }
1941
1942 #[gpui::test]
1943 fn test_vscode_import(cx: &mut App) {
1944 let mut store = SettingsStore::new(cx);
1945 store.register_setting::<UserSettings>(cx);
1946 store.register_setting::<JournalSettings>(cx);
1947 store.register_setting::<LanguageSettings>(cx);
1948 store.register_setting::<MultiKeySettings>(cx);
1949
1950 // create settings that werent present
1951 check_vscode_import(
1952 &mut store,
1953 r#"{
1954 }
1955 "#
1956 .unindent(),
1957 r#" { "user.age": 37 } "#.to_owned(),
1958 r#"{
1959 "user": {
1960 "age": 37
1961 }
1962 }
1963 "#
1964 .unindent(),
1965 cx,
1966 );
1967
1968 // persist settings that were present
1969 check_vscode_import(
1970 &mut store,
1971 r#"{
1972 "user": {
1973 "staff": true,
1974 "age": 37
1975 }
1976 }
1977 "#
1978 .unindent(),
1979 r#"{ "user.age": 42 }"#.to_owned(),
1980 r#"{
1981 "user": {
1982 "staff": true,
1983 "age": 42
1984 }
1985 }
1986 "#
1987 .unindent(),
1988 cx,
1989 );
1990
1991 // don't clobber settings that aren't present in vscode
1992 check_vscode_import(
1993 &mut store,
1994 r#"{
1995 "user": {
1996 "staff": true,
1997 "age": 37
1998 }
1999 }
2000 "#
2001 .unindent(),
2002 r#"{}"#.to_owned(),
2003 r#"{
2004 "user": {
2005 "staff": true,
2006 "age": 37
2007 }
2008 }
2009 "#
2010 .unindent(),
2011 cx,
2012 );
2013
2014 // custom enum
2015 check_vscode_import(
2016 &mut store,
2017 r#"{
2018 "journal": {
2019 "hour_format": "hour12"
2020 }
2021 }
2022 "#
2023 .unindent(),
2024 r#"{ "time_format": "24" }"#.to_owned(),
2025 r#"{
2026 "journal": {
2027 "hour_format": "hour24"
2028 }
2029 }
2030 "#
2031 .unindent(),
2032 cx,
2033 );
2034
2035 // Multiple keys for one setting
2036 check_vscode_import(
2037 &mut store,
2038 r#"{
2039 "key1": "value"
2040 }
2041 "#
2042 .unindent(),
2043 r#"{
2044 "key_1_first": "hello",
2045 "key_1_second": "world"
2046 }"#
2047 .to_owned(),
2048 r#"{
2049 "key1": "hello world"
2050 }
2051 "#
2052 .unindent(),
2053 cx,
2054 );
2055
2056 // Merging lists together entries added and updated
2057 check_vscode_import(
2058 &mut store,
2059 r#"{
2060 "languages": {
2061 "JSON": {
2062 "language_setting_1": true
2063 },
2064 "Rust": {
2065 "language_setting_2": true
2066 }
2067 }
2068 }"#
2069 .unindent(),
2070 r#"{
2071 "vscode_languages": [
2072 {
2073 "name": "JavaScript",
2074 "language_setting_1": true
2075 },
2076 {
2077 "name": "Rust",
2078 "language_setting_2": false
2079 }
2080 ]
2081 }"#
2082 .to_owned(),
2083 r#"{
2084 "languages": {
2085 "JavaScript": {
2086 "language_setting_1": true
2087 },
2088 "JSON": {
2089 "language_setting_1": true
2090 },
2091 "Rust": {
2092 "language_setting_2": false
2093 }
2094 }
2095 }"#
2096 .unindent(),
2097 cx,
2098 );
2099 }
2100
2101 fn check_vscode_import(
2102 store: &mut SettingsStore,
2103 old: String,
2104 vscode: String,
2105 expected: String,
2106 cx: &mut App,
2107 ) {
2108 store.set_user_settings(&old, cx).ok();
2109 let new = store.get_vscode_edits(
2110 old,
2111 &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
2112 );
2113 pretty_assertions::assert_eq!(new, expected);
2114 }
2115
2116 #[derive(Debug, PartialEq, Deserialize, SettingsUi)]
2117 struct UserSettings {
2118 name: String,
2119 age: u32,
2120 staff: bool,
2121 }
2122
2123 #[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi)]
2124 struct UserSettingsContent {
2125 name: Option<String>,
2126 age: Option<u32>,
2127 staff: Option<bool>,
2128 }
2129
2130 impl Settings for UserSettings {
2131 const KEY: Option<&'static str> = Some("user");
2132 type FileContent = UserSettingsContent;
2133
2134 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
2135 sources.json_merge()
2136 }
2137
2138 fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
2139 vscode.u32_setting("user.age", &mut current.age);
2140 }
2141 }
2142
2143 #[derive(Debug, Deserialize, PartialEq)]
2144 struct TurboSetting(bool);
2145
2146 impl Settings for TurboSetting {
2147 const KEY: Option<&'static str> = Some("turbo");
2148 type FileContent = bool;
2149
2150 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
2151 sources.json_merge()
2152 }
2153
2154 fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut Self::FileContent) {}
2155 }
2156
2157 #[derive(Clone, Debug, PartialEq, Deserialize)]
2158 struct MultiKeySettings {
2159 #[serde(default)]
2160 key1: String,
2161 #[serde(default)]
2162 key2: String,
2163 }
2164
2165 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
2166 struct MultiKeySettingsJson {
2167 key1: Option<String>,
2168 key2: Option<String>,
2169 }
2170
2171 impl Settings for MultiKeySettings {
2172 const KEY: Option<&'static str> = None;
2173
2174 type FileContent = MultiKeySettingsJson;
2175
2176 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
2177 sources.json_merge()
2178 }
2179
2180 fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
2181 let first_value = vscode.read_string("key_1_first");
2182 let second_value = vscode.read_string("key_1_second");
2183
2184 if let Some((first, second)) = first_value.zip(second_value) {
2185 current.key1 = Some(format!("{} {}", first, second));
2186 }
2187 }
2188 }
2189
2190 #[derive(Debug, Deserialize)]
2191 struct JournalSettings {
2192 pub path: String,
2193 pub hour_format: HourFormat,
2194 }
2195
2196 #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
2197 #[serde(rename_all = "snake_case")]
2198 enum HourFormat {
2199 Hour12,
2200 Hour24,
2201 }
2202
2203 #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, SettingsUi)]
2204 struct JournalSettingsJson {
2205 pub path: Option<String>,
2206 pub hour_format: Option<HourFormat>,
2207 }
2208
2209 impl Settings for JournalSettings {
2210 const KEY: Option<&'static str> = Some("journal");
2211
2212 type FileContent = JournalSettingsJson;
2213
2214 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
2215 sources.json_merge()
2216 }
2217
2218 fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
2219 vscode.enum_setting("time_format", &mut current.hour_format, |s| match s {
2220 "12" => Some(HourFormat::Hour12),
2221 "24" => Some(HourFormat::Hour24),
2222 _ => None,
2223 });
2224 }
2225 }
2226
2227 #[gpui::test]
2228 fn test_global_settings(cx: &mut App) {
2229 let mut store = SettingsStore::new(cx);
2230 store.register_setting::<UserSettings>(cx);
2231 store
2232 .set_default_settings(
2233 r#"{
2234 "user": {
2235 "name": "John Doe",
2236 "age": 30,
2237 "staff": false
2238 }
2239 }"#,
2240 cx,
2241 )
2242 .unwrap();
2243
2244 // Set global settings - these should override defaults but not user settings
2245 store
2246 .set_global_settings(
2247 r#"{
2248 "user": {
2249 "name": "Global User",
2250 "age": 35,
2251 "staff": true
2252 }
2253 }"#,
2254 cx,
2255 )
2256 .unwrap();
2257
2258 // Before user settings, global settings should apply
2259 assert_eq!(
2260 store.get::<UserSettings>(None),
2261 &UserSettings {
2262 name: "Global User".to_string(),
2263 age: 35,
2264 staff: true,
2265 }
2266 );
2267
2268 // Set user settings - these should override both defaults and global
2269 store
2270 .set_user_settings(
2271 r#"{
2272 "user": {
2273 "age": 40
2274 }
2275 }"#,
2276 cx,
2277 )
2278 .unwrap();
2279
2280 // User settings should override global settings
2281 assert_eq!(
2282 store.get::<UserSettings>(None),
2283 &UserSettings {
2284 name: "Global User".to_string(), // Name from global settings
2285 age: 40, // Age from user settings
2286 staff: true, // Staff from global settings
2287 }
2288 );
2289 }
2290
2291 #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
2292 struct LanguageSettings {
2293 #[serde(default)]
2294 languages: HashMap<String, LanguageSettingEntry>,
2295 }
2296
2297 #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
2298 struct LanguageSettingEntry {
2299 language_setting_1: Option<bool>,
2300 language_setting_2: Option<bool>,
2301 }
2302
2303 impl Settings for LanguageSettings {
2304 const KEY: Option<&'static str> = None;
2305
2306 type FileContent = Self;
2307
2308 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
2309 sources.json_merge()
2310 }
2311
2312 fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
2313 current.languages.extend(
2314 vscode
2315 .read_value("vscode_languages")
2316 .and_then(|value| value.as_array())
2317 .map(|languages| {
2318 languages
2319 .iter()
2320 .filter_map(|value| value.as_object())
2321 .filter_map(|item| {
2322 let mut rest = item.clone();
2323 let name = rest.remove("name")?.as_str()?.to_string();
2324 let entry = serde_json::from_value::<LanguageSettingEntry>(
2325 serde_json::Value::Object(rest),
2326 )
2327 .ok()?;
2328
2329 Some((name, entry))
2330 })
2331 })
2332 .into_iter()
2333 .flatten(),
2334 );
2335 }
2336 }
2337}