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 paths_stack.push(Some((*root_id, directory_path.as_ref())));
948 project_settings_stack.push(local_settings);
949
950 // If a local settings file changed, then avoid recomputing local
951 // settings for any path outside of that directory.
952 if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| {
953 *root_id != changed_root_id || !directory_path.starts_with(changed_local_path)
954 }) {
955 continue;
956 }
957
958 let mut value = setting_value.from_file(&self.default_settings, cx).unwrap();
959 setting_value.refine(value.as_mut(), &refinements, cx);
960 setting_value.refine(value.as_mut(), &project_settings_stack, cx);
961 setting_value.set_local_value(*root_id, directory_path.clone(), value);
962 }
963 }
964 Ok(())
965 }
966
967 pub fn editorconfig_properties(
968 &self,
969 for_worktree: WorktreeId,
970 for_path: &Path,
971 ) -> Option<EditorconfigProperties> {
972 let mut properties = EditorconfigProperties::new();
973
974 for (directory_with_config, _, parsed_editorconfig) in
975 self.local_editorconfig_settings(for_worktree)
976 {
977 if !for_path.starts_with(&directory_with_config) {
978 properties.use_fallbacks();
979 return Some(properties);
980 }
981 let parsed_editorconfig = parsed_editorconfig?;
982 if parsed_editorconfig.is_root {
983 properties = EditorconfigProperties::new();
984 }
985 for section in parsed_editorconfig.sections {
986 section.apply_to(&mut properties, for_path).log_err()?;
987 }
988 }
989
990 properties.use_fallbacks();
991 Some(properties)
992 }
993}
994
995#[derive(Debug, Clone, PartialEq)]
996pub enum InvalidSettingsError {
997 LocalSettings { path: PathBuf, message: String },
998 UserSettings { message: String },
999 ServerSettings { message: String },
1000 DefaultSettings { message: String },
1001 Editorconfig { path: PathBuf, message: String },
1002 Tasks { path: PathBuf, message: String },
1003 Debug { path: PathBuf, message: String },
1004}
1005
1006impl std::fmt::Display for InvalidSettingsError {
1007 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1008 match self {
1009 InvalidSettingsError::LocalSettings { message, .. }
1010 | InvalidSettingsError::UserSettings { message }
1011 | InvalidSettingsError::ServerSettings { message }
1012 | InvalidSettingsError::DefaultSettings { message }
1013 | InvalidSettingsError::Tasks { message, .. }
1014 | InvalidSettingsError::Editorconfig { message, .. }
1015 | InvalidSettingsError::Debug { message, .. } => {
1016 write!(f, "{message}")
1017 }
1018 }
1019 }
1020}
1021impl std::error::Error for InvalidSettingsError {}
1022
1023impl Debug for SettingsStore {
1024 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1025 f.debug_struct("SettingsStore")
1026 .field(
1027 "types",
1028 &self
1029 .setting_values
1030 .values()
1031 .map(|value| value.setting_type_name())
1032 .collect::<Vec<_>>(),
1033 )
1034 .field("default_settings", &self.default_settings)
1035 .field("user_settings", &self.user_settings)
1036 .field("local_settings", &self.local_settings)
1037 .finish_non_exhaustive()
1038 }
1039}
1040
1041impl<T: Settings> AnySettingValue for SettingValue<T> {
1042 fn from_file(&self, s: &SettingsContent, cx: &mut App) -> Option<Box<dyn Any>> {
1043 T::from_file(s, cx).map(|result| Box::new(result) as _)
1044 }
1045
1046 fn refine(&self, value: &mut dyn Any, refinements: &[&SettingsContent], cx: &mut App) {
1047 let value = value.downcast_mut::<T>().unwrap();
1048 for refinement in refinements {
1049 value.refine(refinement, cx)
1050 }
1051 }
1052
1053 fn setting_type_name(&self) -> &'static str {
1054 type_name::<T>()
1055 }
1056
1057 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)> {
1058 self.local_values
1059 .iter()
1060 .map(|(id, path, value)| (*id, path.clone(), value as _))
1061 .collect()
1062 }
1063
1064 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
1065 if let Some(SettingsLocation { worktree_id, path }) = path {
1066 for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
1067 if worktree_id == *settings_root_id && path.starts_with(settings_path) {
1068 return value;
1069 }
1070 }
1071 }
1072
1073 self.global_value
1074 .as_ref()
1075 .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
1076 }
1077
1078 fn set_global_value(&mut self, value: Box<dyn Any>) {
1079 self.global_value = Some(*value.downcast().unwrap());
1080 }
1081
1082 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>) {
1083 let value = *value.downcast().unwrap();
1084 match self
1085 .local_values
1086 .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
1087 {
1088 Ok(ix) => self.local_values[ix].2 = value,
1089 Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
1090 }
1091 }
1092
1093 fn import_from_vscode(
1094 &self,
1095 vscode_settings: &VsCodeSettings,
1096 settings_content: &mut SettingsContent,
1097 ) {
1098 T::import_from_vscode(vscode_settings, settings_content);
1099 }
1100
1101 fn settings_ui_item(&self) -> SettingsUiEntry {
1102 todo!()
1103 // <<T as Settings>::FileContent as SettingsUi>::settings_ui_entry()
1104 }
1105}
1106
1107#[cfg(test)]
1108mod tests {
1109 use std::num::NonZeroU32;
1110
1111 use crate::{
1112 TitleBarSettingsContent, TitleBarVisibilityContent, default_settings,
1113 settings_content::LanguageSettingsContent, test_settings,
1114 };
1115
1116 use super::*;
1117 use unindent::Unindent;
1118 use util::MergeFrom;
1119
1120 #[derive(Debug, PartialEq)]
1121 struct AutoUpdateSetting {
1122 auto_update: bool,
1123 }
1124
1125 impl Settings for AutoUpdateSetting {
1126 fn from_file(content: &SettingsContent, _: &mut App) -> Option<Self> {
1127 content
1128 .auto_update
1129 .map(|auto_update| AutoUpdateSetting { auto_update })
1130 }
1131
1132 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1133 if let Some(auto_update) = content.auto_update {
1134 self.auto_update = auto_update;
1135 }
1136 }
1137
1138 fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {}
1139 }
1140
1141 #[derive(Debug, PartialEq)]
1142 struct TitleBarSettings {
1143 show: TitleBarVisibilityContent,
1144 show_branch_name: bool,
1145 }
1146
1147 impl Settings for TitleBarSettings {
1148 fn from_file(content: &SettingsContent, _: &mut App) -> Option<Self> {
1149 let content = content.title_bar.clone()?;
1150 Some(TitleBarSettings {
1151 show: content.show?,
1152 show_branch_name: content.show_branch_name?,
1153 })
1154 }
1155
1156 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1157 let Some(content) = content.title_bar.as_ref() else {
1158 return;
1159 };
1160 self.show.merge_from(&content.show)
1161 }
1162
1163 fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {}
1164 }
1165
1166 #[derive(Debug, PartialEq)]
1167 struct DefaultLanguageSettings {
1168 tab_size: NonZeroU32,
1169 preferred_line_length: u32,
1170 }
1171
1172 impl Settings for DefaultLanguageSettings {
1173 fn from_file(content: &SettingsContent, _: &mut App) -> Option<Self> {
1174 let content = &content.project.all_languages.defaults;
1175 Some(DefaultLanguageSettings {
1176 tab_size: content.tab_size?,
1177 preferred_line_length: content.preferred_line_length?,
1178 })
1179 }
1180
1181 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1182 let content = &content.project.all_languages.defaults;
1183 self.tab_size.merge_from(&content.tab_size);
1184 self.preferred_line_length
1185 .merge_from(&content.preferred_line_length);
1186 }
1187
1188 fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {}
1189 }
1190
1191 #[gpui::test]
1192 fn test_settings_store_basic(cx: &mut App) {
1193 let mut store = SettingsStore::new(cx, &default_settings());
1194 store.register_setting::<AutoUpdateSetting>(cx);
1195 store.register_setting::<TitleBarSettings>(cx);
1196 store.register_setting::<DefaultLanguageSettings>(cx);
1197
1198 assert_eq!(
1199 store.get::<AutoUpdateSetting>(None),
1200 &AutoUpdateSetting { auto_update: true }
1201 );
1202 assert_eq!(
1203 store.get::<TitleBarSettings>(None).show,
1204 TitleBarVisibilityContent::Always
1205 );
1206
1207 store
1208 .set_user_settings(
1209 r#"{
1210 "auto_update": false,
1211 "title_bar": {
1212 "show": "never"
1213 }
1214 }"#,
1215 cx,
1216 )
1217 .unwrap();
1218
1219 assert_eq!(
1220 store.get::<AutoUpdateSetting>(None),
1221 &AutoUpdateSetting { auto_update: false }
1222 );
1223 assert_eq!(
1224 store.get::<TitleBarSettings>(None).show,
1225 TitleBarVisibilityContent::Never
1226 );
1227
1228 // todo!()
1229 store
1230 .set_local_settings(
1231 WorktreeId::from_usize(1),
1232 Path::new("/root1").into(),
1233 LocalSettingsKind::Settings,
1234 Some(r#"{ "tab_size": 5 }"#),
1235 cx,
1236 )
1237 .unwrap();
1238 store
1239 .set_local_settings(
1240 WorktreeId::from_usize(1),
1241 Path::new("/root1/subdir").into(),
1242 LocalSettingsKind::Settings,
1243 Some(r#"{ "preferred_line_length": 50 }"#),
1244 cx,
1245 )
1246 .unwrap();
1247
1248 store
1249 .set_local_settings(
1250 WorktreeId::from_usize(1),
1251 Path::new("/root2").into(),
1252 LocalSettingsKind::Settings,
1253 Some(r#"{ "tab_size": 9, "title_bar": { "show_branch_name": false } }"#),
1254 cx,
1255 )
1256 .unwrap();
1257
1258 assert_eq!(
1259 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1260 worktree_id: WorktreeId::from_usize(1),
1261 path: Path::new("/root1/something"),
1262 })),
1263 &DefaultLanguageSettings {
1264 preferred_line_length: 80,
1265 tab_size: 5.try_into().unwrap(),
1266 }
1267 );
1268 assert_eq!(
1269 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1270 worktree_id: WorktreeId::from_usize(1),
1271 path: Path::new("/root1/subdir/something")
1272 })),
1273 &DefaultLanguageSettings {
1274 preferred_line_length: 50,
1275 tab_size: 5.try_into().unwrap(),
1276 }
1277 );
1278 assert_eq!(
1279 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1280 worktree_id: WorktreeId::from_usize(1),
1281 path: Path::new("/root2/something")
1282 })),
1283 &DefaultLanguageSettings {
1284 preferred_line_length: 80,
1285 tab_size: 9.try_into().unwrap(),
1286 }
1287 );
1288 assert_eq!(
1289 store.get::<TitleBarSettings>(Some(SettingsLocation {
1290 worktree_id: WorktreeId::from_usize(1),
1291 path: Path::new("/root2/something")
1292 })),
1293 &TitleBarSettings {
1294 show: TitleBarVisibilityContent::Never,
1295 show_branch_name: true,
1296 }
1297 );
1298 }
1299
1300 #[gpui::test]
1301 fn test_setting_store_assign_json_before_register(cx: &mut App) {
1302 let mut store = SettingsStore::new(cx, &test_settings());
1303 store
1304 .set_user_settings(r#"{ "auto_update": false }"#, cx)
1305 .unwrap();
1306 store.register_setting::<AutoUpdateSetting>(cx);
1307 store.register_setting::<TitleBarSettings>(cx);
1308
1309 assert_eq!(
1310 store.get::<AutoUpdateSetting>(None),
1311 &AutoUpdateSetting { auto_update: false }
1312 );
1313 assert_eq!(
1314 store.get::<TitleBarSettings>(None).show,
1315 TitleBarVisibilityContent::Always,
1316 );
1317 }
1318
1319 #[track_caller]
1320 fn check_settings_update(
1321 store: &mut SettingsStore,
1322 old_json: String,
1323 update: fn(&mut SettingsContent),
1324 expected_new_json: String,
1325 cx: &mut App,
1326 ) {
1327 store.set_user_settings(&old_json, cx).ok();
1328 let edits = store.edits_for_update(&old_json, update);
1329 dbg!(&edits);
1330 let mut new_json = old_json;
1331 for (range, replacement) in edits.into_iter() {
1332 new_json.replace_range(range, &replacement);
1333 }
1334 pretty_assertions::assert_eq!(new_json, expected_new_json);
1335 }
1336
1337 #[gpui::test]
1338 fn test_setting_store_update(cx: &mut App) {
1339 let mut store = SettingsStore::new(cx, &test_settings());
1340 // store.register_setting::<MultiKeySettings>(cx);
1341 // store.register_setting::<UserSettings>(cx);
1342 // store.register_setting::<LanguageSettings>(cx);
1343
1344 // entries added and updated
1345 check_settings_update(
1346 &mut store,
1347 r#"{
1348 "languages": {
1349 "JSON": {
1350 "auto_indent": true
1351 }
1352 }
1353 }"#
1354 .unindent(),
1355 |settings| {
1356 settings
1357 .languages_mut()
1358 .get_mut("JSON")
1359 .unwrap()
1360 .auto_indent = Some(false);
1361
1362 settings.languages_mut().insert(
1363 "Rust".into(),
1364 LanguageSettingsContent {
1365 auto_indent: Some(true),
1366 ..Default::default()
1367 },
1368 );
1369 },
1370 r#"{
1371 "languages": {
1372 "Rust": {
1373 "auto_indent": true
1374 },
1375 "JSON": {
1376 "auto_indent": false
1377 }
1378 }
1379 }"#
1380 .unindent(),
1381 cx,
1382 );
1383
1384 // entries removed
1385 check_settings_update(
1386 &mut store,
1387 r#"{
1388 "languages": {
1389 "Rust": {
1390 "language_setting_2": true
1391 },
1392 "JSON": {
1393 "language_setting_1": false
1394 }
1395 }
1396 }"#
1397 .unindent(),
1398 |settings| {
1399 settings.languages_mut().remove("JSON").unwrap();
1400 },
1401 r#"{
1402 "languages": {
1403 "Rust": {
1404 "language_setting_2": true
1405 }
1406 }
1407 }"#
1408 .unindent(),
1409 cx,
1410 );
1411
1412 check_settings_update(
1413 &mut store,
1414 r#"{
1415 "languages": {
1416 "Rust": {
1417 "language_setting_2": true
1418 },
1419 "JSON": {
1420 "language_setting_1": false
1421 }
1422 }
1423 }"#
1424 .unindent(),
1425 |settings| {
1426 settings.languages_mut().remove("Rust").unwrap();
1427 },
1428 r#"{
1429 "languages": {
1430 "JSON": {
1431 "language_setting_1": false
1432 }
1433 }
1434 }"#
1435 .unindent(),
1436 cx,
1437 );
1438
1439 // weird formatting
1440 check_settings_update(
1441 &mut store,
1442 r#"{
1443 "title_bar": { "show": "always", "name": "Max" }
1444 }"#
1445 .unindent(),
1446 |settings| {
1447 dbg!(&settings.title_bar);
1448 settings.title_bar.as_mut().unwrap().show = Some(TitleBarVisibilityContent::Never);
1449 dbg!(&settings.title_bar);
1450 },
1451 r#"{
1452 "title_bar": { "show": "never", "name": "Max" }
1453 }"#
1454 .unindent(),
1455 cx,
1456 );
1457
1458 // single-line formatting, other keys
1459 check_settings_update(
1460 &mut store,
1461 r#"{ "one": 1, "two": 2 }"#.to_owned(),
1462 |settings| settings.auto_update = Some(true),
1463 r#"{ "auto_update": true, "one": 1, "two": 2 }"#.to_owned(),
1464 cx,
1465 );
1466
1467 // empty object
1468 check_settings_update(
1469 &mut store,
1470 r#"{
1471 "title_bar": {}
1472 }"#
1473 .unindent(),
1474 |settings| settings.title_bar.as_mut().unwrap().show_menus = Some(true),
1475 r#"{
1476 "title_bar": {
1477 "show_menus": true
1478 }
1479 }"#
1480 .unindent(),
1481 cx,
1482 );
1483
1484 // no content
1485 check_settings_update(
1486 &mut store,
1487 r#""#.unindent(),
1488 |settings| {
1489 settings.title_bar = Some(TitleBarSettingsContent {
1490 show_branch_name: Some(true),
1491 ..Default::default()
1492 })
1493 },
1494 r#"{
1495 "title_bar": {
1496 "show_branch_name": true
1497 }
1498 }
1499 "#
1500 .unindent(),
1501 cx,
1502 );
1503
1504 check_settings_update(
1505 &mut store,
1506 r#"{
1507 }
1508 "#
1509 .unindent(),
1510 |settings| settings.title_bar.get_or_insert_default().show_branch_name = Some(true),
1511 r#"{
1512 "title_bar": {
1513 "show_branch_name": true
1514 }
1515 }
1516 "#
1517 .unindent(),
1518 cx,
1519 );
1520 }
1521
1522 // #[gpui::test]
1523 // fn test_vscode_import(cx: &mut App) {
1524 // let mut store = SettingsStore::new(cx);
1525 // store.register_setting::<UserSettings>(cx);
1526 // store.register_setting::<JournalSettings>(cx);
1527 // store.register_setting::<LanguageSettings>(cx);
1528 // store.register_setting::<MultiKeySettings>(cx);
1529
1530 // // create settings that werent present
1531 // check_vscode_import(
1532 // &mut store,
1533 // r#"{
1534 // }
1535 // "#
1536 // .unindent(),
1537 // r#" { "user.age": 37 } "#.to_owned(),
1538 // r#"{
1539 // "user": {
1540 // "age": 37
1541 // }
1542 // }
1543 // "#
1544 // .unindent(),
1545 // cx,
1546 // );
1547
1548 // // persist settings that were present
1549 // check_vscode_import(
1550 // &mut store,
1551 // r#"{
1552 // "user": {
1553 // "staff": true,
1554 // "age": 37
1555 // }
1556 // }
1557 // "#
1558 // .unindent(),
1559 // r#"{ "user.age": 42 }"#.to_owned(),
1560 // r#"{
1561 // "user": {
1562 // "staff": true,
1563 // "age": 42
1564 // }
1565 // }
1566 // "#
1567 // .unindent(),
1568 // cx,
1569 // );
1570
1571 // // don't clobber settings that aren't present in vscode
1572 // check_vscode_import(
1573 // &mut store,
1574 // r#"{
1575 // "user": {
1576 // "staff": true,
1577 // "age": 37
1578 // }
1579 // }
1580 // "#
1581 // .unindent(),
1582 // r#"{}"#.to_owned(),
1583 // r#"{
1584 // "user": {
1585 // "staff": true,
1586 // "age": 37
1587 // }
1588 // }
1589 // "#
1590 // .unindent(),
1591 // cx,
1592 // );
1593
1594 // // custom enum
1595 // check_vscode_import(
1596 // &mut store,
1597 // r#"{
1598 // "journal": {
1599 // "hour_format": "hour12"
1600 // }
1601 // }
1602 // "#
1603 // .unindent(),
1604 // r#"{ "time_format": "24" }"#.to_owned(),
1605 // r#"{
1606 // "journal": {
1607 // "hour_format": "hour24"
1608 // }
1609 // }
1610 // "#
1611 // .unindent(),
1612 // cx,
1613 // );
1614
1615 // // Multiple keys for one setting
1616 // check_vscode_import(
1617 // &mut store,
1618 // r#"{
1619 // "key1": "value"
1620 // }
1621 // "#
1622 // .unindent(),
1623 // r#"{
1624 // "key_1_first": "hello",
1625 // "key_1_second": "world"
1626 // }"#
1627 // .to_owned(),
1628 // r#"{
1629 // "key1": "hello world"
1630 // }
1631 // "#
1632 // .unindent(),
1633 // cx,
1634 // );
1635
1636 // // Merging lists together entries added and updated
1637 // check_vscode_import(
1638 // &mut store,
1639 // r#"{
1640 // "languages": {
1641 // "JSON": {
1642 // "language_setting_1": true
1643 // },
1644 // "Rust": {
1645 // "language_setting_2": true
1646 // }
1647 // }
1648 // }"#
1649 // .unindent(),
1650 // r#"{
1651 // "vscode_languages": [
1652 // {
1653 // "name": "JavaScript",
1654 // "language_setting_1": true
1655 // },
1656 // {
1657 // "name": "Rust",
1658 // "language_setting_2": false
1659 // }
1660 // ]
1661 // }"#
1662 // .to_owned(),
1663 // r#"{
1664 // "languages": {
1665 // "JavaScript": {
1666 // "language_setting_1": true
1667 // },
1668 // "JSON": {
1669 // "language_setting_1": true
1670 // },
1671 // "Rust": {
1672 // "language_setting_2": false
1673 // }
1674 // }
1675 // }"#
1676 // .unindent(),
1677 // cx,
1678 // );
1679 // }
1680
1681 // fn check_vscode_import(
1682 // store: &mut SettingsStore,
1683 // old: String,
1684 // vscode: String,
1685 // expected: String,
1686 // cx: &mut App,
1687 // ) {
1688 // store.set_user_settings(&old, cx).ok();
1689 // let new = store.get_vscode_edits(
1690 // old,
1691 // &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
1692 // );
1693 // pretty_assertions::assert_eq!(new, expected);
1694 // }
1695
1696 // #[derive(Debug, PartialEq, Deserialize, SettingsUi)]
1697 // struct UserSettings {
1698 // name: String,
1699 // age: u32,
1700 // staff: bool,
1701 // }
1702
1703 // #[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
1704 // #[settings_key(key = "user")]
1705 // struct UserSettingsContent {
1706 // name: Option<String>,
1707 // age: Option<u32>,
1708 // staff: Option<bool>,
1709 // }
1710
1711 // impl Settings for UserSettings {
1712 // type FileContent = UserSettingsContent;
1713
1714 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1715 // sources.json_merge()
1716 // }
1717
1718 // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
1719 // vscode.u32_setting("user.age", &mut current.age);
1720 // }
1721 // }
1722
1723 // #[derive(Debug, Deserialize, PartialEq)]
1724 // struct TurboSetting(bool);
1725
1726 // #[derive(
1727 // Copy,
1728 // Clone,
1729 // PartialEq,
1730 // Eq,
1731 // Debug,
1732 // Default,
1733 // serde::Serialize,
1734 // serde::Deserialize,
1735 // SettingsUi,
1736 // SettingsKey,
1737 // JsonSchema,
1738 // )]
1739 // #[serde(default)]
1740 // #[settings_key(None)]
1741 // pub struct TurboSettingContent {
1742 // turbo: Option<bool>,
1743 // }
1744
1745 // impl Settings for TurboSetting {
1746 // type FileContent = TurboSettingContent;
1747
1748 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1749 // Ok(Self(
1750 // sources
1751 // .user
1752 // .or(sources.server)
1753 // .unwrap_or(sources.default)
1754 // .turbo
1755 // .unwrap_or_default(),
1756 // ))
1757 // }
1758
1759 // fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut Self::FileContent) {}
1760 // }
1761
1762 // #[derive(Clone, Debug, PartialEq, Deserialize)]
1763 // struct MultiKeySettings {
1764 // #[serde(default)]
1765 // key1: String,
1766 // #[serde(default)]
1767 // key2: String,
1768 // }
1769
1770 // #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
1771 // #[settings_key(None)]
1772 // struct MultiKeySettingsJson {
1773 // key1: Option<String>,
1774 // key2: Option<String>,
1775 // }
1776
1777 // impl Settings for MultiKeySettings {
1778 // type FileContent = MultiKeySettingsJson;
1779
1780 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1781 // sources.json_merge()
1782 // }
1783
1784 // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
1785 // let first_value = vscode.read_string("key_1_first");
1786 // let second_value = vscode.read_string("key_1_second");
1787
1788 // if let Some((first, second)) = first_value.zip(second_value) {
1789 // current.key1 = Some(format!("{} {}", first, second));
1790 // }
1791 // }
1792 // }
1793
1794 // #[derive(Debug, Deserialize)]
1795 // struct JournalSettings {
1796 // #[expect(unused)]
1797 // pub path: String,
1798 // #[expect(unused)]
1799 // pub hour_format: HourFormat,
1800 // }
1801
1802 // #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1803 // #[serde(rename_all = "snake_case")]
1804 // enum HourFormat {
1805 // Hour12,
1806 // Hour24,
1807 // }
1808
1809 // #[derive(
1810 // Clone, Default, Debug, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey,
1811 // )]
1812 // #[settings_key(key = "journal")]
1813 // struct JournalSettingsJson {
1814 // pub path: Option<String>,
1815 // pub hour_format: Option<HourFormat>,
1816 // }
1817
1818 // impl Settings for JournalSettings {
1819 // type FileContent = JournalSettingsJson;
1820
1821 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1822 // sources.json_merge()
1823 // }
1824
1825 // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
1826 // vscode.enum_setting("time_format", &mut current.hour_format, |s| match s {
1827 // "12" => Some(HourFormat::Hour12),
1828 // "24" => Some(HourFormat::Hour24),
1829 // _ => None,
1830 // });
1831 // }
1832 // }
1833
1834 // #[gpui::test]
1835 // fn test_global_settings(cx: &mut App) {
1836 // let mut store = SettingsStore::new(cx);
1837 // store.register_setting::<UserSettings>(cx);
1838 // store
1839 // .set_default_settings(
1840 // r#"{
1841 // "user": {
1842 // "name": "John Doe",
1843 // "age": 30,
1844 // "staff": false
1845 // }
1846 // }"#,
1847 // cx,
1848 // )
1849 // .unwrap();
1850
1851 // // Set global settings - these should override defaults but not user settings
1852 // store
1853 // .set_global_settings(
1854 // r#"{
1855 // "user": {
1856 // "name": "Global User",
1857 // "age": 35,
1858 // "staff": true
1859 // }
1860 // }"#,
1861 // cx,
1862 // )
1863 // .unwrap();
1864
1865 // // Before user settings, global settings should apply
1866 // assert_eq!(
1867 // store.get::<UserSettings>(None),
1868 // &UserSettings {
1869 // name: "Global User".to_string(),
1870 // age: 35,
1871 // staff: true,
1872 // }
1873 // );
1874
1875 // // Set user settings - these should override both defaults and global
1876 // store
1877 // .set_user_settings(
1878 // r#"{
1879 // "user": {
1880 // "age": 40
1881 // }
1882 // }"#,
1883 // cx,
1884 // )
1885 // .unwrap();
1886
1887 // // User settings should override global settings
1888 // assert_eq!(
1889 // store.get::<UserSettings>(None),
1890 // &UserSettings {
1891 // name: "Global User".to_string(), // Name from global settings
1892 // age: 40, // Age from user settings
1893 // staff: true, // Staff from global settings
1894 // }
1895 // );
1896 // }
1897
1898 // #[derive(
1899 // Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey,
1900 // )]
1901 // #[settings_key(None)]
1902 // struct LanguageSettings {
1903 // #[serde(default)]
1904 // languages: HashMap<String, LanguageSettingEntry>,
1905 // }
1906
1907 // #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
1908 // struct LanguageSettingEntry {
1909 // language_setting_1: Option<bool>,
1910 // language_setting_2: Option<bool>,
1911 // }
1912
1913 // impl Settings for LanguageSettings {
1914 // type FileContent = Self;
1915
1916 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1917 // sources.json_merge()
1918 // }
1919
1920 // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
1921 // current.languages.extend(
1922 // vscode
1923 // .read_value("vscode_languages")
1924 // .and_then(|value| value.as_array())
1925 // .map(|languages| {
1926 // languages
1927 // .iter()
1928 // .filter_map(|value| value.as_object())
1929 // .filter_map(|item| {
1930 // let mut rest = item.clone();
1931 // let name = rest.remove("name")?.as_str()?.to_string();
1932 // let entry = serde_json::from_value::<LanguageSettingEntry>(
1933 // serde_json::Value::Object(rest),
1934 // )
1935 // .ok()?;
1936
1937 // Some((name, entry))
1938 // })
1939 // })
1940 // .into_iter()
1941 // .flatten(),
1942 // );
1943 // }
1944 // }
1945}