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