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