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