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