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: SettingsContent,
144 user_settings: Option<UserSettingsContent>,
145 global_settings: Option<SettingsContent>,
146
147 extension_settings: Option<SettingsContent>,
148 server_settings: Option<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_ref() {
264 refinements.push(extension_settings)
265 }
266
267 if let Some(global_settings) = self.global_settings.as_ref() {
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 let new_text = update(old_text, cx)?;
428 let settings_path = paths::settings_file().as_path();
429 if fs.is_file(settings_path).await {
430 let resolved_path =
431 fs.canonicalize(settings_path).await.with_context(|| {
432 format!(
433 "Failed to canonicalize settings path {:?}",
434 settings_path
435 )
436 })?;
437
438 fs.atomic_write(resolved_path.clone(), new_text)
439 .await
440 .with_context(|| {
441 format!("Failed to write settings to file {:?}", resolved_path)
442 })?;
443 } else {
444 fs.atomic_write(settings_path.to_path_buf(), new_text)
445 .await
446 .with_context(|| {
447 format!("Failed to write settings to file {:?}", settings_path)
448 })?;
449 }
450 anyhow::Ok(())
451 }
452 .await;
453
454 let new_res = match &res {
455 Ok(_) => anyhow::Ok(()),
456 Err(e) => Err(anyhow::anyhow!("Failed to write settings to file {:?}", e)),
457 };
458
459 _ = tx.send(new_res);
460 res
461 }
462 .boxed_local()
463 }))
464 .map_err(|err| anyhow::format_err!("Failed to update settings file: {}", err))
465 .log_with_level(log::Level::Warn);
466 return rx;
467 }
468
469 pub fn update_settings_file_at_path(
470 &self,
471 fs: Arc<dyn Fs>,
472 path: &[impl AsRef<str>],
473 new_value: serde_json::Value,
474 ) -> oneshot::Receiver<Result<()>> {
475 let key_path = path
476 .into_iter()
477 .map(AsRef::as_ref)
478 .map(SharedString::new)
479 .collect::<Vec<_>>();
480 let update = move |mut old_text: String, cx: AsyncApp| {
481 cx.read_global(|store: &SettingsStore, _cx| {
482 // todo(settings_ui) use `update_value_in_json_text` for merging new and old objects with comment preservation, needs old value though...
483 let (range, replacement) = replace_value_in_json_text(
484 &old_text,
485 key_path.as_slice(),
486 store.json_tab_size(),
487 Some(&new_value),
488 None,
489 );
490 old_text.replace_range(range, &replacement);
491 old_text
492 })
493 };
494 self.update_settings_file_inner(fs, update)
495 }
496
497 pub fn update_settings_file(
498 &self,
499 fs: Arc<dyn Fs>,
500 update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
501 ) {
502 _ = self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
503 cx.read_global(|store: &SettingsStore, cx| {
504 store.new_text_for_update(old_text, |content| update(content, cx))
505 })
506 });
507 }
508
509 pub fn import_vscode_settings(
510 &self,
511 fs: Arc<dyn Fs>,
512 vscode_settings: VsCodeSettings,
513 ) -> oneshot::Receiver<Result<()>> {
514 self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
515 cx.read_global(|store: &SettingsStore, _cx| {
516 store.get_vscode_edits(old_text, &vscode_settings)
517 })
518 })
519 }
520
521 pub fn settings_ui_items(&self) -> impl IntoIterator<Item = SettingsUiEntry> {
522 [].into_iter()
523 }
524}
525
526impl SettingsStore {
527 /// Updates the value of a setting in a JSON file, returning the new text
528 /// for that JSON file.
529 pub fn new_text_for_update(
530 &self,
531 old_text: String,
532 update: impl FnOnce(&mut SettingsContent),
533 ) -> String {
534 let edits = self.edits_for_update(&old_text, update);
535 let mut new_text = old_text;
536 for (range, replacement) in edits.into_iter() {
537 new_text.replace_range(range, &replacement);
538 }
539 new_text
540 }
541
542 pub fn get_vscode_edits(&self, old_text: String, vscode: &VsCodeSettings) -> String {
543 self.new_text_for_update(old_text, |settings_content| {
544 for v in self.setting_values.values() {
545 v.import_from_vscode(vscode, settings_content)
546 }
547 })
548 }
549
550 /// Updates the value of a setting in a JSON file, returning a list
551 /// of edits to apply to the JSON file.
552 pub fn edits_for_update(
553 &self,
554 text: &str,
555 update: impl FnOnce(&mut SettingsContent),
556 ) -> Vec<(Range<usize>, String)> {
557 let old_content: UserSettingsContent = serde_json::from_str(text).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 &[], // todo!() is this still needed?
575 &mut edits,
576 );
577 edits
578 }
579
580 pub fn json_tab_size(&self) -> usize {
581 2
582 }
583
584 /// Sets the default settings via a JSON string.
585 ///
586 /// The string should contain a JSON object with a default value for every setting.
587 pub fn set_default_settings(
588 &mut self,
589 default_settings_content: &str,
590 cx: &mut App,
591 ) -> Result<()> {
592 self.default_settings = parse_json_with_comments(default_settings_content)?;
593 self.recompute_values(None, cx)?;
594 Ok(())
595 }
596
597 /// Sets the user settings via a JSON string.
598 pub fn set_user_settings(&mut self, user_settings_content: &str, cx: &mut App) -> Result<()> {
599 let settings: UserSettingsContent = if user_settings_content.is_empty() {
600 parse_json_with_comments("{}")?
601 } else {
602 parse_json_with_comments(user_settings_content)?
603 };
604
605 self.user_settings = Some(settings);
606 self.recompute_values(None, cx)?;
607 Ok(())
608 }
609
610 /// Sets the global settings via a JSON string.
611 pub fn set_global_settings(
612 &mut self,
613 global_settings_content: &str,
614 cx: &mut App,
615 ) -> Result<()> {
616 let settings: SettingsContent = if global_settings_content.is_empty() {
617 parse_json_with_comments("{}")?
618 } else {
619 parse_json_with_comments(global_settings_content)?
620 };
621
622 self.global_settings = Some(settings);
623 self.recompute_values(None, cx)?;
624 Ok(())
625 }
626
627 pub fn set_server_settings(
628 &mut self,
629 server_settings_content: &str,
630 cx: &mut App,
631 ) -> Result<()> {
632 let settings: Option<ServerSettingsContent> = if server_settings_content.is_empty() {
633 None
634 } else {
635 parse_json_with_comments(server_settings_content)?
636 };
637
638 // Rewrite the server settings into a content type
639 self.server_settings = settings.map(|settings| SettingsContent {
640 project: settings.project,
641 ..Default::default()
642 });
643
644 self.recompute_values(None, cx)?;
645 Ok(())
646 }
647
648 /// Add or remove a set of local settings via a JSON string.
649 pub fn set_local_settings(
650 &mut self,
651 root_id: WorktreeId,
652 directory_path: Arc<Path>,
653 kind: LocalSettingsKind,
654 settings_content: Option<&str>,
655 cx: &mut App,
656 ) -> std::result::Result<(), InvalidSettingsError> {
657 let mut zed_settings_changed = false;
658 match (
659 kind,
660 settings_content
661 .map(|content| content.trim())
662 .filter(|content| !content.is_empty()),
663 ) {
664 (LocalSettingsKind::Tasks, _) => {
665 return Err(InvalidSettingsError::Tasks {
666 message: "Attempted to submit tasks into the settings store".to_string(),
667 path: directory_path.join(task_file_name()),
668 });
669 }
670 (LocalSettingsKind::Debug, _) => {
671 return Err(InvalidSettingsError::Debug {
672 message: "Attempted to submit debugger config into the settings store"
673 .to_string(),
674 path: directory_path.join(task_file_name()),
675 });
676 }
677 (LocalSettingsKind::Settings, None) => {
678 zed_settings_changed = self
679 .local_settings
680 .remove(&(root_id, directory_path.clone()))
681 .is_some()
682 }
683 (LocalSettingsKind::Editorconfig, None) => {
684 self.raw_editorconfig_settings
685 .remove(&(root_id, directory_path.clone()));
686 }
687 (LocalSettingsKind::Settings, Some(settings_contents)) => {
688 let new_settings = parse_json_with_comments::<ProjectSettingsContent>(
689 settings_contents,
690 )
691 .map_err(|e| InvalidSettingsError::LocalSettings {
692 path: directory_path.join(local_settings_file_relative_path()),
693 message: e.to_string(),
694 })?;
695 match self.local_settings.entry((root_id, directory_path.clone())) {
696 btree_map::Entry::Vacant(v) => {
697 v.insert(SettingsContent {
698 project: new_settings,
699 ..Default::default()
700 });
701 zed_settings_changed = true;
702 }
703 btree_map::Entry::Occupied(mut o) => {
704 if &o.get().project != &new_settings {
705 o.insert(SettingsContent {
706 project: new_settings,
707 ..Default::default()
708 });
709 zed_settings_changed = true;
710 }
711 }
712 }
713 }
714 (LocalSettingsKind::Editorconfig, Some(editorconfig_contents)) => {
715 match self
716 .raw_editorconfig_settings
717 .entry((root_id, directory_path.clone()))
718 {
719 btree_map::Entry::Vacant(v) => match editorconfig_contents.parse() {
720 Ok(new_contents) => {
721 v.insert((editorconfig_contents.to_owned(), Some(new_contents)));
722 }
723 Err(e) => {
724 v.insert((editorconfig_contents.to_owned(), None));
725 return Err(InvalidSettingsError::Editorconfig {
726 message: e.to_string(),
727 path: directory_path.join(EDITORCONFIG_NAME),
728 });
729 }
730 },
731 btree_map::Entry::Occupied(mut o) => {
732 if o.get().0 != editorconfig_contents {
733 match editorconfig_contents.parse() {
734 Ok(new_contents) => {
735 o.insert((
736 editorconfig_contents.to_owned(),
737 Some(new_contents),
738 ));
739 }
740 Err(e) => {
741 o.insert((editorconfig_contents.to_owned(), None));
742 return Err(InvalidSettingsError::Editorconfig {
743 message: e.to_string(),
744 path: directory_path.join(EDITORCONFIG_NAME),
745 });
746 }
747 }
748 }
749 }
750 }
751 }
752 };
753
754 if zed_settings_changed {
755 self.recompute_values(Some((root_id, &directory_path)), cx)?;
756 }
757 Ok(())
758 }
759
760 pub fn set_extension_settings(
761 &mut self,
762 content: ExtensionsSettingsContent,
763 cx: &mut App,
764 ) -> Result<()> {
765 self.extension_settings = Some(SettingsContent {
766 project: ProjectSettingsContent {
767 all_languages: content.all_languages,
768 ..Default::default()
769 },
770 ..Default::default()
771 });
772 self.recompute_values(None, cx)?;
773 Ok(())
774 }
775
776 /// Add or remove a set of local settings via a JSON string.
777 pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut App) -> Result<()> {
778 self.local_settings
779 .retain(|(worktree_id, _), _| worktree_id != &root_id);
780 self.recompute_values(Some((root_id, "".as_ref())), cx)?;
781 Ok(())
782 }
783
784 pub fn local_settings(
785 &self,
786 root_id: WorktreeId,
787 ) -> impl '_ + Iterator<Item = (Arc<Path>, String)> {
788 self.local_settings
789 .range(
790 (root_id, Path::new("").into())
791 ..(
792 WorktreeId::from_usize(root_id.to_usize() + 1),
793 Path::new("").into(),
794 ),
795 )
796 .map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap()))
797 }
798
799 pub fn local_editorconfig_settings(
800 &self,
801 root_id: WorktreeId,
802 ) -> impl '_ + Iterator<Item = (Arc<Path>, String, Option<Editorconfig>)> {
803 self.raw_editorconfig_settings
804 .range(
805 (root_id, Path::new("").into())
806 ..(
807 WorktreeId::from_usize(root_id.to_usize() + 1),
808 Path::new("").into(),
809 ),
810 )
811 .map(|((_, path), (content, parsed_content))| {
812 (path.clone(), content.clone(), parsed_content.clone())
813 })
814 }
815
816 pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value {
817 let mut generator = schemars::generate::SchemaSettings::draft2019_09()
818 .with_transform(DefaultDenyUnknownFields)
819 .into_generator();
820
821 let schema = UserSettingsContent::json_schema(&mut generator);
822
823 // add schemas which are determined at runtime
824 for parameterized_json_schema in inventory::iter::<ParameterizedJsonSchema>() {
825 (parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx);
826 }
827
828 schema.to_value()
829 }
830
831 fn recompute_values(
832 &mut self,
833 changed_local_path: Option<(WorktreeId, &Path)>,
834 cx: &mut App,
835 ) -> std::result::Result<(), InvalidSettingsError> {
836 // Reload the global and local values for every setting.
837 let mut project_settings_stack = Vec::<&SettingsContent>::new();
838 let mut paths_stack = Vec::<Option<(WorktreeId, &Path)>>::new();
839
840 let mut refinements = Vec::default();
841
842 if let Some(extension_settings) = self.extension_settings.as_ref() {
843 refinements.push(extension_settings)
844 }
845
846 if let Some(global_settings) = self.global_settings.as_ref() {
847 refinements.push(global_settings)
848 }
849
850 if let Some(user_settings) = self.user_settings.as_ref() {
851 refinements.push(&user_settings.content);
852 if let Some(release_channel) = user_settings.for_release_channel() {
853 refinements.push(release_channel)
854 }
855 if let Some(os) = user_settings.for_os() {
856 refinements.push(os)
857 }
858 if let Some(profile) = user_settings.for_profile(cx) {
859 refinements.push(profile)
860 }
861 }
862
863 if let Some(server_settings) = self.server_settings.as_ref() {
864 refinements.push(server_settings)
865 }
866
867 for setting_value in self.setting_values.values_mut() {
868 // If the global settings file changed, reload the global value for the field.
869 if changed_local_path.is_none() {
870 let mut value = setting_value.from_default(&self.default_settings, cx);
871 setting_value.refine(value.as_mut(), &refinements, cx);
872 setting_value.set_global_value(value);
873 }
874
875 // Reload the local values for the setting.
876 paths_stack.clear();
877 project_settings_stack.clear();
878 for ((root_id, directory_path), local_settings) in &self.local_settings {
879 // Build a stack of all of the local values for that setting.
880 while let Some(prev_entry) = paths_stack.last() {
881 if let Some((prev_root_id, prev_path)) = prev_entry
882 && (root_id != prev_root_id || !directory_path.starts_with(prev_path))
883 {
884 paths_stack.pop();
885 project_settings_stack.pop();
886 continue;
887 }
888 break;
889 }
890
891 paths_stack.push(Some((*root_id, directory_path.as_ref())));
892 project_settings_stack.push(local_settings);
893
894 // If a local settings file changed, then avoid recomputing local
895 // settings for any path outside of that directory.
896 if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| {
897 *root_id != changed_root_id || !directory_path.starts_with(changed_local_path)
898 }) {
899 continue;
900 }
901
902 let mut value = setting_value.from_default(&self.default_settings, cx);
903 setting_value.refine(value.as_mut(), &refinements, cx);
904 setting_value.refine(value.as_mut(), &project_settings_stack, cx);
905 setting_value.set_local_value(*root_id, directory_path.clone(), value);
906 }
907 }
908 Ok(())
909 }
910
911 pub fn editorconfig_properties(
912 &self,
913 for_worktree: WorktreeId,
914 for_path: &Path,
915 ) -> Option<EditorconfigProperties> {
916 let mut properties = EditorconfigProperties::new();
917
918 for (directory_with_config, _, parsed_editorconfig) in
919 self.local_editorconfig_settings(for_worktree)
920 {
921 if !for_path.starts_with(&directory_with_config) {
922 properties.use_fallbacks();
923 return Some(properties);
924 }
925 let parsed_editorconfig = parsed_editorconfig?;
926 if parsed_editorconfig.is_root {
927 properties = EditorconfigProperties::new();
928 }
929 for section in parsed_editorconfig.sections {
930 section.apply_to(&mut properties, for_path).log_err()?;
931 }
932 }
933
934 properties.use_fallbacks();
935 Some(properties)
936 }
937}
938
939#[derive(Debug, Clone, PartialEq)]
940pub enum InvalidSettingsError {
941 LocalSettings { path: PathBuf, message: String },
942 UserSettings { message: String },
943 ServerSettings { message: String },
944 DefaultSettings { message: String },
945 Editorconfig { path: PathBuf, message: String },
946 Tasks { path: PathBuf, message: String },
947 Debug { path: PathBuf, message: String },
948}
949
950impl std::fmt::Display for InvalidSettingsError {
951 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
952 match self {
953 InvalidSettingsError::LocalSettings { message, .. }
954 | InvalidSettingsError::UserSettings { message }
955 | InvalidSettingsError::ServerSettings { message }
956 | InvalidSettingsError::DefaultSettings { message }
957 | InvalidSettingsError::Tasks { message, .. }
958 | InvalidSettingsError::Editorconfig { message, .. }
959 | InvalidSettingsError::Debug { message, .. } => {
960 write!(f, "{message}")
961 }
962 }
963 }
964}
965impl std::error::Error for InvalidSettingsError {}
966
967impl Debug for SettingsStore {
968 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
969 f.debug_struct("SettingsStore")
970 .field(
971 "types",
972 &self
973 .setting_values
974 .values()
975 .map(|value| value.setting_type_name())
976 .collect::<Vec<_>>(),
977 )
978 .field("default_settings", &self.default_settings)
979 .field("user_settings", &self.user_settings)
980 .field("local_settings", &self.local_settings)
981 .finish_non_exhaustive()
982 }
983}
984
985impl<T: Settings> AnySettingValue for SettingValue<T> {
986 fn from_default(&self, s: &SettingsContent, cx: &mut App) -> Box<dyn Any> {
987 Box::new(T::from_defaults(s, cx)) as _
988 }
989
990 fn refine(&self, value: &mut dyn Any, refinements: &[&SettingsContent], cx: &mut App) {
991 let value = value.downcast_mut::<T>().unwrap();
992 for refinement in refinements {
993 value.refine(refinement, cx)
994 }
995 }
996
997 fn setting_type_name(&self) -> &'static str {
998 type_name::<T>()
999 }
1000
1001 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)> {
1002 self.local_values
1003 .iter()
1004 .map(|(id, path, value)| (*id, path.clone(), value as _))
1005 .collect()
1006 }
1007
1008 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
1009 if let Some(SettingsLocation { worktree_id, path }) = path {
1010 for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
1011 if worktree_id == *settings_root_id && path.starts_with(settings_path) {
1012 return value;
1013 }
1014 }
1015 }
1016
1017 self.global_value
1018 .as_ref()
1019 .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
1020 }
1021
1022 fn set_global_value(&mut self, value: Box<dyn Any>) {
1023 self.global_value = Some(*value.downcast().unwrap());
1024 }
1025
1026 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>) {
1027 let value = *value.downcast().unwrap();
1028 match self
1029 .local_values
1030 .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
1031 {
1032 Ok(ix) => self.local_values[ix].2 = value,
1033 Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
1034 }
1035 }
1036
1037 fn import_from_vscode(
1038 &self,
1039 vscode_settings: &VsCodeSettings,
1040 settings_content: &mut SettingsContent,
1041 ) {
1042 T::import_from_vscode(vscode_settings, settings_content);
1043 }
1044}
1045
1046#[cfg(test)]
1047mod tests {
1048 use std::num::NonZeroU32;
1049
1050 use crate::{
1051 TitleBarSettingsContent, TitleBarVisibilityContent, VsCodeSettingsSource, default_settings,
1052 settings_content::LanguageSettingsContent, test_settings,
1053 };
1054
1055 use super::*;
1056 use unindent::Unindent;
1057 use util::MergeFrom;
1058
1059 #[derive(Debug, PartialEq)]
1060 struct AutoUpdateSetting {
1061 auto_update: bool,
1062 }
1063
1064 impl Settings for AutoUpdateSetting {
1065 fn from_defaults(content: &SettingsContent, _: &mut App) -> Self {
1066 AutoUpdateSetting {
1067 auto_update: content.auto_update.unwrap(),
1068 }
1069 }
1070
1071 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1072 if let Some(auto_update) = content.auto_update {
1073 self.auto_update = auto_update;
1074 }
1075 }
1076
1077 fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {}
1078 }
1079
1080 #[derive(Debug, PartialEq)]
1081 struct TitleBarSettings {
1082 show: TitleBarVisibilityContent,
1083 show_branch_name: bool,
1084 }
1085
1086 impl Settings for TitleBarSettings {
1087 fn from_defaults(content: &SettingsContent, _: &mut App) -> Self {
1088 let content = content.title_bar.clone().unwrap();
1089 TitleBarSettings {
1090 show: content.show.unwrap(),
1091 show_branch_name: content.show_branch_name.unwrap(),
1092 }
1093 }
1094
1095 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1096 let Some(content) = content.title_bar.as_ref() else {
1097 return;
1098 };
1099 self.show.merge_from(&content.show)
1100 }
1101
1102 fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
1103 let mut show = None;
1104
1105 vscode.enum_setting("window.titleBarStyle", &mut show, |value| match value {
1106 "never" => Some(TitleBarVisibilityContent::Never),
1107 "always" => Some(TitleBarVisibilityContent::Always),
1108 _ => None,
1109 });
1110 if let Some(show) = show {
1111 content.title_bar.get_or_insert_default().show.replace(show);
1112 }
1113 }
1114 }
1115
1116 #[derive(Debug, PartialEq)]
1117 struct DefaultLanguageSettings {
1118 tab_size: NonZeroU32,
1119 preferred_line_length: u32,
1120 }
1121
1122 impl Settings for DefaultLanguageSettings {
1123 fn from_defaults(content: &SettingsContent, _: &mut App) -> Self {
1124 let content = &content.project.all_languages.defaults;
1125 DefaultLanguageSettings {
1126 tab_size: content.tab_size.unwrap(),
1127 preferred_line_length: content.preferred_line_length.unwrap(),
1128 }
1129 }
1130
1131 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1132 let content = &content.project.all_languages.defaults;
1133 self.tab_size.merge_from(&content.tab_size);
1134 self.preferred_line_length
1135 .merge_from(&content.preferred_line_length);
1136 }
1137
1138 fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
1139 let content = &mut content.project.all_languages.defaults;
1140
1141 if let Some(size) = vscode
1142 .read_value("editor.tabSize")
1143 .and_then(|v| v.as_u64())
1144 .and_then(|n| NonZeroU32::new(n as u32))
1145 {
1146 content.tab_size = Some(size);
1147 }
1148 }
1149 }
1150
1151 #[gpui::test]
1152 fn test_settings_store_basic(cx: &mut App) {
1153 let mut store = SettingsStore::new(cx, &default_settings());
1154 store.register_setting::<AutoUpdateSetting>(cx);
1155 store.register_setting::<TitleBarSettings>(cx);
1156 store.register_setting::<DefaultLanguageSettings>(cx);
1157
1158 assert_eq!(
1159 store.get::<AutoUpdateSetting>(None),
1160 &AutoUpdateSetting { auto_update: true }
1161 );
1162 assert_eq!(
1163 store.get::<TitleBarSettings>(None).show,
1164 TitleBarVisibilityContent::Always
1165 );
1166
1167 store
1168 .set_user_settings(
1169 r#"{
1170 "auto_update": false,
1171 "title_bar": {
1172 "show": "never"
1173 }
1174 }"#,
1175 cx,
1176 )
1177 .unwrap();
1178
1179 assert_eq!(
1180 store.get::<AutoUpdateSetting>(None),
1181 &AutoUpdateSetting { auto_update: false }
1182 );
1183 assert_eq!(
1184 store.get::<TitleBarSettings>(None).show,
1185 TitleBarVisibilityContent::Never
1186 );
1187
1188 // todo!()
1189 store
1190 .set_local_settings(
1191 WorktreeId::from_usize(1),
1192 Path::new("/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 Path::new("/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 Path::new("/root2").into(),
1212 LocalSettingsKind::Settings,
1213 Some(r#"{ "tab_size": 9, "title_bar": { "show_branch_name": false } }"#),
1214 cx,
1215 )
1216 .unwrap();
1217
1218 assert_eq!(
1219 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1220 worktree_id: WorktreeId::from_usize(1),
1221 path: Path::new("/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: Path::new("/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: Path::new("/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::<TitleBarSettings>(Some(SettingsLocation {
1250 worktree_id: WorktreeId::from_usize(1),
1251 path: Path::new("/root2/something")
1252 })),
1253 &TitleBarSettings {
1254 show: TitleBarVisibilityContent::Never,
1255 show_branch_name: true,
1256 }
1257 );
1258 }
1259
1260 #[gpui::test]
1261 fn test_setting_store_assign_json_before_register(cx: &mut App) {
1262 let mut store = SettingsStore::new(cx, &test_settings());
1263 store
1264 .set_user_settings(r#"{ "auto_update": false }"#, cx)
1265 .unwrap();
1266 store.register_setting::<AutoUpdateSetting>(cx);
1267 store.register_setting::<TitleBarSettings>(cx);
1268
1269 assert_eq!(
1270 store.get::<AutoUpdateSetting>(None),
1271 &AutoUpdateSetting { auto_update: false }
1272 );
1273 assert_eq!(
1274 store.get::<TitleBarSettings>(None).show,
1275 TitleBarVisibilityContent::Always,
1276 );
1277 }
1278
1279 #[track_caller]
1280 fn check_settings_update(
1281 store: &mut SettingsStore,
1282 old_json: String,
1283 update: fn(&mut SettingsContent),
1284 expected_new_json: String,
1285 cx: &mut App,
1286 ) {
1287 store.set_user_settings(&old_json, cx).ok();
1288 let edits = store.edits_for_update(&old_json, update);
1289 dbg!(&edits);
1290 let mut new_json = old_json;
1291 for (range, replacement) in edits.into_iter() {
1292 new_json.replace_range(range, &replacement);
1293 }
1294 pretty_assertions::assert_eq!(new_json, expected_new_json);
1295 }
1296
1297 #[gpui::test]
1298 fn test_setting_store_update(cx: &mut App) {
1299 let mut store = SettingsStore::new(cx, &test_settings());
1300
1301 // entries added and updated
1302 check_settings_update(
1303 &mut store,
1304 r#"{
1305 "languages": {
1306 "JSON": {
1307 "auto_indent": true
1308 }
1309 }
1310 }"#
1311 .unindent(),
1312 |settings| {
1313 settings
1314 .languages_mut()
1315 .get_mut("JSON")
1316 .unwrap()
1317 .auto_indent = Some(false);
1318
1319 settings.languages_mut().insert(
1320 "Rust".into(),
1321 LanguageSettingsContent {
1322 auto_indent: Some(true),
1323 ..Default::default()
1324 },
1325 );
1326 },
1327 r#"{
1328 "languages": {
1329 "Rust": {
1330 "auto_indent": true
1331 },
1332 "JSON": {
1333 "auto_indent": false
1334 }
1335 }
1336 }"#
1337 .unindent(),
1338 cx,
1339 );
1340
1341 // entries removed
1342 check_settings_update(
1343 &mut store,
1344 r#"{
1345 "languages": {
1346 "Rust": {
1347 "language_setting_2": true
1348 },
1349 "JSON": {
1350 "language_setting_1": false
1351 }
1352 }
1353 }"#
1354 .unindent(),
1355 |settings| {
1356 settings.languages_mut().remove("JSON").unwrap();
1357 },
1358 r#"{
1359 "languages": {
1360 "Rust": {
1361 "language_setting_2": true
1362 }
1363 }
1364 }"#
1365 .unindent(),
1366 cx,
1367 );
1368
1369 check_settings_update(
1370 &mut store,
1371 r#"{
1372 "languages": {
1373 "Rust": {
1374 "language_setting_2": true
1375 },
1376 "JSON": {
1377 "language_setting_1": false
1378 }
1379 }
1380 }"#
1381 .unindent(),
1382 |settings| {
1383 settings.languages_mut().remove("Rust").unwrap();
1384 },
1385 r#"{
1386 "languages": {
1387 "JSON": {
1388 "language_setting_1": false
1389 }
1390 }
1391 }"#
1392 .unindent(),
1393 cx,
1394 );
1395
1396 // weird formatting
1397 check_settings_update(
1398 &mut store,
1399 r#"{
1400 "title_bar": { "show": "always", "name": "Max" }
1401 }"#
1402 .unindent(),
1403 |settings| {
1404 dbg!(&settings.title_bar);
1405 settings.title_bar.as_mut().unwrap().show = Some(TitleBarVisibilityContent::Never);
1406 dbg!(&settings.title_bar);
1407 },
1408 r#"{
1409 "title_bar": { "show": "never", "name": "Max" }
1410 }"#
1411 .unindent(),
1412 cx,
1413 );
1414
1415 // single-line formatting, other keys
1416 check_settings_update(
1417 &mut store,
1418 r#"{ "one": 1, "two": 2 }"#.to_owned(),
1419 |settings| settings.auto_update = Some(true),
1420 r#"{ "auto_update": true, "one": 1, "two": 2 }"#.to_owned(),
1421 cx,
1422 );
1423
1424 // empty object
1425 check_settings_update(
1426 &mut store,
1427 r#"{
1428 "title_bar": {}
1429 }"#
1430 .unindent(),
1431 |settings| settings.title_bar.as_mut().unwrap().show_menus = Some(true),
1432 r#"{
1433 "title_bar": {
1434 "show_menus": true
1435 }
1436 }"#
1437 .unindent(),
1438 cx,
1439 );
1440
1441 // no content
1442 check_settings_update(
1443 &mut store,
1444 r#""#.unindent(),
1445 |settings| {
1446 settings.title_bar = Some(TitleBarSettingsContent {
1447 show_branch_name: Some(true),
1448 ..Default::default()
1449 })
1450 },
1451 r#"{
1452 "title_bar": {
1453 "show_branch_name": true
1454 }
1455 }
1456 "#
1457 .unindent(),
1458 cx,
1459 );
1460
1461 check_settings_update(
1462 &mut store,
1463 r#"{
1464 }
1465 "#
1466 .unindent(),
1467 |settings| settings.title_bar.get_or_insert_default().show_branch_name = Some(true),
1468 r#"{
1469 "title_bar": {
1470 "show_branch_name": true
1471 }
1472 }
1473 "#
1474 .unindent(),
1475 cx,
1476 );
1477 }
1478
1479 #[gpui::test]
1480 fn test_vscode_import(cx: &mut App) {
1481 let mut store = SettingsStore::new(cx, &test_settings());
1482 store.register_setting::<DefaultLanguageSettings>(cx);
1483 store.register_setting::<TitleBarSettings>(cx);
1484 store.register_setting::<AutoUpdateSetting>(cx);
1485
1486 // create settings that werent present
1487 check_vscode_import(
1488 &mut store,
1489 r#"{
1490 }
1491 "#
1492 .unindent(),
1493 r#" { "editor.tabSize": 37 } "#.to_owned(),
1494 r#"{
1495 "tab_size": 37
1496 }
1497 "#
1498 .unindent(),
1499 cx,
1500 );
1501
1502 // persist settings that were present
1503 check_vscode_import(
1504 &mut store,
1505 r#"{
1506 "preferred_line_length": 99,
1507 }
1508 "#
1509 .unindent(),
1510 r#"{ "editor.tabSize": 42 }"#.to_owned(),
1511 r#"{
1512 "tab_size": 42,
1513 "preferred_line_length": 99,
1514 }
1515 "#
1516 .unindent(),
1517 cx,
1518 );
1519
1520 // don't clobber settings that aren't present in vscode
1521 check_vscode_import(
1522 &mut store,
1523 r#"{
1524 "preferred_line_length": 99,
1525 "tab_size": 42
1526 }
1527 "#
1528 .unindent(),
1529 r#"{}"#.to_owned(),
1530 r#"{
1531 "preferred_line_length": 99,
1532 "tab_size": 42
1533 }
1534 "#
1535 .unindent(),
1536 cx,
1537 );
1538
1539 // custom enum
1540 check_vscode_import(
1541 &mut store,
1542 r#"{
1543 "title_bar": {
1544 "show": "always"
1545 }
1546 }
1547 "#
1548 .unindent(),
1549 r#"{ "window.titleBarStyle": "never" }"#.to_owned(),
1550 r#"{
1551 "title_bar": {
1552 "show": "never"
1553 }
1554 }
1555 "#
1556 .unindent(),
1557 cx,
1558 );
1559 }
1560
1561 #[track_caller]
1562 fn check_vscode_import(
1563 store: &mut SettingsStore,
1564 old: String,
1565 vscode: String,
1566 expected: String,
1567 cx: &mut App,
1568 ) {
1569 store.set_user_settings(&old, cx).ok();
1570 let new = store.get_vscode_edits(
1571 old,
1572 &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
1573 );
1574 pretty_assertions::assert_eq!(new, expected);
1575 }
1576
1577 #[gpui::test]
1578 fn test_global_settings(cx: &mut App) {
1579 let mut store = SettingsStore::new(cx, &test_settings());
1580 store.register_setting::<TitleBarSettings>(cx);
1581
1582 // Set global settings - these should override defaults but not user settings
1583 store
1584 .set_global_settings(
1585 r#"{
1586 "title_bar": {
1587 "show": "never",
1588 }
1589 }"#,
1590 cx,
1591 )
1592 .unwrap();
1593
1594 // Before user settings, global settings should apply
1595 assert_eq!(
1596 store.get::<TitleBarSettings>(None),
1597 &TitleBarSettings {
1598 show: TitleBarVisibilityContent::Never,
1599 show_branch_name: true,
1600 }
1601 );
1602
1603 // Set user settings - these should override both defaults and global
1604 store
1605 .set_user_settings(
1606 r#"{
1607 "title_bar": {
1608 "show": "always"
1609 }
1610 }"#,
1611 cx,
1612 )
1613 .unwrap();
1614
1615 // User settings should override global settings
1616 assert_eq!(
1617 store.get::<TitleBarSettings>(None),
1618 &TitleBarSettings {
1619 show: TitleBarVisibilityContent::Always,
1620 show_branch_name: true, // Staff from global settings
1621 }
1622 );
1623 }
1624}