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