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_file(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(user_settings) = self.user_settings.as_ref() {
326 refinements.push(&user_settings.content);
327 if let Some(release_channel) = user_settings.for_release_channel() {
328 refinements.push(release_channel)
329 }
330 if let Some(os) = user_settings.for_os() {
331 refinements.push(os)
332 }
333 if let Some(profile) = user_settings.for_profile(cx) {
334 refinements.push(profile)
335 }
336 }
337
338 if let Some(server_settings) = self.server_settings.as_ref() {
339 refinements.push(server_settings)
340 }
341 // todo!() unwrap...
342 let mut value = T::from_file(&self.default_settings, cx).unwrap();
343 for refinement in refinements {
344 value.refine(refinement, cx)
345 }
346
347 setting_value.set_global_value(Box::new(value));
348
349 // todo!() local settings
350 // (they weren't handled before...)
351 }
352
353 /// Get the value of a setting.
354 ///
355 /// Panics if the given setting type has not been registered, or if there is no
356 /// value for this setting.
357 pub fn get<T: Settings>(&self, path: Option<SettingsLocation>) -> &T {
358 self.setting_values
359 .get(&TypeId::of::<T>())
360 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
361 .value_for_path(path)
362 .downcast_ref::<T>()
363 .expect("no default value for setting type")
364 }
365
366 /// Get the value of a setting.
367 ///
368 /// Does not panic
369 pub fn try_get<T: Settings>(&self, path: Option<SettingsLocation>) -> Option<&T> {
370 self.setting_values
371 .get(&TypeId::of::<T>())
372 .map(|value| value.value_for_path(path))
373 .and_then(|value| value.downcast_ref::<T>())
374 }
375
376 /// Get all values from project specific settings
377 pub fn get_all_locals<T: Settings>(&self) -> Vec<(WorktreeId, Arc<Path>, &T)> {
378 self.setting_values
379 .get(&TypeId::of::<T>())
380 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
381 .all_local_values()
382 .into_iter()
383 .map(|(id, path, any)| {
384 (
385 id,
386 path,
387 any.downcast_ref::<T>()
388 .expect("wrong value type for setting"),
389 )
390 })
391 .collect()
392 }
393
394 /// Override the global value for a setting.
395 ///
396 /// The given value will be overwritten if the user settings file changes.
397 pub fn override_global<T: Settings>(&mut self, value: T) {
398 self.setting_values
399 .get_mut(&TypeId::of::<T>())
400 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
401 .set_global_value(Box::new(value))
402 }
403
404 /// Get the user's settings as a raw JSON value.
405 ///
406 /// For user-facing functionality use the typed setting interface.
407 /// (e.g. ProjectSettings::get_global(cx))
408 pub fn raw_user_settings(&self) -> Option<&UserSettingsContent> {
409 self.user_settings.as_ref()
410 }
411
412 /// Replaces current settings with the values from the given JSON.
413 pub fn set_raw_user_settings(
414 &mut self,
415 new_settings: UserSettingsContent,
416 cx: &mut App,
417 ) -> Result<()> {
418 self.user_settings = Some(new_settings);
419 self.recompute_values(None, cx)?;
420 Ok(())
421 }
422
423 /// Get the configured settings profile names.
424 pub fn configured_settings_profiles(&self) -> impl Iterator<Item = &str> {
425 self.user_settings
426 .iter()
427 .flat_map(|settings| settings.profiles.keys().map(|k| k.as_str()))
428 }
429
430 /// Access the raw JSON value of the default settings.
431 pub fn raw_default_settings(&self) -> &SettingsContent {
432 &self.default_settings
433 }
434
435 #[cfg(any(test, feature = "test-support"))]
436 pub fn test(cx: &mut App) -> Self {
437 Self::new(cx, &crate::test_settings())
438 }
439
440 /// Updates the value of a setting in the user's global configuration.
441 ///
442 /// This is only for tests. Normally, settings are only loaded from
443 /// JSON files.
444 #[cfg(any(test, feature = "test-support"))]
445 pub fn update_user_settings(
446 &mut self,
447 cx: &mut App,
448 update: impl FnOnce(&mut SettingsContent),
449 ) {
450 let mut content = self.user_settings.as_ref().unwrap().content.clone();
451 update(&mut content);
452 let new_text = serde_json::to_string(&UserSettingsContent {
453 content,
454 ..Default::default()
455 })
456 .unwrap();
457 self.set_user_settings(&new_text, cx).unwrap();
458 }
459
460 pub async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
461 match fs.load(paths::settings_file()).await {
462 result @ Ok(_) => result,
463 Err(err) => {
464 if let Some(e) = err.downcast_ref::<std::io::Error>()
465 && e.kind() == std::io::ErrorKind::NotFound
466 {
467 return Ok(crate::initial_user_settings_content().to_string());
468 }
469 Err(err)
470 }
471 }
472 }
473
474 fn update_settings_file_inner(
475 &self,
476 fs: Arc<dyn Fs>,
477 update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>,
478 ) -> oneshot::Receiver<Result<()>> {
479 let (tx, rx) = oneshot::channel::<Result<()>>();
480 self.setting_file_updates_tx
481 .unbounded_send(Box::new(move |cx: AsyncApp| {
482 async move {
483 let res = async move {
484 let old_text = Self::load_settings(&fs).await?;
485 let new_text = update(old_text, cx)?;
486 let settings_path = paths::settings_file().as_path();
487 if fs.is_file(settings_path).await {
488 let resolved_path =
489 fs.canonicalize(settings_path).await.with_context(|| {
490 format!(
491 "Failed to canonicalize settings path {:?}",
492 settings_path
493 )
494 })?;
495
496 fs.atomic_write(resolved_path.clone(), new_text)
497 .await
498 .with_context(|| {
499 format!("Failed to write settings to file {:?}", resolved_path)
500 })?;
501 } else {
502 fs.atomic_write(settings_path.to_path_buf(), new_text)
503 .await
504 .with_context(|| {
505 format!("Failed to write settings to file {:?}", settings_path)
506 })?;
507 }
508 anyhow::Ok(())
509 }
510 .await;
511
512 let new_res = match &res {
513 Ok(_) => anyhow::Ok(()),
514 Err(e) => Err(anyhow::anyhow!("Failed to write settings to file {:?}", e)),
515 };
516
517 _ = tx.send(new_res);
518 res
519 }
520 .boxed_local()
521 }))
522 .map_err(|err| anyhow::format_err!("Failed to update settings file: {}", err))
523 .log_with_level(log::Level::Warn);
524 return rx;
525 }
526
527 pub fn update_settings_file_at_path(
528 &self,
529 fs: Arc<dyn Fs>,
530 path: &[impl AsRef<str>],
531 new_value: serde_json::Value,
532 ) -> oneshot::Receiver<Result<()>> {
533 let key_path = path
534 .into_iter()
535 .map(AsRef::as_ref)
536 .map(SharedString::new)
537 .collect::<Vec<_>>();
538 let update = move |mut old_text: String, cx: AsyncApp| {
539 cx.read_global(|store: &SettingsStore, _cx| {
540 // todo(settings_ui) use `update_value_in_json_text` for merging new and old objects with comment preservation, needs old value though...
541 let (range, replacement) = replace_value_in_json_text(
542 &old_text,
543 key_path.as_slice(),
544 store.json_tab_size(),
545 Some(&new_value),
546 None,
547 );
548 old_text.replace_range(range, &replacement);
549 old_text
550 })
551 };
552 self.update_settings_file_inner(fs, update)
553 }
554
555 pub fn update_settings_file(
556 &self,
557 fs: Arc<dyn Fs>,
558 update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
559 ) {
560 _ = self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
561 cx.read_global(|store: &SettingsStore, cx| {
562 store.new_text_for_update(old_text, |content| update(content, cx))
563 })
564 });
565 }
566
567 pub fn import_vscode_settings(
568 &self,
569 fs: Arc<dyn Fs>,
570 vscode_settings: VsCodeSettings,
571 ) -> oneshot::Receiver<Result<()>> {
572 self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
573 cx.read_global(|store: &SettingsStore, _cx| {
574 store.get_vscode_edits(old_text, &vscode_settings)
575 })
576 })
577 }
578
579 pub fn settings_ui_items(&self) -> impl IntoIterator<Item = SettingsUiEntry> {
580 self.setting_values
581 .values()
582 .map(|item| item.settings_ui_item())
583 }
584}
585
586impl SettingsStore {
587 /// Updates the value of a setting in a JSON file, returning the new text
588 /// for that JSON file.
589 pub fn new_text_for_update(
590 &self,
591 old_text: String,
592 update: impl FnOnce(&mut SettingsContent),
593 ) -> String {
594 let edits = self.edits_for_update(&old_text, update);
595 let mut new_text = old_text;
596 for (range, replacement) in edits.into_iter() {
597 new_text.replace_range(range, &replacement);
598 }
599 new_text
600 }
601
602 pub fn get_vscode_edits(&self, old_text: String, vscode: &VsCodeSettings) -> String {
603 self.new_text_for_update(old_text, |settings_content| {
604 for v in self.setting_values.values() {
605 v.import_from_vscode(vscode, settings_content)
606 }
607 })
608 }
609
610 /// Updates the value of a setting in a JSON file, returning a list
611 /// of edits to apply to the JSON file.
612 pub fn edits_for_update(
613 &self,
614 text: &str,
615 update: impl FnOnce(&mut SettingsContent),
616 ) -> Vec<(Range<usize>, String)> {
617 let old_content: UserSettingsContent = serde_json::from_str(text).unwrap_or_default();
618 let mut new_content = old_content.clone();
619 dbg!(&new_content.content.title_bar);
620 update(&mut new_content.content);
621 dbg!(&new_content.content.title_bar);
622
623 let old_value = serde_json::to_value(&old_content).unwrap();
624 let new_value = serde_json::to_value(new_content).unwrap();
625 // dbg!(&old_value, &new_value);
626
627 let mut key_path = Vec::new();
628 let mut edits = Vec::new();
629 let tab_size = self.json_tab_size();
630 let mut text = text.to_string();
631 update_value_in_json_text(
632 &mut text,
633 &mut key_path,
634 tab_size,
635 &old_value,
636 &new_value,
637 &[], // todo!() is this still needed?
638 &mut edits,
639 );
640 edits
641 }
642
643 pub fn json_tab_size(&self) -> usize {
644 2
645 }
646
647 /// Sets the default settings via a JSON string.
648 ///
649 /// The string should contain a JSON object with a default value for every setting.
650 pub fn set_default_settings(
651 &mut self,
652 default_settings_content: &str,
653 cx: &mut App,
654 ) -> Result<()> {
655 self.default_settings = parse_json_with_comments(default_settings_content)?;
656 self.recompute_values(None, cx)?;
657 Ok(())
658 }
659
660 /// Sets the user settings via a JSON string.
661 pub fn set_user_settings(&mut self, user_settings_content: &str, cx: &mut App) -> Result<()> {
662 let settings: UserSettingsContent = if user_settings_content.is_empty() {
663 parse_json_with_comments("{}")?
664 } else {
665 parse_json_with_comments(user_settings_content)?
666 };
667
668 self.user_settings = Some(settings);
669 self.recompute_values(None, cx)?;
670 Ok(())
671 }
672
673 /// Sets the global settings via a JSON string.
674 pub fn set_global_settings(
675 &mut self,
676 global_settings_content: &str,
677 cx: &mut App,
678 ) -> Result<()> {
679 let settings: SettingsContent = if global_settings_content.is_empty() {
680 parse_json_with_comments("{}")?
681 } else {
682 parse_json_with_comments(global_settings_content)?
683 };
684
685 self.global_settings = Some(settings);
686 self.recompute_values(None, cx)?;
687 Ok(())
688 }
689
690 pub fn set_server_settings(
691 &mut self,
692 server_settings_content: &str,
693 cx: &mut App,
694 ) -> Result<()> {
695 let settings: Option<ServerSettingsContent> = if server_settings_content.is_empty() {
696 None
697 } else {
698 parse_json_with_comments(server_settings_content)?
699 };
700
701 // Rewrite the server settings into a content type
702 self.server_settings = settings.map(|settings| SettingsContent {
703 project: settings.project,
704 ..Default::default()
705 });
706
707 self.recompute_values(None, cx)?;
708 Ok(())
709 }
710
711 /// Add or remove a set of local settings via a JSON string.
712 pub fn set_local_settings(
713 &mut self,
714 root_id: WorktreeId,
715 directory_path: Arc<Path>,
716 kind: LocalSettingsKind,
717 settings_content: Option<&str>,
718 cx: &mut App,
719 ) -> std::result::Result<(), InvalidSettingsError> {
720 let mut zed_settings_changed = false;
721 match (
722 kind,
723 settings_content
724 .map(|content| content.trim())
725 .filter(|content| !content.is_empty()),
726 ) {
727 (LocalSettingsKind::Tasks, _) => {
728 return Err(InvalidSettingsError::Tasks {
729 message: "Attempted to submit tasks into the settings store".to_string(),
730 path: directory_path.join(task_file_name()),
731 });
732 }
733 (LocalSettingsKind::Debug, _) => {
734 return Err(InvalidSettingsError::Debug {
735 message: "Attempted to submit debugger config into the settings store"
736 .to_string(),
737 path: directory_path.join(task_file_name()),
738 });
739 }
740 (LocalSettingsKind::Settings, None) => {
741 zed_settings_changed = self
742 .local_settings
743 .remove(&(root_id, directory_path.clone()))
744 .is_some()
745 }
746 (LocalSettingsKind::Editorconfig, None) => {
747 self.raw_editorconfig_settings
748 .remove(&(root_id, directory_path.clone()));
749 }
750 (LocalSettingsKind::Settings, Some(settings_contents)) => {
751 let new_settings = parse_json_with_comments::<ProjectSettingsContent>(
752 settings_contents,
753 )
754 .map_err(|e| InvalidSettingsError::LocalSettings {
755 path: directory_path.join(local_settings_file_relative_path()),
756 message: e.to_string(),
757 })?;
758 match self.local_settings.entry((root_id, directory_path.clone())) {
759 btree_map::Entry::Vacant(v) => {
760 v.insert(SettingsContent {
761 project: new_settings,
762 ..Default::default()
763 });
764 zed_settings_changed = true;
765 }
766 btree_map::Entry::Occupied(mut o) => {
767 if &o.get().project != &new_settings {
768 o.insert(SettingsContent {
769 project: new_settings,
770 ..Default::default()
771 });
772 zed_settings_changed = true;
773 }
774 }
775 }
776 }
777 (LocalSettingsKind::Editorconfig, Some(editorconfig_contents)) => {
778 match self
779 .raw_editorconfig_settings
780 .entry((root_id, directory_path.clone()))
781 {
782 btree_map::Entry::Vacant(v) => match editorconfig_contents.parse() {
783 Ok(new_contents) => {
784 v.insert((editorconfig_contents.to_owned(), Some(new_contents)));
785 }
786 Err(e) => {
787 v.insert((editorconfig_contents.to_owned(), None));
788 return Err(InvalidSettingsError::Editorconfig {
789 message: e.to_string(),
790 path: directory_path.join(EDITORCONFIG_NAME),
791 });
792 }
793 },
794 btree_map::Entry::Occupied(mut o) => {
795 if o.get().0 != editorconfig_contents {
796 match editorconfig_contents.parse() {
797 Ok(new_contents) => {
798 o.insert((
799 editorconfig_contents.to_owned(),
800 Some(new_contents),
801 ));
802 }
803 Err(e) => {
804 o.insert((editorconfig_contents.to_owned(), None));
805 return Err(InvalidSettingsError::Editorconfig {
806 message: e.to_string(),
807 path: directory_path.join(EDITORCONFIG_NAME),
808 });
809 }
810 }
811 }
812 }
813 }
814 }
815 };
816
817 if zed_settings_changed {
818 self.recompute_values(Some((root_id, &directory_path)), cx)?;
819 }
820 Ok(())
821 }
822
823 pub fn set_extension_settings(
824 &mut self,
825 content: ExtensionsSettingsContent,
826 cx: &mut App,
827 ) -> Result<()> {
828 self.extension_settings = Some(SettingsContent {
829 project: ProjectSettingsContent {
830 all_languages: content.all_languages,
831 },
832 ..Default::default()
833 });
834 self.recompute_values(None, cx)?;
835 Ok(())
836 }
837
838 /// Add or remove a set of local settings via a JSON string.
839 pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut App) -> Result<()> {
840 self.local_settings
841 .retain(|(worktree_id, _), _| worktree_id != &root_id);
842 self.recompute_values(Some((root_id, "".as_ref())), cx)?;
843 Ok(())
844 }
845
846 pub fn local_settings(
847 &self,
848 root_id: WorktreeId,
849 ) -> impl '_ + Iterator<Item = (Arc<Path>, String)> {
850 self.local_settings
851 .range(
852 (root_id, Path::new("").into())
853 ..(
854 WorktreeId::from_usize(root_id.to_usize() + 1),
855 Path::new("").into(),
856 ),
857 )
858 .map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap()))
859 }
860
861 pub fn local_editorconfig_settings(
862 &self,
863 root_id: WorktreeId,
864 ) -> impl '_ + Iterator<Item = (Arc<Path>, String, Option<Editorconfig>)> {
865 self.raw_editorconfig_settings
866 .range(
867 (root_id, Path::new("").into())
868 ..(
869 WorktreeId::from_usize(root_id.to_usize() + 1),
870 Path::new("").into(),
871 ),
872 )
873 .map(|((_, path), (content, parsed_content))| {
874 (path.clone(), content.clone(), parsed_content.clone())
875 })
876 }
877
878 pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value {
879 let mut generator = schemars::generate::SchemaSettings::draft2019_09()
880 .with_transform(DefaultDenyUnknownFields)
881 .into_generator();
882
883 let schema = UserSettingsContent::json_schema(&mut generator);
884
885 // add schemas which are determined at runtime
886 for parameterized_json_schema in inventory::iter::<ParameterizedJsonSchema>() {
887 (parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx);
888 }
889
890 schema.to_value()
891 }
892
893 fn recompute_values(
894 &mut self,
895 changed_local_path: Option<(WorktreeId, &Path)>,
896 cx: &mut App,
897 ) -> std::result::Result<(), InvalidSettingsError> {
898 // Reload the global and local values for every setting.
899 let mut project_settings_stack = Vec::<&SettingsContent>::new();
900 let mut paths_stack = Vec::<Option<(WorktreeId, &Path)>>::new();
901
902 let mut refinements = Vec::default();
903
904 if let Some(extension_settings) = self.extension_settings.as_ref() {
905 refinements.push(extension_settings)
906 }
907
908 if let Some(user_settings) = self.user_settings.as_ref() {
909 refinements.push(&user_settings.content);
910 if let Some(release_channel) = user_settings.for_release_channel() {
911 refinements.push(release_channel)
912 }
913 if let Some(os) = user_settings.for_os() {
914 refinements.push(os)
915 }
916 if let Some(profile) = user_settings.for_profile(cx) {
917 refinements.push(profile)
918 }
919 }
920
921 if let Some(server_settings) = self.server_settings.as_ref() {
922 refinements.push(server_settings)
923 }
924
925 for setting_value in self.setting_values.values_mut() {
926 // If the global settings file changed, reload the global value for the field.
927 if changed_local_path.is_none() {
928 let mut value = setting_value.from_file(&self.default_settings, cx).unwrap();
929 setting_value.refine(value.as_mut(), &refinements, cx);
930 setting_value.set_global_value(value);
931 }
932
933 // Reload the local values for the setting.
934 paths_stack.clear();
935 project_settings_stack.clear();
936 for ((root_id, directory_path), local_settings) in &self.local_settings {
937 // Build a stack of all of the local values for that setting.
938 while let Some(prev_entry) = paths_stack.last() {
939 if let Some((prev_root_id, prev_path)) = prev_entry
940 && (root_id != prev_root_id || !directory_path.starts_with(prev_path))
941 {
942 paths_stack.pop();
943 project_settings_stack.pop();
944 continue;
945 }
946 break;
947 }
948
949 // NOTE: this kind of condition existing in the old code too,
950 // but is there a problem when a setting is removed from a file?
951 if setting_value.from_file(local_settings, cx).is_some() {
952 paths_stack.push(Some((*root_id, directory_path.as_ref())));
953 project_settings_stack.push(local_settings);
954
955 // If a local settings file changed, then avoid recomputing local
956 // settings for any path outside of that directory.
957 if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| {
958 *root_id != changed_root_id
959 || !directory_path.starts_with(changed_local_path)
960 }) {
961 continue;
962 }
963
964 let mut value = setting_value.from_file(&self.default_settings, cx).unwrap();
965 setting_value.refine(value.as_mut(), &refinements, cx);
966 setting_value.refine(value.as_mut(), &project_settings_stack, cx);
967 setting_value.set_local_value(*root_id, directory_path.clone(), value);
968 }
969 }
970 }
971 Ok(())
972 }
973
974 pub fn editorconfig_properties(
975 &self,
976 for_worktree: WorktreeId,
977 for_path: &Path,
978 ) -> Option<EditorconfigProperties> {
979 let mut properties = EditorconfigProperties::new();
980
981 for (directory_with_config, _, parsed_editorconfig) in
982 self.local_editorconfig_settings(for_worktree)
983 {
984 if !for_path.starts_with(&directory_with_config) {
985 properties.use_fallbacks();
986 return Some(properties);
987 }
988 let parsed_editorconfig = parsed_editorconfig?;
989 if parsed_editorconfig.is_root {
990 properties = EditorconfigProperties::new();
991 }
992 for section in parsed_editorconfig.sections {
993 section.apply_to(&mut properties, for_path).log_err()?;
994 }
995 }
996
997 properties.use_fallbacks();
998 Some(properties)
999 }
1000}
1001
1002#[derive(Debug, Clone, PartialEq)]
1003pub enum InvalidSettingsError {
1004 LocalSettings { path: PathBuf, message: String },
1005 UserSettings { message: String },
1006 ServerSettings { message: String },
1007 DefaultSettings { message: String },
1008 Editorconfig { path: PathBuf, message: String },
1009 Tasks { path: PathBuf, message: String },
1010 Debug { path: PathBuf, message: String },
1011}
1012
1013impl std::fmt::Display for InvalidSettingsError {
1014 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1015 match self {
1016 InvalidSettingsError::LocalSettings { message, .. }
1017 | InvalidSettingsError::UserSettings { message }
1018 | InvalidSettingsError::ServerSettings { message }
1019 | InvalidSettingsError::DefaultSettings { message }
1020 | InvalidSettingsError::Tasks { message, .. }
1021 | InvalidSettingsError::Editorconfig { message, .. }
1022 | InvalidSettingsError::Debug { message, .. } => {
1023 write!(f, "{message}")
1024 }
1025 }
1026 }
1027}
1028impl std::error::Error for InvalidSettingsError {}
1029
1030impl Debug for SettingsStore {
1031 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1032 f.debug_struct("SettingsStore")
1033 .field(
1034 "types",
1035 &self
1036 .setting_values
1037 .values()
1038 .map(|value| value.setting_type_name())
1039 .collect::<Vec<_>>(),
1040 )
1041 .field("default_settings", &self.default_settings)
1042 .field("user_settings", &self.user_settings)
1043 .field("local_settings", &self.local_settings)
1044 .finish_non_exhaustive()
1045 }
1046}
1047
1048impl<T: Settings> AnySettingValue for SettingValue<T> {
1049 fn from_file(&self, s: &SettingsContent, cx: &mut App) -> Option<Box<dyn Any>> {
1050 (type_name::<T>(), TypeId::of::<T>());
1051 T::from_file(s, cx).map(|result| Box::new(result) as _)
1052 }
1053
1054 fn refine(&self, value: &mut dyn Any, refinements: &[&SettingsContent], cx: &mut App) {
1055 (type_name::<T>(), TypeId::of::<T>());
1056 let value = value.downcast_mut::<T>().unwrap();
1057 for refinement in refinements {
1058 value.refine(refinement, cx)
1059 }
1060 }
1061
1062 fn setting_type_name(&self) -> &'static str {
1063 type_name::<T>()
1064 }
1065
1066 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)> {
1067 self.local_values
1068 .iter()
1069 .map(|(id, path, value)| (*id, path.clone(), value as _))
1070 .collect()
1071 }
1072
1073 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
1074 if let Some(SettingsLocation { worktree_id, path }) = path {
1075 for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
1076 if worktree_id == *settings_root_id && path.starts_with(settings_path) {
1077 return value;
1078 }
1079 }
1080 }
1081
1082 self.global_value
1083 .as_ref()
1084 .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
1085 }
1086
1087 fn set_global_value(&mut self, value: Box<dyn Any>) {
1088 self.global_value = Some(*value.downcast().unwrap());
1089 }
1090
1091 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>) {
1092 let value = *value.downcast().unwrap();
1093 match self
1094 .local_values
1095 .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
1096 {
1097 Ok(ix) => self.local_values[ix].2 = value,
1098 Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
1099 }
1100 }
1101
1102 fn import_from_vscode(
1103 &self,
1104 vscode_settings: &VsCodeSettings,
1105 settings_content: &mut SettingsContent,
1106 ) {
1107 T::import_from_vscode(vscode_settings, settings_content);
1108 }
1109
1110 fn settings_ui_item(&self) -> SettingsUiEntry {
1111 todo!()
1112 // <<T as Settings>::FileContent as SettingsUi>::settings_ui_entry()
1113 }
1114}
1115
1116#[cfg(test)]
1117mod tests {
1118 use crate::{
1119 TitleBarSettingsContent, TitleBarVisibilityContent, VsCodeSettingsSource, default_settings,
1120 settings_content::LanguageSettingsContent, test_settings,
1121 };
1122
1123 use super::*;
1124 // This is so the SettingsUi macro can still work properly
1125 use crate as settings;
1126 use serde::Deserialize;
1127 use settings_ui_macros::{SettingsKey, SettingsUi};
1128 use unindent::Unindent;
1129 use util::MergeFrom;
1130
1131 #[derive(Debug, PartialEq)]
1132 struct AutoUpdateSetting {
1133 auto_update: bool,
1134 }
1135
1136 impl Settings for AutoUpdateSetting {
1137 fn from_file(content: &SettingsContent, _: &mut App) -> Option<Self> {
1138 content
1139 .auto_update
1140 .map(|auto_update| AutoUpdateSetting { auto_update })
1141 }
1142
1143 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1144 if let Some(auto_update) = content.auto_update {
1145 self.auto_update = auto_update;
1146 }
1147 }
1148
1149 fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {}
1150 }
1151
1152 #[derive(Debug, PartialEq)]
1153 struct TitleBarSettings {
1154 show: TitleBarVisibilityContent,
1155 show_branch_name: bool,
1156 }
1157
1158 impl Settings for TitleBarSettings {
1159 fn from_file(content: &SettingsContent, _: &mut App) -> Option<Self> {
1160 let content = content.title_bar.clone()?;
1161 Some(TitleBarSettings {
1162 show: content.show?,
1163 show_branch_name: content.show_branch_name?,
1164 })
1165 }
1166
1167 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1168 let Some(content) = content.title_bar.as_ref() else {
1169 return;
1170 };
1171 self.show.merge_from(&content.show)
1172 }
1173
1174 fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {}
1175 }
1176
1177 #[gpui::test]
1178 fn test_settings_store_basic(cx: &mut App) {
1179 let mut store = SettingsStore::new(cx, &default_settings());
1180 store.register_setting::<AutoUpdateSetting>(cx);
1181 store.register_setting::<TitleBarSettings>(cx);
1182 // store.register_setting::<MultiKeySettings>(cx);
1183
1184 assert_eq!(
1185 store.get::<AutoUpdateSetting>(None),
1186 &AutoUpdateSetting { auto_update: true }
1187 );
1188 assert_eq!(
1189 store.get::<TitleBarSettings>(None).show,
1190 TitleBarVisibilityContent::Always
1191 );
1192
1193 store
1194 .set_user_settings(
1195 r#"{
1196 "auto_update": false,
1197 "title_bar": {
1198 "show": "never"
1199 }
1200 }"#,
1201 cx,
1202 )
1203 .unwrap();
1204
1205 assert_eq!(
1206 store.get::<AutoUpdateSetting>(None),
1207 &AutoUpdateSetting { auto_update: false }
1208 );
1209 assert_eq!(
1210 store.get::<TitleBarSettings>(None).show,
1211 TitleBarVisibilityContent::Never
1212 );
1213
1214 // todo!()
1215 // store
1216 // .set_local_settings(
1217 // WorktreeId::from_usize(1),
1218 // Path::new("/root1").into(),
1219 // LocalSettingsKind::Settings,
1220 // Some(r#"{ "user": { "staff": true } }"#),
1221 // cx,
1222 // )
1223 // .unwrap();
1224 // store
1225 // .set_local_settings(
1226 // WorktreeId::from_usize(1),
1227 // Path::new("/root1/subdir").into(),
1228 // LocalSettingsKind::Settings,
1229 // Some(r#"{ "user": { "name": "Jane Doe" } }"#),
1230 // cx,
1231 // )
1232 // .unwrap();
1233
1234 // store
1235 // .set_local_settings(
1236 // WorktreeId::from_usize(1),
1237 // Path::new("/root2").into(),
1238 // LocalSettingsKind::Settings,
1239 // Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
1240 // cx,
1241 // )
1242 // .unwrap();
1243
1244 // assert_eq!(
1245 // store.get::<UserSettings>(Some(SettingsLocation {
1246 // worktree_id: WorktreeId::from_usize(1),
1247 // path: Path::new("/root1/something"),
1248 // })),
1249 // &UserSettings {
1250 // name: "John Doe".to_string(),
1251 // age: 31,
1252 // staff: true
1253 // }
1254 // );
1255 // assert_eq!(
1256 // store.get::<UserSettings>(Some(SettingsLocation {
1257 // worktree_id: WorktreeId::from_usize(1),
1258 // path: Path::new("/root1/subdir/something")
1259 // })),
1260 // &UserSettings {
1261 // name: "Jane Doe".to_string(),
1262 // age: 31,
1263 // staff: true
1264 // }
1265 // );
1266 // assert_eq!(
1267 // store.get::<UserSettings>(Some(SettingsLocation {
1268 // worktree_id: WorktreeId::from_usize(1),
1269 // path: Path::new("/root2/something")
1270 // })),
1271 // &UserSettings {
1272 // name: "John Doe".to_string(),
1273 // age: 42,
1274 // staff: false
1275 // }
1276 // );
1277 // assert_eq!(
1278 // store.get::<MultiKeySettings>(Some(SettingsLocation {
1279 // worktree_id: WorktreeId::from_usize(1),
1280 // path: Path::new("/root2/something")
1281 // })),
1282 // &MultiKeySettings {
1283 // key1: "a".to_string(),
1284 // key2: "b".to_string(),
1285 // }
1286 // );
1287 }
1288
1289 #[gpui::test]
1290 fn test_setting_store_assign_json_before_register(cx: &mut App) {
1291 let mut store = SettingsStore::new(cx, &test_settings());
1292 store
1293 .set_user_settings(r#"{ "auto_update": false }"#, cx)
1294 .unwrap();
1295 store.register_setting::<AutoUpdateSetting>(cx);
1296 store.register_setting::<TitleBarSettings>(cx);
1297
1298 assert_eq!(
1299 store.get::<AutoUpdateSetting>(None),
1300 &AutoUpdateSetting { auto_update: false }
1301 );
1302 assert_eq!(
1303 store.get::<TitleBarSettings>(None).show,
1304 TitleBarVisibilityContent::Always,
1305 );
1306 }
1307
1308 #[track_caller]
1309 fn check_settings_update(
1310 store: &mut SettingsStore,
1311 old_json: String,
1312 update: fn(&mut SettingsContent),
1313 expected_new_json: String,
1314 cx: &mut App,
1315 ) {
1316 store.set_user_settings(&old_json, cx).ok();
1317 let edits = store.edits_for_update(&old_json, update);
1318 dbg!(&edits);
1319 let mut new_json = old_json;
1320 for (range, replacement) in edits.into_iter() {
1321 new_json.replace_range(range, &replacement);
1322 }
1323 pretty_assertions::assert_eq!(new_json, expected_new_json);
1324 }
1325
1326 #[gpui::test]
1327 fn test_setting_store_update(cx: &mut App) {
1328 let mut store = SettingsStore::new(cx, &test_settings());
1329 // store.register_setting::<MultiKeySettings>(cx);
1330 // store.register_setting::<UserSettings>(cx);
1331 // store.register_setting::<LanguageSettings>(cx);
1332
1333 // entries added and updated
1334 check_settings_update(
1335 &mut store,
1336 r#"{
1337 "languages": {
1338 "JSON": {
1339 "auto_indent": true
1340 }
1341 }
1342 }"#
1343 .unindent(),
1344 |settings| {
1345 settings
1346 .languages_mut()
1347 .get_mut("JSON")
1348 .unwrap()
1349 .auto_indent = Some(false);
1350
1351 settings.languages_mut().insert(
1352 "Rust".into(),
1353 LanguageSettingsContent {
1354 auto_indent: Some(true),
1355 ..Default::default()
1356 },
1357 );
1358 },
1359 r#"{
1360 "languages": {
1361 "Rust": {
1362 "auto_indent": true
1363 },
1364 "JSON": {
1365 "auto_indent": false
1366 }
1367 }
1368 }"#
1369 .unindent(),
1370 cx,
1371 );
1372
1373 // entries removed
1374 check_settings_update(
1375 &mut store,
1376 r#"{
1377 "languages": {
1378 "Rust": {
1379 "language_setting_2": true
1380 },
1381 "JSON": {
1382 "language_setting_1": false
1383 }
1384 }
1385 }"#
1386 .unindent(),
1387 |settings| {
1388 settings.languages_mut().remove("JSON").unwrap();
1389 },
1390 r#"{
1391 "languages": {
1392 "Rust": {
1393 "language_setting_2": true
1394 }
1395 }
1396 }"#
1397 .unindent(),
1398 cx,
1399 );
1400
1401 check_settings_update(
1402 &mut store,
1403 r#"{
1404 "languages": {
1405 "Rust": {
1406 "language_setting_2": true
1407 },
1408 "JSON": {
1409 "language_setting_1": false
1410 }
1411 }
1412 }"#
1413 .unindent(),
1414 |settings| {
1415 settings.languages_mut().remove("Rust").unwrap();
1416 },
1417 r#"{
1418 "languages": {
1419 "JSON": {
1420 "language_setting_1": false
1421 }
1422 }
1423 }"#
1424 .unindent(),
1425 cx,
1426 );
1427
1428 // weird formatting
1429 check_settings_update(
1430 &mut store,
1431 r#"{
1432 "title_bar": { "show": "always", "name": "Max" }
1433 }"#
1434 .unindent(),
1435 |settings| {
1436 dbg!(&settings.title_bar);
1437 settings.title_bar.as_mut().unwrap().show = Some(TitleBarVisibilityContent::Never);
1438 dbg!(&settings.title_bar);
1439 },
1440 r#"{
1441 "title_bar": { "show": "never", "name": "Max" }
1442 }"#
1443 .unindent(),
1444 cx,
1445 );
1446
1447 // single-line formatting, other keys
1448 check_settings_update(
1449 &mut store,
1450 r#"{ "one": 1, "two": 2 }"#.to_owned(),
1451 |settings| settings.auto_update = Some(true),
1452 r#"{ "auto_update": true, "one": 1, "two": 2 }"#.to_owned(),
1453 cx,
1454 );
1455
1456 // empty object
1457 check_settings_update(
1458 &mut store,
1459 r#"{
1460 "title_bar": {}
1461 }"#
1462 .unindent(),
1463 |settings| settings.title_bar.as_mut().unwrap().show_menus = Some(true),
1464 r#"{
1465 "title_bar": {
1466 "show_menus": true
1467 }
1468 }"#
1469 .unindent(),
1470 cx,
1471 );
1472
1473 // no content
1474 check_settings_update(
1475 &mut store,
1476 r#""#.unindent(),
1477 |settings| {
1478 settings.title_bar = Some(TitleBarSettingsContent {
1479 show_branch_name: Some(true),
1480 ..Default::default()
1481 })
1482 },
1483 r#"{
1484 "title_bar": {
1485 "show_branch_name": true
1486 }
1487 }
1488 "#
1489 .unindent(),
1490 cx,
1491 );
1492
1493 check_settings_update(
1494 &mut store,
1495 r#"{
1496 }
1497 "#
1498 .unindent(),
1499 |settings| settings.title_bar.get_or_insert_default().show_branch_name = Some(true),
1500 r#"{
1501 "title_bar": {
1502 "show_branch_name": true
1503 }
1504 }
1505 "#
1506 .unindent(),
1507 cx,
1508 );
1509 }
1510
1511 // #[gpui::test]
1512 // fn test_vscode_import(cx: &mut App) {
1513 // let mut store = SettingsStore::new(cx);
1514 // store.register_setting::<UserSettings>(cx);
1515 // store.register_setting::<JournalSettings>(cx);
1516 // store.register_setting::<LanguageSettings>(cx);
1517 // store.register_setting::<MultiKeySettings>(cx);
1518
1519 // // create settings that werent present
1520 // check_vscode_import(
1521 // &mut store,
1522 // r#"{
1523 // }
1524 // "#
1525 // .unindent(),
1526 // r#" { "user.age": 37 } "#.to_owned(),
1527 // r#"{
1528 // "user": {
1529 // "age": 37
1530 // }
1531 // }
1532 // "#
1533 // .unindent(),
1534 // cx,
1535 // );
1536
1537 // // persist settings that were present
1538 // check_vscode_import(
1539 // &mut store,
1540 // r#"{
1541 // "user": {
1542 // "staff": true,
1543 // "age": 37
1544 // }
1545 // }
1546 // "#
1547 // .unindent(),
1548 // r#"{ "user.age": 42 }"#.to_owned(),
1549 // r#"{
1550 // "user": {
1551 // "staff": true,
1552 // "age": 42
1553 // }
1554 // }
1555 // "#
1556 // .unindent(),
1557 // cx,
1558 // );
1559
1560 // // don't clobber settings that aren't present in vscode
1561 // check_vscode_import(
1562 // &mut store,
1563 // r#"{
1564 // "user": {
1565 // "staff": true,
1566 // "age": 37
1567 // }
1568 // }
1569 // "#
1570 // .unindent(),
1571 // r#"{}"#.to_owned(),
1572 // r#"{
1573 // "user": {
1574 // "staff": true,
1575 // "age": 37
1576 // }
1577 // }
1578 // "#
1579 // .unindent(),
1580 // cx,
1581 // );
1582
1583 // // custom enum
1584 // check_vscode_import(
1585 // &mut store,
1586 // r#"{
1587 // "journal": {
1588 // "hour_format": "hour12"
1589 // }
1590 // }
1591 // "#
1592 // .unindent(),
1593 // r#"{ "time_format": "24" }"#.to_owned(),
1594 // r#"{
1595 // "journal": {
1596 // "hour_format": "hour24"
1597 // }
1598 // }
1599 // "#
1600 // .unindent(),
1601 // cx,
1602 // );
1603
1604 // // Multiple keys for one setting
1605 // check_vscode_import(
1606 // &mut store,
1607 // r#"{
1608 // "key1": "value"
1609 // }
1610 // "#
1611 // .unindent(),
1612 // r#"{
1613 // "key_1_first": "hello",
1614 // "key_1_second": "world"
1615 // }"#
1616 // .to_owned(),
1617 // r#"{
1618 // "key1": "hello world"
1619 // }
1620 // "#
1621 // .unindent(),
1622 // cx,
1623 // );
1624
1625 // // Merging lists together entries added and updated
1626 // check_vscode_import(
1627 // &mut store,
1628 // r#"{
1629 // "languages": {
1630 // "JSON": {
1631 // "language_setting_1": true
1632 // },
1633 // "Rust": {
1634 // "language_setting_2": true
1635 // }
1636 // }
1637 // }"#
1638 // .unindent(),
1639 // r#"{
1640 // "vscode_languages": [
1641 // {
1642 // "name": "JavaScript",
1643 // "language_setting_1": true
1644 // },
1645 // {
1646 // "name": "Rust",
1647 // "language_setting_2": false
1648 // }
1649 // ]
1650 // }"#
1651 // .to_owned(),
1652 // r#"{
1653 // "languages": {
1654 // "JavaScript": {
1655 // "language_setting_1": true
1656 // },
1657 // "JSON": {
1658 // "language_setting_1": true
1659 // },
1660 // "Rust": {
1661 // "language_setting_2": false
1662 // }
1663 // }
1664 // }"#
1665 // .unindent(),
1666 // cx,
1667 // );
1668 // }
1669
1670 // fn check_vscode_import(
1671 // store: &mut SettingsStore,
1672 // old: String,
1673 // vscode: String,
1674 // expected: String,
1675 // cx: &mut App,
1676 // ) {
1677 // store.set_user_settings(&old, cx).ok();
1678 // let new = store.get_vscode_edits(
1679 // old,
1680 // &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
1681 // );
1682 // pretty_assertions::assert_eq!(new, expected);
1683 // }
1684
1685 // #[derive(Debug, PartialEq, Deserialize, SettingsUi)]
1686 // struct UserSettings {
1687 // name: String,
1688 // age: u32,
1689 // staff: bool,
1690 // }
1691
1692 // #[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
1693 // #[settings_key(key = "user")]
1694 // struct UserSettingsContent {
1695 // name: Option<String>,
1696 // age: Option<u32>,
1697 // staff: Option<bool>,
1698 // }
1699
1700 // impl Settings for UserSettings {
1701 // type FileContent = UserSettingsContent;
1702
1703 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1704 // sources.json_merge()
1705 // }
1706
1707 // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
1708 // vscode.u32_setting("user.age", &mut current.age);
1709 // }
1710 // }
1711
1712 // #[derive(Debug, Deserialize, PartialEq)]
1713 // struct TurboSetting(bool);
1714
1715 // #[derive(
1716 // Copy,
1717 // Clone,
1718 // PartialEq,
1719 // Eq,
1720 // Debug,
1721 // Default,
1722 // serde::Serialize,
1723 // serde::Deserialize,
1724 // SettingsUi,
1725 // SettingsKey,
1726 // JsonSchema,
1727 // )]
1728 // #[serde(default)]
1729 // #[settings_key(None)]
1730 // pub struct TurboSettingContent {
1731 // turbo: Option<bool>,
1732 // }
1733
1734 // impl Settings for TurboSetting {
1735 // type FileContent = TurboSettingContent;
1736
1737 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1738 // Ok(Self(
1739 // sources
1740 // .user
1741 // .or(sources.server)
1742 // .unwrap_or(sources.default)
1743 // .turbo
1744 // .unwrap_or_default(),
1745 // ))
1746 // }
1747
1748 // fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut Self::FileContent) {}
1749 // }
1750
1751 // #[derive(Clone, Debug, PartialEq, Deserialize)]
1752 // struct MultiKeySettings {
1753 // #[serde(default)]
1754 // key1: String,
1755 // #[serde(default)]
1756 // key2: String,
1757 // }
1758
1759 // #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
1760 // #[settings_key(None)]
1761 // struct MultiKeySettingsJson {
1762 // key1: Option<String>,
1763 // key2: Option<String>,
1764 // }
1765
1766 // impl Settings for MultiKeySettings {
1767 // type FileContent = MultiKeySettingsJson;
1768
1769 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1770 // sources.json_merge()
1771 // }
1772
1773 // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
1774 // let first_value = vscode.read_string("key_1_first");
1775 // let second_value = vscode.read_string("key_1_second");
1776
1777 // if let Some((first, second)) = first_value.zip(second_value) {
1778 // current.key1 = Some(format!("{} {}", first, second));
1779 // }
1780 // }
1781 // }
1782
1783 // #[derive(Debug, Deserialize)]
1784 // struct JournalSettings {
1785 // #[expect(unused)]
1786 // pub path: String,
1787 // #[expect(unused)]
1788 // pub hour_format: HourFormat,
1789 // }
1790
1791 // #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1792 // #[serde(rename_all = "snake_case")]
1793 // enum HourFormat {
1794 // Hour12,
1795 // Hour24,
1796 // }
1797
1798 // #[derive(
1799 // Clone, Default, Debug, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey,
1800 // )]
1801 // #[settings_key(key = "journal")]
1802 // struct JournalSettingsJson {
1803 // pub path: Option<String>,
1804 // pub hour_format: Option<HourFormat>,
1805 // }
1806
1807 // impl Settings for JournalSettings {
1808 // type FileContent = JournalSettingsJson;
1809
1810 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1811 // sources.json_merge()
1812 // }
1813
1814 // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
1815 // vscode.enum_setting("time_format", &mut current.hour_format, |s| match s {
1816 // "12" => Some(HourFormat::Hour12),
1817 // "24" => Some(HourFormat::Hour24),
1818 // _ => None,
1819 // });
1820 // }
1821 // }
1822
1823 // #[gpui::test]
1824 // fn test_global_settings(cx: &mut App) {
1825 // let mut store = SettingsStore::new(cx);
1826 // store.register_setting::<UserSettings>(cx);
1827 // store
1828 // .set_default_settings(
1829 // r#"{
1830 // "user": {
1831 // "name": "John Doe",
1832 // "age": 30,
1833 // "staff": false
1834 // }
1835 // }"#,
1836 // cx,
1837 // )
1838 // .unwrap();
1839
1840 // // Set global settings - these should override defaults but not user settings
1841 // store
1842 // .set_global_settings(
1843 // r#"{
1844 // "user": {
1845 // "name": "Global User",
1846 // "age": 35,
1847 // "staff": true
1848 // }
1849 // }"#,
1850 // cx,
1851 // )
1852 // .unwrap();
1853
1854 // // Before user settings, global settings should apply
1855 // assert_eq!(
1856 // store.get::<UserSettings>(None),
1857 // &UserSettings {
1858 // name: "Global User".to_string(),
1859 // age: 35,
1860 // staff: true,
1861 // }
1862 // );
1863
1864 // // Set user settings - these should override both defaults and global
1865 // store
1866 // .set_user_settings(
1867 // r#"{
1868 // "user": {
1869 // "age": 40
1870 // }
1871 // }"#,
1872 // cx,
1873 // )
1874 // .unwrap();
1875
1876 // // User settings should override global settings
1877 // assert_eq!(
1878 // store.get::<UserSettings>(None),
1879 // &UserSettings {
1880 // name: "Global User".to_string(), // Name from global settings
1881 // age: 40, // Age from user settings
1882 // staff: true, // Staff from global settings
1883 // }
1884 // );
1885 // }
1886
1887 // #[derive(
1888 // Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey,
1889 // )]
1890 // #[settings_key(None)]
1891 // struct LanguageSettings {
1892 // #[serde(default)]
1893 // languages: HashMap<String, LanguageSettingEntry>,
1894 // }
1895
1896 // #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
1897 // struct LanguageSettingEntry {
1898 // language_setting_1: Option<bool>,
1899 // language_setting_2: Option<bool>,
1900 // }
1901
1902 // impl Settings for LanguageSettings {
1903 // type FileContent = Self;
1904
1905 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1906 // sources.json_merge()
1907 // }
1908
1909 // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
1910 // current.languages.extend(
1911 // vscode
1912 // .read_value("vscode_languages")
1913 // .and_then(|value| value.as_array())
1914 // .map(|languages| {
1915 // languages
1916 // .iter()
1917 // .filter_map(|value| value.as_object())
1918 // .filter_map(|item| {
1919 // let mut rest = item.clone();
1920 // let name = rest.remove("name")?.as_str()?.to_string();
1921 // let entry = serde_json::from_value::<LanguageSettingEntry>(
1922 // serde_json::Value::Object(rest),
1923 // )
1924 // .ok()?;
1925
1926 // Some((name, entry))
1927 // })
1928 // })
1929 // .into_iter()
1930 // .flatten(),
1931 // );
1932 // }
1933 // }
1934}