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