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_file(&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_file 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.as_ref().unwrap().content.clone();
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.from_file(&self.default_settings, cx).unwrap();
868 setting_value.refine(value.as_mut(), &refinements, cx);
869 setting_value.set_global_value(value);
870 }
871
872 // Reload the local values for the setting.
873 paths_stack.clear();
874 project_settings_stack.clear();
875 for ((root_id, directory_path), local_settings) in &self.local_settings {
876 // Build a stack of all of the local values for that setting.
877 while let Some(prev_entry) = paths_stack.last() {
878 if let Some((prev_root_id, prev_path)) = prev_entry
879 && (root_id != prev_root_id || !directory_path.starts_with(prev_path))
880 {
881 paths_stack.pop();
882 project_settings_stack.pop();
883 continue;
884 }
885 break;
886 }
887
888 paths_stack.push(Some((*root_id, directory_path.as_ref())));
889 project_settings_stack.push(local_settings);
890
891 // If a local settings file changed, then avoid recomputing local
892 // settings for any path outside of that directory.
893 if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| {
894 *root_id != changed_root_id || !directory_path.starts_with(changed_local_path)
895 }) {
896 continue;
897 }
898
899 let mut value = setting_value.from_file(&self.default_settings, cx).unwrap();
900 setting_value.refine(value.as_mut(), &refinements, cx);
901 setting_value.refine(value.as_mut(), &project_settings_stack, cx);
902 setting_value.set_local_value(*root_id, directory_path.clone(), value);
903 }
904 }
905 Ok(())
906 }
907
908 pub fn editorconfig_properties(
909 &self,
910 for_worktree: WorktreeId,
911 for_path: &Path,
912 ) -> Option<EditorconfigProperties> {
913 let mut properties = EditorconfigProperties::new();
914
915 for (directory_with_config, _, parsed_editorconfig) in
916 self.local_editorconfig_settings(for_worktree)
917 {
918 if !for_path.starts_with(&directory_with_config) {
919 properties.use_fallbacks();
920 return Some(properties);
921 }
922 let parsed_editorconfig = parsed_editorconfig?;
923 if parsed_editorconfig.is_root {
924 properties = EditorconfigProperties::new();
925 }
926 for section in parsed_editorconfig.sections {
927 section.apply_to(&mut properties, for_path).log_err()?;
928 }
929 }
930
931 properties.use_fallbacks();
932 Some(properties)
933 }
934}
935
936#[derive(Debug, Clone, PartialEq)]
937pub enum InvalidSettingsError {
938 LocalSettings { path: PathBuf, message: String },
939 UserSettings { message: String },
940 ServerSettings { message: String },
941 DefaultSettings { message: String },
942 Editorconfig { path: PathBuf, message: String },
943 Tasks { path: PathBuf, message: String },
944 Debug { path: PathBuf, message: String },
945}
946
947impl std::fmt::Display for InvalidSettingsError {
948 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
949 match self {
950 InvalidSettingsError::LocalSettings { message, .. }
951 | InvalidSettingsError::UserSettings { message }
952 | InvalidSettingsError::ServerSettings { message }
953 | InvalidSettingsError::DefaultSettings { message }
954 | InvalidSettingsError::Tasks { message, .. }
955 | InvalidSettingsError::Editorconfig { message, .. }
956 | InvalidSettingsError::Debug { message, .. } => {
957 write!(f, "{message}")
958 }
959 }
960 }
961}
962impl std::error::Error for InvalidSettingsError {}
963
964impl Debug for SettingsStore {
965 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
966 f.debug_struct("SettingsStore")
967 .field(
968 "types",
969 &self
970 .setting_values
971 .values()
972 .map(|value| value.setting_type_name())
973 .collect::<Vec<_>>(),
974 )
975 .field("default_settings", &self.default_settings)
976 .field("user_settings", &self.user_settings)
977 .field("local_settings", &self.local_settings)
978 .finish_non_exhaustive()
979 }
980}
981
982impl<T: Settings> AnySettingValue for SettingValue<T> {
983 fn from_file(&self, s: &SettingsContent, cx: &mut App) -> Option<Box<dyn Any>> {
984 T::from_default(s, cx).map(|result| Box::new(result) as _)
985 }
986
987 fn refine(&self, value: &mut dyn Any, refinements: &[&SettingsContent], cx: &mut App) {
988 let value = value.downcast_mut::<T>().unwrap();
989 for refinement in refinements {
990 value.refine(refinement, cx)
991 }
992 }
993
994 fn setting_type_name(&self) -> &'static str {
995 type_name::<T>()
996 }
997
998 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)> {
999 self.local_values
1000 .iter()
1001 .map(|(id, path, value)| (*id, path.clone(), value as _))
1002 .collect()
1003 }
1004
1005 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
1006 if let Some(SettingsLocation { worktree_id, path }) = path {
1007 for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
1008 if worktree_id == *settings_root_id && path.starts_with(settings_path) {
1009 return value;
1010 }
1011 }
1012 }
1013
1014 self.global_value
1015 .as_ref()
1016 .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
1017 }
1018
1019 fn set_global_value(&mut self, value: Box<dyn Any>) {
1020 self.global_value = Some(*value.downcast().unwrap());
1021 }
1022
1023 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>) {
1024 let value = *value.downcast().unwrap();
1025 match self
1026 .local_values
1027 .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
1028 {
1029 Ok(ix) => self.local_values[ix].2 = value,
1030 Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
1031 }
1032 }
1033
1034 fn import_from_vscode(
1035 &self,
1036 vscode_settings: &VsCodeSettings,
1037 settings_content: &mut SettingsContent,
1038 ) {
1039 T::import_from_vscode(vscode_settings, settings_content);
1040 }
1041}
1042
1043#[cfg(test)]
1044mod tests {
1045 use std::num::NonZeroU32;
1046
1047 use crate::{
1048 TitleBarSettingsContent, TitleBarVisibilityContent, VsCodeSettingsSource, default_settings,
1049 settings_content::LanguageSettingsContent, test_settings,
1050 };
1051
1052 use super::*;
1053 use unindent::Unindent;
1054 use util::MergeFrom;
1055
1056 #[derive(Debug, PartialEq)]
1057 struct AutoUpdateSetting {
1058 auto_update: bool,
1059 }
1060
1061 impl Settings for AutoUpdateSetting {
1062 fn from_default(content: &SettingsContent, _: &mut App) -> Option<Self> {
1063 content
1064 .auto_update
1065 .map(|auto_update| AutoUpdateSetting { auto_update })
1066 }
1067
1068 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1069 if let Some(auto_update) = content.auto_update {
1070 self.auto_update = auto_update;
1071 }
1072 }
1073
1074 fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {}
1075 }
1076
1077 #[derive(Debug, PartialEq)]
1078 struct TitleBarSettings {
1079 show: TitleBarVisibilityContent,
1080 show_branch_name: bool,
1081 }
1082
1083 impl Settings for TitleBarSettings {
1084 fn from_default(content: &SettingsContent, _: &mut App) -> Option<Self> {
1085 let content = content.title_bar.clone()?;
1086 Some(TitleBarSettings {
1087 show: content.show?,
1088 show_branch_name: content.show_branch_name?,
1089 })
1090 }
1091
1092 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1093 let Some(content) = content.title_bar.as_ref() else {
1094 return;
1095 };
1096 self.show.merge_from(&content.show)
1097 }
1098
1099 fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
1100 let mut show = None;
1101
1102 vscode.enum_setting("window.titleBarStyle", &mut show, |value| match value {
1103 "never" => Some(TitleBarVisibilityContent::Never),
1104 "always" => Some(TitleBarVisibilityContent::Always),
1105 _ => None,
1106 });
1107 if let Some(show) = show {
1108 content.title_bar.get_or_insert_default().show.replace(show);
1109 }
1110 }
1111 }
1112
1113 #[derive(Debug, PartialEq)]
1114 struct DefaultLanguageSettings {
1115 tab_size: NonZeroU32,
1116 preferred_line_length: u32,
1117 }
1118
1119 impl Settings for DefaultLanguageSettings {
1120 fn from_default(content: &SettingsContent, _: &mut App) -> Option<Self> {
1121 let content = &content.project.all_languages.defaults;
1122 Some(DefaultLanguageSettings {
1123 tab_size: content.tab_size?,
1124 preferred_line_length: content.preferred_line_length?,
1125 })
1126 }
1127
1128 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1129 let content = &content.project.all_languages.defaults;
1130 self.tab_size.merge_from(&content.tab_size);
1131 self.preferred_line_length
1132 .merge_from(&content.preferred_line_length);
1133 }
1134
1135 fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
1136 let content = &mut content.project.all_languages.defaults;
1137
1138 if let Some(size) = vscode
1139 .read_value("editor.tabSize")
1140 .and_then(|v| v.as_u64())
1141 .and_then(|n| NonZeroU32::new(n as u32))
1142 {
1143 content.tab_size = Some(size);
1144 }
1145 }
1146 }
1147
1148 #[gpui::test]
1149 fn test_settings_store_basic(cx: &mut App) {
1150 let mut store = SettingsStore::new(cx, &default_settings());
1151 store.register_setting::<AutoUpdateSetting>(cx);
1152 store.register_setting::<TitleBarSettings>(cx);
1153 store.register_setting::<DefaultLanguageSettings>(cx);
1154
1155 assert_eq!(
1156 store.get::<AutoUpdateSetting>(None),
1157 &AutoUpdateSetting { auto_update: true }
1158 );
1159 assert_eq!(
1160 store.get::<TitleBarSettings>(None).show,
1161 TitleBarVisibilityContent::Always
1162 );
1163
1164 store
1165 .set_user_settings(
1166 r#"{
1167 "auto_update": false,
1168 "title_bar": {
1169 "show": "never"
1170 }
1171 }"#,
1172 cx,
1173 )
1174 .unwrap();
1175
1176 assert_eq!(
1177 store.get::<AutoUpdateSetting>(None),
1178 &AutoUpdateSetting { auto_update: false }
1179 );
1180 assert_eq!(
1181 store.get::<TitleBarSettings>(None).show,
1182 TitleBarVisibilityContent::Never
1183 );
1184
1185 // todo!()
1186 store
1187 .set_local_settings(
1188 WorktreeId::from_usize(1),
1189 Path::new("/root1").into(),
1190 LocalSettingsKind::Settings,
1191 Some(r#"{ "tab_size": 5 }"#),
1192 cx,
1193 )
1194 .unwrap();
1195 store
1196 .set_local_settings(
1197 WorktreeId::from_usize(1),
1198 Path::new("/root1/subdir").into(),
1199 LocalSettingsKind::Settings,
1200 Some(r#"{ "preferred_line_length": 50 }"#),
1201 cx,
1202 )
1203 .unwrap();
1204
1205 store
1206 .set_local_settings(
1207 WorktreeId::from_usize(1),
1208 Path::new("/root2").into(),
1209 LocalSettingsKind::Settings,
1210 Some(r#"{ "tab_size": 9, "title_bar": { "show_branch_name": false } }"#),
1211 cx,
1212 )
1213 .unwrap();
1214
1215 assert_eq!(
1216 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1217 worktree_id: WorktreeId::from_usize(1),
1218 path: Path::new("/root1/something"),
1219 })),
1220 &DefaultLanguageSettings {
1221 preferred_line_length: 80,
1222 tab_size: 5.try_into().unwrap(),
1223 }
1224 );
1225 assert_eq!(
1226 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1227 worktree_id: WorktreeId::from_usize(1),
1228 path: Path::new("/root1/subdir/something")
1229 })),
1230 &DefaultLanguageSettings {
1231 preferred_line_length: 50,
1232 tab_size: 5.try_into().unwrap(),
1233 }
1234 );
1235 assert_eq!(
1236 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1237 worktree_id: WorktreeId::from_usize(1),
1238 path: Path::new("/root2/something")
1239 })),
1240 &DefaultLanguageSettings {
1241 preferred_line_length: 80,
1242 tab_size: 9.try_into().unwrap(),
1243 }
1244 );
1245 assert_eq!(
1246 store.get::<TitleBarSettings>(Some(SettingsLocation {
1247 worktree_id: WorktreeId::from_usize(1),
1248 path: Path::new("/root2/something")
1249 })),
1250 &TitleBarSettings {
1251 show: TitleBarVisibilityContent::Never,
1252 show_branch_name: true,
1253 }
1254 );
1255 }
1256
1257 #[gpui::test]
1258 fn test_setting_store_assign_json_before_register(cx: &mut App) {
1259 let mut store = SettingsStore::new(cx, &test_settings());
1260 store
1261 .set_user_settings(r#"{ "auto_update": false }"#, cx)
1262 .unwrap();
1263 store.register_setting::<AutoUpdateSetting>(cx);
1264 store.register_setting::<TitleBarSettings>(cx);
1265
1266 assert_eq!(
1267 store.get::<AutoUpdateSetting>(None),
1268 &AutoUpdateSetting { auto_update: false }
1269 );
1270 assert_eq!(
1271 store.get::<TitleBarSettings>(None).show,
1272 TitleBarVisibilityContent::Always,
1273 );
1274 }
1275
1276 #[track_caller]
1277 fn check_settings_update(
1278 store: &mut SettingsStore,
1279 old_json: String,
1280 update: fn(&mut SettingsContent),
1281 expected_new_json: String,
1282 cx: &mut App,
1283 ) {
1284 store.set_user_settings(&old_json, cx).ok();
1285 let edits = store.edits_for_update(&old_json, update);
1286 dbg!(&edits);
1287 let mut new_json = old_json;
1288 for (range, replacement) in edits.into_iter() {
1289 new_json.replace_range(range, &replacement);
1290 }
1291 pretty_assertions::assert_eq!(new_json, expected_new_json);
1292 }
1293
1294 #[gpui::test]
1295 fn test_setting_store_update(cx: &mut App) {
1296 let mut store = SettingsStore::new(cx, &test_settings());
1297
1298 // entries added and updated
1299 check_settings_update(
1300 &mut store,
1301 r#"{
1302 "languages": {
1303 "JSON": {
1304 "auto_indent": true
1305 }
1306 }
1307 }"#
1308 .unindent(),
1309 |settings| {
1310 settings
1311 .languages_mut()
1312 .get_mut("JSON")
1313 .unwrap()
1314 .auto_indent = Some(false);
1315
1316 settings.languages_mut().insert(
1317 "Rust".into(),
1318 LanguageSettingsContent {
1319 auto_indent: Some(true),
1320 ..Default::default()
1321 },
1322 );
1323 },
1324 r#"{
1325 "languages": {
1326 "Rust": {
1327 "auto_indent": true
1328 },
1329 "JSON": {
1330 "auto_indent": false
1331 }
1332 }
1333 }"#
1334 .unindent(),
1335 cx,
1336 );
1337
1338 // entries removed
1339 check_settings_update(
1340 &mut store,
1341 r#"{
1342 "languages": {
1343 "Rust": {
1344 "language_setting_2": true
1345 },
1346 "JSON": {
1347 "language_setting_1": false
1348 }
1349 }
1350 }"#
1351 .unindent(),
1352 |settings| {
1353 settings.languages_mut().remove("JSON").unwrap();
1354 },
1355 r#"{
1356 "languages": {
1357 "Rust": {
1358 "language_setting_2": true
1359 }
1360 }
1361 }"#
1362 .unindent(),
1363 cx,
1364 );
1365
1366 check_settings_update(
1367 &mut store,
1368 r#"{
1369 "languages": {
1370 "Rust": {
1371 "language_setting_2": true
1372 },
1373 "JSON": {
1374 "language_setting_1": false
1375 }
1376 }
1377 }"#
1378 .unindent(),
1379 |settings| {
1380 settings.languages_mut().remove("Rust").unwrap();
1381 },
1382 r#"{
1383 "languages": {
1384 "JSON": {
1385 "language_setting_1": false
1386 }
1387 }
1388 }"#
1389 .unindent(),
1390 cx,
1391 );
1392
1393 // weird formatting
1394 check_settings_update(
1395 &mut store,
1396 r#"{
1397 "title_bar": { "show": "always", "name": "Max" }
1398 }"#
1399 .unindent(),
1400 |settings| {
1401 dbg!(&settings.title_bar);
1402 settings.title_bar.as_mut().unwrap().show = Some(TitleBarVisibilityContent::Never);
1403 dbg!(&settings.title_bar);
1404 },
1405 r#"{
1406 "title_bar": { "show": "never", "name": "Max" }
1407 }"#
1408 .unindent(),
1409 cx,
1410 );
1411
1412 // single-line formatting, other keys
1413 check_settings_update(
1414 &mut store,
1415 r#"{ "one": 1, "two": 2 }"#.to_owned(),
1416 |settings| settings.auto_update = Some(true),
1417 r#"{ "auto_update": true, "one": 1, "two": 2 }"#.to_owned(),
1418 cx,
1419 );
1420
1421 // empty object
1422 check_settings_update(
1423 &mut store,
1424 r#"{
1425 "title_bar": {}
1426 }"#
1427 .unindent(),
1428 |settings| settings.title_bar.as_mut().unwrap().show_menus = Some(true),
1429 r#"{
1430 "title_bar": {
1431 "show_menus": true
1432 }
1433 }"#
1434 .unindent(),
1435 cx,
1436 );
1437
1438 // no content
1439 check_settings_update(
1440 &mut store,
1441 r#""#.unindent(),
1442 |settings| {
1443 settings.title_bar = Some(TitleBarSettingsContent {
1444 show_branch_name: Some(true),
1445 ..Default::default()
1446 })
1447 },
1448 r#"{
1449 "title_bar": {
1450 "show_branch_name": true
1451 }
1452 }
1453 "#
1454 .unindent(),
1455 cx,
1456 );
1457
1458 check_settings_update(
1459 &mut store,
1460 r#"{
1461 }
1462 "#
1463 .unindent(),
1464 |settings| settings.title_bar.get_or_insert_default().show_branch_name = Some(true),
1465 r#"{
1466 "title_bar": {
1467 "show_branch_name": true
1468 }
1469 }
1470 "#
1471 .unindent(),
1472 cx,
1473 );
1474 }
1475
1476 #[gpui::test]
1477 fn test_vscode_import(cx: &mut App) {
1478 let mut store = SettingsStore::new(cx, &test_settings());
1479 store.register_setting::<DefaultLanguageSettings>(cx);
1480 store.register_setting::<TitleBarSettings>(cx);
1481 store.register_setting::<AutoUpdateSetting>(cx);
1482
1483 // create settings that werent present
1484 check_vscode_import(
1485 &mut store,
1486 r#"{
1487 }
1488 "#
1489 .unindent(),
1490 r#" { "editor.tabSize": 37 } "#.to_owned(),
1491 r#"{
1492 "tab_size": 37
1493 }
1494 "#
1495 .unindent(),
1496 cx,
1497 );
1498
1499 // persist settings that were present
1500 check_vscode_import(
1501 &mut store,
1502 r#"{
1503 "preferred_line_length": 99,
1504 }
1505 "#
1506 .unindent(),
1507 r#"{ "editor.tabSize": 42 }"#.to_owned(),
1508 r#"{
1509 "tab_size": 42,
1510 "preferred_line_length": 99,
1511 }
1512 "#
1513 .unindent(),
1514 cx,
1515 );
1516
1517 // don't clobber settings that aren't present in vscode
1518 check_vscode_import(
1519 &mut store,
1520 r#"{
1521 "preferred_line_length": 99,
1522 "tab_size": 42
1523 }
1524 "#
1525 .unindent(),
1526 r#"{}"#.to_owned(),
1527 r#"{
1528 "preferred_line_length": 99,
1529 "tab_size": 42
1530 }
1531 "#
1532 .unindent(),
1533 cx,
1534 );
1535
1536 // custom enum
1537 check_vscode_import(
1538 &mut store,
1539 r#"{
1540 "title_bar": {
1541 "show": "always"
1542 }
1543 }
1544 "#
1545 .unindent(),
1546 r#"{ "window.titleBarStyle": "never" }"#.to_owned(),
1547 r#"{
1548 "title_bar": {
1549 "show": "never"
1550 }
1551 }
1552 "#
1553 .unindent(),
1554 cx,
1555 );
1556 }
1557
1558 #[track_caller]
1559 fn check_vscode_import(
1560 store: &mut SettingsStore,
1561 old: String,
1562 vscode: String,
1563 expected: String,
1564 cx: &mut App,
1565 ) {
1566 store.set_user_settings(&old, cx).ok();
1567 let new = store.get_vscode_edits(
1568 old,
1569 &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
1570 );
1571 pretty_assertions::assert_eq!(new, expected);
1572 }
1573
1574 #[gpui::test]
1575 fn test_global_settings(cx: &mut App) {
1576 let mut store = SettingsStore::new(cx, &test_settings());
1577 store.register_setting::<TitleBarSettings>(cx);
1578
1579 // Set global settings - these should override defaults but not user settings
1580 store
1581 .set_global_settings(
1582 r#"{
1583 "title_bar": {
1584 "show": "never",
1585 }
1586 }"#,
1587 cx,
1588 )
1589 .unwrap();
1590
1591 // Before user settings, global settings should apply
1592 assert_eq!(
1593 store.get::<TitleBarSettings>(None),
1594 &TitleBarSettings {
1595 show: TitleBarVisibilityContent::Never,
1596 show_branch_name: true,
1597 }
1598 );
1599
1600 // Set user settings - these should override both defaults and global
1601 store
1602 .set_user_settings(
1603 r#"{
1604 "title_bar": {
1605 "show": "always"
1606 }
1607 }"#,
1608 cx,
1609 )
1610 .unwrap();
1611
1612 // User settings should override global settings
1613 assert_eq!(
1614 store.get::<TitleBarSettings>(None),
1615 &TitleBarSettings {
1616 show: TitleBarVisibilityContent::Always,
1617 show_branch_name: true, // Staff from global settings
1618 }
1619 );
1620 }
1621}