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