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