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