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