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