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 serde::{Serialize, de::DeserializeOwned};
14use serde_json::Value;
15use smallvec::SmallVec;
16use std::{
17 any::{Any, TypeId, type_name},
18 env,
19 fmt::Debug,
20 ops::Range,
21 path::{Path, PathBuf},
22 str::{self, FromStr},
23 sync::Arc,
24};
25use util::{
26 ResultExt as _, merge_non_null_json_value_into,
27 schemars::{DefaultDenyUnknownFields, add_new_subschema},
28};
29
30pub type EditorconfigProperties = ec4rs::Properties;
31
32use crate::{
33 ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, SettingsUiEntry,
34 VsCodeSettings, WorktreeId, default_settings, parse_json_with_comments,
35 replace_value_in_json_text,
36 settings_content::{
37 ExtensionsSettingsContent, ProjectSettingsContent, ServerSettingsContent, SettingsContent,
38 UserSettingsContent,
39 },
40 update_value_in_json_text,
41};
42
43pub trait SettingsKey: 'static + Send + Sync {
44 /// The name of a key within the JSON file from which this setting should
45 /// be deserialized. If this is `None`, then the setting will be deserialized
46 /// from the root object.
47 const KEY: Option<&'static str>;
48
49 const FALLBACK_KEY: Option<&'static str> = None;
50}
51
52/// A value that can be defined as a user setting.
53///
54/// Settings can be loaded from a combination of multiple JSON files.
55pub trait Settings: 'static + Send + Sync + Sized {
56 /// The name of the keys in the [`FileContent`](Self::FileContent) that should
57 /// always be written to a settings file, even if their value matches the default
58 /// value.
59 ///
60 /// This is useful for tagged [`FileContent`](Self::FileContent)s where the tag
61 /// is a "version" field that should always be persisted, even if the current
62 /// user settings match the current version of the settings.
63 const PRESERVED_KEYS: Option<&'static [&'static str]> = None;
64
65 fn from_file(content: &SettingsContent) -> Option<Self>;
66
67 fn refine(&mut self, content: &SettingsContent);
68
69 fn missing_default() -> anyhow::Error {
70 anyhow::anyhow!("missing default for: {}", std::any::type_name::<Self>())
71 }
72
73 /// Use [the helpers in the vscode_import module](crate::vscode_import) to apply known
74 /// equivalent settings from a vscode config to our config
75 fn import_from_vscode(vscode: &VsCodeSettings, current: &mut SettingsContent);
76
77 #[track_caller]
78 fn register(cx: &mut App)
79 where
80 Self: Sized,
81 {
82 SettingsStore::update_global(cx, |store, cx| {
83 store.register_setting::<Self>(cx);
84 });
85 }
86
87 #[track_caller]
88 fn get<'a>(path: Option<SettingsLocation>, cx: &'a App) -> &'a Self
89 where
90 Self: Sized,
91 {
92 cx.global::<SettingsStore>().get(path)
93 }
94
95 #[track_caller]
96 fn get_global(cx: &App) -> &Self
97 where
98 Self: Sized,
99 {
100 cx.global::<SettingsStore>().get(None)
101 }
102
103 #[track_caller]
104 fn try_get(cx: &App) -> Option<&Self>
105 where
106 Self: Sized,
107 {
108 if cx.has_global::<SettingsStore>() {
109 cx.global::<SettingsStore>().try_get(None)
110 } else {
111 None
112 }
113 }
114
115 #[track_caller]
116 fn try_read_global<R>(cx: &AsyncApp, f: impl FnOnce(&Self) -> R) -> Option<R>
117 where
118 Self: Sized,
119 {
120 cx.try_read_global(|s: &SettingsStore, _| f(s.get(None)))
121 }
122
123 #[track_caller]
124 fn override_global(settings: Self, cx: &mut App)
125 where
126 Self: Sized,
127 {
128 cx.global_mut::<SettingsStore>().override_global(settings)
129 }
130}
131
132#[derive(Clone, Copy, Debug)]
133pub struct SettingsSources<'a, T> {
134 /// The default Zed settings.
135 pub default: &'a T,
136 /// Global settings (loaded before user settings).
137 pub global: Option<&'a T>,
138 /// Settings provided by extensions.
139 pub extensions: Option<&'a T>,
140 /// The user settings.
141 pub user: Option<&'a T>,
142 /// The user settings for the current release channel.
143 pub release_channel: Option<&'a T>,
144 /// The user settings for the current operating system.
145 pub operating_system: Option<&'a T>,
146 /// The settings associated with an enabled settings profile
147 pub profile: Option<&'a T>,
148 /// The server's settings.
149 pub server: Option<&'a T>,
150 /// The project settings, ordered from least specific to most specific.
151 pub project: &'a [&'a T],
152}
153
154impl<'a, T: Serialize> SettingsSources<'a, T> {
155 /// Returns an iterator over the default settings as well as all settings customizations.
156 pub fn defaults_and_customizations(&self) -> impl Iterator<Item = &T> {
157 [self.default].into_iter().chain(self.customizations())
158 }
159
160 /// Returns an iterator over all of the settings customizations.
161 pub fn customizations(&self) -> impl Iterator<Item = &T> {
162 self.global
163 .into_iter()
164 .chain(self.extensions)
165 .chain(self.user)
166 .chain(self.release_channel)
167 .chain(self.operating_system)
168 .chain(self.profile)
169 .chain(self.server)
170 .chain(self.project.iter().copied())
171 }
172
173 /// Returns the settings after performing a JSON merge of the provided customizations.
174 ///
175 /// Customizations later in the iterator win out over the earlier ones.
176 pub fn json_merge_with<O: DeserializeOwned>(
177 customizations: impl Iterator<Item = &'a T>,
178 ) -> Result<O> {
179 let mut merged = Value::Null;
180 for value in customizations {
181 merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged);
182 }
183 Ok(serde_json::from_value(merged)?)
184 }
185
186 /// Returns the settings after performing a JSON merge of the customizations into the
187 /// default settings.
188 ///
189 /// More-specific customizations win out over the less-specific ones.
190 pub fn json_merge<O: DeserializeOwned>(&'a self) -> Result<O> {
191 Self::json_merge_with(self.defaults_and_customizations())
192 }
193}
194
195#[derive(Clone, Copy, Debug)]
196pub struct SettingsLocation<'a> {
197 pub worktree_id: WorktreeId,
198 pub path: &'a Path,
199}
200
201/// A set of strongly-typed setting values defined via multiple config files.
202pub struct SettingsStore {
203 setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
204 default_settings: SettingsContent,
205 user_settings: Option<UserSettingsContent>,
206 global_settings: Option<SettingsContent>,
207
208 extension_settings: Option<SettingsContent>,
209 server_settings: Option<SettingsContent>,
210 local_settings: BTreeMap<(WorktreeId, Arc<Path>), SettingsContent>,
211 raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc<Path>), (String, Option<Editorconfig>)>,
212
213 _setting_file_updates: Task<()>,
214 setting_file_updates_tx:
215 mpsc::UnboundedSender<Box<dyn FnOnce(AsyncApp) -> LocalBoxFuture<'static, Result<()>>>>,
216}
217
218#[derive(Clone)]
219pub struct Editorconfig {
220 pub is_root: bool,
221 pub sections: SmallVec<[Section; 5]>,
222}
223
224impl FromStr for Editorconfig {
225 type Err = anyhow::Error;
226
227 fn from_str(contents: &str) -> Result<Self, Self::Err> {
228 let parser = ConfigParser::new_buffered(contents.as_bytes())
229 .context("creating editorconfig parser")?;
230 let is_root = parser.is_root;
231 let sections = parser
232 .collect::<Result<SmallVec<_>, _>>()
233 .context("parsing editorconfig sections")?;
234 Ok(Self { is_root, sections })
235 }
236}
237
238#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
239pub enum LocalSettingsKind {
240 Settings,
241 Tasks,
242 Editorconfig,
243 Debug,
244}
245
246impl Global for SettingsStore {}
247
248#[derive(Debug)]
249struct SettingValue<T> {
250 global_value: Option<T>,
251 local_values: Vec<(WorktreeId, Arc<Path>, T)>,
252}
253
254trait AnySettingValue: 'static + Send + Sync {
255 fn setting_type_name(&self) -> &'static str;
256
257 fn from_file(&self, s: &SettingsContent) -> Option<Box<dyn Any>>;
258 fn refine(&self, value: &mut dyn Any, s: &[&SettingsContent]);
259
260 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any;
261 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)>;
262 fn set_global_value(&mut self, value: Box<dyn Any>);
263 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>);
264 fn json_schema(&self, generator: &mut schemars::SchemaGenerator) -> schemars::Schema;
265 fn edits_for_update(
266 &self,
267 raw_settings: &serde_json::Value,
268 tab_size: usize,
269 vscode_settings: &VsCodeSettings,
270 text: &mut String,
271 edits: &mut Vec<(Range<usize>, String)>,
272 );
273 fn settings_ui_item(&self) -> SettingsUiEntry;
274}
275
276struct DeserializedSetting(Box<dyn Any>);
277
278impl SettingsStore {
279 pub fn new(cx: &App, default_settings: &str) -> Self {
280 let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();
281 let default_settings = parse_json_with_comments(default_settings).unwrap();
282 Self {
283 setting_values: Default::default(),
284 default_settings,
285 global_settings: None,
286 server_settings: None,
287 user_settings: Some(Default::default()), // todo!()
288 extension_settings: None,
289 local_settings: BTreeMap::default(),
290 raw_editorconfig_settings: BTreeMap::default(),
291 setting_file_updates_tx,
292 _setting_file_updates: cx.spawn(async move |cx| {
293 while let Some(setting_file_update) = setting_file_updates_rx.next().await {
294 (setting_file_update)(cx.clone()).await.log_err();
295 }
296 }),
297 }
298 }
299
300 pub fn observe_active_settings_profile_name(cx: &mut App) -> gpui::Subscription {
301 cx.observe_global::<ActiveSettingsProfileName>(|cx| {
302 Self::update_global(cx, |store, cx| {
303 store.recompute_values(None, cx).log_err();
304 });
305 })
306 }
307
308 pub fn update<C, R>(cx: &mut C, f: impl FnOnce(&mut Self, &mut C) -> R) -> R
309 where
310 C: BorrowAppContext,
311 {
312 cx.update_global(f)
313 }
314
315 /// Add a new type of setting to the store.
316 pub fn register_setting<T: Settings>(&mut self, cx: &mut App) {
317 let setting_type_id = TypeId::of::<T>();
318 let entry = self.setting_values.entry(setting_type_id);
319
320 if matches!(entry, hash_map::Entry::Occupied(_)) {
321 return;
322 }
323
324 let setting_value = entry.or_insert(Box::new(SettingValue::<T> {
325 global_value: None,
326 local_values: Vec::new(),
327 }));
328
329 let mut refinements = Vec::default();
330
331 if let Some(extension_settings) = self.extension_settings.as_ref() {
332 refinements.push(extension_settings)
333 }
334
335 if let Some(user_settings) = self.user_settings.as_ref() {
336 refinements.push(&user_settings.content);
337 if let Some(release_channel) = user_settings.for_release_channel() {
338 refinements.push(release_channel)
339 }
340 if let Some(os) = user_settings.for_os() {
341 refinements.push(os)
342 }
343 if let Some(profile) = user_settings.for_profile(cx) {
344 refinements.push(profile)
345 }
346 }
347
348 if let Some(server_settings) = self.server_settings.as_ref() {
349 refinements.push(server_settings)
350 }
351 // todo!() unwrap...
352 let mut value = T::from_file(&self.default_settings).unwrap();
353 for refinement in refinements {
354 value.refine(refinement)
355 }
356
357 setting_value.set_global_value(Box::new(value));
358
359 // todo!() local settings
360 // (they weren't handled before...)
361 }
362
363 /// Get the value of a setting.
364 ///
365 /// Panics if the given setting type has not been registered, or if there is no
366 /// value for this setting.
367 pub fn get<T: Settings>(&self, path: Option<SettingsLocation>) -> &T {
368 self.setting_values
369 .get(&TypeId::of::<T>())
370 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
371 .value_for_path(path)
372 .downcast_ref::<T>()
373 .expect("no default value for setting type")
374 }
375
376 /// Get the value of a setting.
377 ///
378 /// Does not panic
379 pub fn try_get<T: Settings>(&self, path: Option<SettingsLocation>) -> Option<&T> {
380 self.setting_values
381 .get(&TypeId::of::<T>())
382 .map(|value| value.value_for_path(path))
383 .and_then(|value| value.downcast_ref::<T>())
384 }
385
386 /// Get all values from project specific settings
387 pub fn get_all_locals<T: Settings>(&self) -> Vec<(WorktreeId, Arc<Path>, &T)> {
388 self.setting_values
389 .get(&TypeId::of::<T>())
390 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
391 .all_local_values()
392 .into_iter()
393 .map(|(id, path, any)| {
394 (
395 id,
396 path,
397 any.downcast_ref::<T>()
398 .expect("wrong value type for setting"),
399 )
400 })
401 .collect()
402 }
403
404 /// Override the global value for a setting.
405 ///
406 /// The given value will be overwritten if the user settings file changes.
407 pub fn override_global<T: Settings>(&mut self, value: T) {
408 self.setting_values
409 .get_mut(&TypeId::of::<T>())
410 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
411 .set_global_value(Box::new(value))
412 }
413
414 /// Get the user's settings as a raw JSON value.
415 ///
416 /// For user-facing functionality use the typed setting interface.
417 /// (e.g. ProjectSettings::get_global(cx))
418 pub fn raw_user_settings(&self) -> Option<&UserSettingsContent> {
419 self.user_settings.as_ref()
420 }
421
422 /// Replaces current settings with the values from the given JSON.
423 pub fn set_raw_user_settings(
424 &mut self,
425 new_settings: UserSettingsContent,
426 cx: &mut App,
427 ) -> Result<()> {
428 self.user_settings = Some(new_settings);
429 self.recompute_values(None, cx)?;
430 Ok(())
431 }
432
433 /// Get the configured settings profile names.
434 pub fn configured_settings_profiles(&self) -> impl Iterator<Item = &str> {
435 self.user_settings
436 .iter()
437 .flat_map(|settings| settings.profiles.keys().map(|k| k.as_str()))
438 }
439
440 /// Access the raw JSON value of the default settings.
441 pub fn raw_default_settings(&self) -> &SettingsContent {
442 &self.default_settings
443 }
444
445 #[cfg(any(test, feature = "test-support"))]
446 pub fn test(cx: &mut App) -> Self {
447 Self::new(cx, &crate::test_settings())
448 }
449
450 /// Updates the value of a setting in the user's global configuration.
451 ///
452 /// This is only for tests. Normally, settings are only loaded from
453 /// JSON files.
454 #[cfg(any(test, feature = "test-support"))]
455 pub fn update_user_settings(
456 &mut self,
457 cx: &mut App,
458 update: impl FnOnce(&mut SettingsContent),
459 ) {
460 let mut content = self.user_settings.as_ref().unwrap().content.clone();
461 update(&mut content);
462 let new_text = serde_json::to_string(&UserSettingsContent {
463 content,
464 ..Default::default()
465 })
466 .unwrap();
467 self.set_user_settings(&new_text, cx).unwrap();
468 }
469
470 pub async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
471 match fs.load(paths::settings_file()).await {
472 result @ Ok(_) => result,
473 Err(err) => {
474 if let Some(e) = err.downcast_ref::<std::io::Error>()
475 && e.kind() == std::io::ErrorKind::NotFound
476 {
477 return Ok(crate::initial_user_settings_content().to_string());
478 }
479 Err(err)
480 }
481 }
482 }
483
484 fn update_settings_file_inner(
485 &self,
486 fs: Arc<dyn Fs>,
487 update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>,
488 ) -> oneshot::Receiver<Result<()>> {
489 let (tx, rx) = oneshot::channel::<Result<()>>();
490 self.setting_file_updates_tx
491 .unbounded_send(Box::new(move |cx: AsyncApp| {
492 async move {
493 let res = async move {
494 let old_text = Self::load_settings(&fs).await?;
495 let new_text = update(old_text, cx)?;
496 let settings_path = paths::settings_file().as_path();
497 if fs.is_file(settings_path).await {
498 let resolved_path =
499 fs.canonicalize(settings_path).await.with_context(|| {
500 format!(
501 "Failed to canonicalize settings path {:?}",
502 settings_path
503 )
504 })?;
505
506 fs.atomic_write(resolved_path.clone(), new_text)
507 .await
508 .with_context(|| {
509 format!("Failed to write settings to file {:?}", resolved_path)
510 })?;
511 } else {
512 fs.atomic_write(settings_path.to_path_buf(), new_text)
513 .await
514 .with_context(|| {
515 format!("Failed to write settings to file {:?}", settings_path)
516 })?;
517 }
518 anyhow::Ok(())
519 }
520 .await;
521
522 let new_res = match &res {
523 Ok(_) => anyhow::Ok(()),
524 Err(e) => Err(anyhow::anyhow!("Failed to write settings to file {:?}", e)),
525 };
526
527 _ = tx.send(new_res);
528 res
529 }
530 .boxed_local()
531 }))
532 .map_err(|err| anyhow::format_err!("Failed to update settings file: {}", err))
533 .log_with_level(log::Level::Warn);
534 return rx;
535 }
536
537 pub fn update_settings_file_at_path(
538 &self,
539 fs: Arc<dyn Fs>,
540 path: &[impl AsRef<str>],
541 new_value: serde_json::Value,
542 ) -> oneshot::Receiver<Result<()>> {
543 let key_path = path
544 .into_iter()
545 .map(AsRef::as_ref)
546 .map(SharedString::new)
547 .collect::<Vec<_>>();
548 let update = move |mut old_text: String, cx: AsyncApp| {
549 cx.read_global(|store: &SettingsStore, _cx| {
550 // todo(settings_ui) use `update_value_in_json_text` for merging new and old objects with comment preservation, needs old value though...
551 let (range, replacement) = replace_value_in_json_text(
552 &old_text,
553 key_path.as_slice(),
554 store.json_tab_size(),
555 Some(&new_value),
556 None,
557 );
558 old_text.replace_range(range, &replacement);
559 old_text
560 })
561 };
562 self.update_settings_file_inner(fs, update)
563 }
564
565 pub fn update_settings_file(
566 &self,
567 fs: Arc<dyn Fs>,
568 update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
569 ) {
570 _ = self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
571 cx.read_global(|store: &SettingsStore, cx| {
572 store.new_text_for_update(old_text, |content| update(content, cx))
573 })
574 });
575 }
576
577 pub fn import_vscode_settings(
578 &self,
579 fs: Arc<dyn Fs>,
580 vscode_settings: VsCodeSettings,
581 ) -> oneshot::Receiver<Result<()>> {
582 self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
583 cx.read_global(|store: &SettingsStore, _cx| {
584 store.get_vscode_edits(old_text, &vscode_settings)
585 })
586 })
587 }
588
589 pub fn settings_ui_items(&self) -> impl IntoIterator<Item = SettingsUiEntry> {
590 self.setting_values
591 .values()
592 .map(|item| item.settings_ui_item())
593 }
594}
595
596impl SettingsStore {
597 /// Updates the value of a setting in a JSON file, returning the new text
598 /// for that JSON file.
599 pub fn new_text_for_update(
600 &self,
601 old_text: String,
602 update: impl FnOnce(&mut SettingsContent),
603 ) -> String {
604 let edits = self.edits_for_update(&old_text, update);
605 let mut new_text = old_text;
606 for (range, replacement) in edits.into_iter() {
607 new_text.replace_range(range, &replacement);
608 }
609 new_text
610 }
611
612 pub fn get_vscode_edits(&self, mut old_text: String, vscode: &VsCodeSettings) -> String {
613 let mut new_text = old_text.clone();
614 let mut edits: Vec<(Range<usize>, String)> = Vec::new();
615 let raw_settings = parse_json_with_comments::<Value>(&old_text).unwrap_or_default();
616 let tab_size = self.json_tab_size();
617 for v in self.setting_values.values() {
618 v.edits_for_update(&raw_settings, tab_size, vscode, &mut old_text, &mut edits);
619 }
620 for (range, replacement) in edits.into_iter() {
621 new_text.replace_range(range, &replacement);
622 }
623 new_text
624 }
625
626 /// Updates the value of a setting in a JSON file, returning a list
627 /// of edits to apply to the JSON file.
628 pub fn edits_for_update(
629 &self,
630 text: &str,
631 update: impl FnOnce(&mut SettingsContent),
632 ) -> Vec<(Range<usize>, String)> {
633 let old_content: UserSettingsContent = serde_json::from_str(text).unwrap_or_default();
634 let mut new_content = old_content.clone();
635 update(&mut new_content.content);
636
637 let old_value = serde_json::to_value(&old_content).unwrap();
638 let new_value = serde_json::to_value(new_content).unwrap();
639
640 let mut key_path = Vec::new();
641 let mut edits = Vec::new();
642 let tab_size = self.json_tab_size();
643 let mut text = text.to_string();
644 update_value_in_json_text(
645 &mut text,
646 &mut key_path,
647 tab_size,
648 &old_value,
649 &new_value,
650 &[], // todo!() is this still needed?
651 &mut edits,
652 );
653 edits
654 }
655
656 pub fn json_tab_size(&self) -> usize {
657 2
658 }
659
660 /// Sets the default settings via a JSON string.
661 ///
662 /// The string should contain a JSON object with a default value for every setting.
663 pub fn set_default_settings(
664 &mut self,
665 default_settings_content: &str,
666 cx: &mut App,
667 ) -> Result<()> {
668 self.default_settings = parse_json_with_comments(default_settings_content)?;
669 self.recompute_values(None, cx)?;
670 Ok(())
671 }
672
673 /// Sets the user settings via a JSON string.
674 pub fn set_user_settings(&mut self, user_settings_content: &str, cx: &mut App) -> Result<()> {
675 let settings: UserSettingsContent = if user_settings_content.is_empty() {
676 parse_json_with_comments("{}")?
677 } else {
678 parse_json_with_comments(user_settings_content)?
679 };
680
681 self.user_settings = Some(settings);
682 self.recompute_values(None, cx)?;
683 Ok(())
684 }
685
686 /// Sets the global settings via a JSON string.
687 pub fn set_global_settings(
688 &mut self,
689 global_settings_content: &str,
690 cx: &mut App,
691 ) -> Result<()> {
692 let settings: SettingsContent = if global_settings_content.is_empty() {
693 parse_json_with_comments("{}")?
694 } else {
695 parse_json_with_comments(global_settings_content)?
696 };
697
698 self.global_settings = Some(settings);
699 self.recompute_values(None, cx)?;
700 Ok(())
701 }
702
703 pub fn set_server_settings(
704 &mut self,
705 server_settings_content: &str,
706 cx: &mut App,
707 ) -> Result<()> {
708 let settings: Option<ServerSettingsContent> = if server_settings_content.is_empty() {
709 None
710 } else {
711 parse_json_with_comments(server_settings_content)?
712 };
713
714 // Rewrite the server settings into a content type
715 self.server_settings = settings.map(|settings| SettingsContent {
716 project: settings.project,
717 ..Default::default()
718 });
719
720 todo!();
721 // self.server_settings = Some(settings);
722 self.recompute_values(None, cx)?;
723 Ok(())
724 }
725
726 /// Add or remove a set of local settings via a JSON string.
727 pub fn set_local_settings(
728 &mut self,
729 root_id: WorktreeId,
730 directory_path: Arc<Path>,
731 kind: LocalSettingsKind,
732 settings_content: Option<&str>,
733 cx: &mut App,
734 ) -> std::result::Result<(), InvalidSettingsError> {
735 let mut zed_settings_changed = false;
736 match (
737 kind,
738 settings_content
739 .map(|content| content.trim())
740 .filter(|content| !content.is_empty()),
741 ) {
742 (LocalSettingsKind::Tasks, _) => {
743 return Err(InvalidSettingsError::Tasks {
744 message: "Attempted to submit tasks into the settings store".to_string(),
745 path: directory_path.join(task_file_name()),
746 });
747 }
748 (LocalSettingsKind::Debug, _) => {
749 return Err(InvalidSettingsError::Debug {
750 message: "Attempted to submit debugger config into the settings store"
751 .to_string(),
752 path: directory_path.join(task_file_name()),
753 });
754 }
755 (LocalSettingsKind::Settings, None) => {
756 zed_settings_changed = self
757 .local_settings
758 .remove(&(root_id, directory_path.clone()))
759 .is_some()
760 }
761 (LocalSettingsKind::Editorconfig, None) => {
762 self.raw_editorconfig_settings
763 .remove(&(root_id, directory_path.clone()));
764 }
765 (LocalSettingsKind::Settings, Some(settings_contents)) => {
766 let new_settings = parse_json_with_comments::<ProjectSettingsContent>(
767 settings_contents,
768 )
769 .map_err(|e| InvalidSettingsError::LocalSettings {
770 path: directory_path.join(local_settings_file_relative_path()),
771 message: e.to_string(),
772 })?;
773 match self.local_settings.entry((root_id, directory_path.clone())) {
774 btree_map::Entry::Vacant(v) => {
775 v.insert(SettingsContent {
776 project: new_settings,
777 ..Default::default()
778 });
779 zed_settings_changed = true;
780 }
781 btree_map::Entry::Occupied(mut o) => {
782 if &o.get().project != &new_settings {
783 o.insert(SettingsContent {
784 project: new_settings,
785 ..Default::default()
786 });
787 zed_settings_changed = true;
788 }
789 }
790 }
791 }
792 (LocalSettingsKind::Editorconfig, Some(editorconfig_contents)) => {
793 match self
794 .raw_editorconfig_settings
795 .entry((root_id, directory_path.clone()))
796 {
797 btree_map::Entry::Vacant(v) => match editorconfig_contents.parse() {
798 Ok(new_contents) => {
799 v.insert((editorconfig_contents.to_owned(), Some(new_contents)));
800 }
801 Err(e) => {
802 v.insert((editorconfig_contents.to_owned(), None));
803 return Err(InvalidSettingsError::Editorconfig {
804 message: e.to_string(),
805 path: directory_path.join(EDITORCONFIG_NAME),
806 });
807 }
808 },
809 btree_map::Entry::Occupied(mut o) => {
810 if o.get().0 != editorconfig_contents {
811 match editorconfig_contents.parse() {
812 Ok(new_contents) => {
813 o.insert((
814 editorconfig_contents.to_owned(),
815 Some(new_contents),
816 ));
817 }
818 Err(e) => {
819 o.insert((editorconfig_contents.to_owned(), None));
820 return Err(InvalidSettingsError::Editorconfig {
821 message: e.to_string(),
822 path: directory_path.join(EDITORCONFIG_NAME),
823 });
824 }
825 }
826 }
827 }
828 }
829 }
830 };
831
832 if zed_settings_changed {
833 self.recompute_values(Some((root_id, &directory_path)), cx)?;
834 }
835 Ok(())
836 }
837
838 pub fn set_extension_settings(
839 &mut self,
840 content: ExtensionsSettingsContent,
841 cx: &mut App,
842 ) -> Result<()> {
843 self.extension_settings = Some(SettingsContent {
844 project: ProjectSettingsContent {
845 all_languages: content.all_languages,
846 },
847 ..Default::default()
848 });
849 self.recompute_values(None, cx)?;
850 Ok(())
851 }
852
853 /// Add or remove a set of local settings via a JSON string.
854 pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut App) -> Result<()> {
855 self.local_settings
856 .retain(|(worktree_id, _), _| worktree_id != &root_id);
857 self.recompute_values(Some((root_id, "".as_ref())), cx)?;
858 Ok(())
859 }
860
861 pub fn local_settings(
862 &self,
863 root_id: WorktreeId,
864 ) -> impl '_ + Iterator<Item = (Arc<Path>, String)> {
865 self.local_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)| (path.clone(), serde_json::to_string(content).unwrap()))
874 }
875
876 pub fn local_editorconfig_settings(
877 &self,
878 root_id: WorktreeId,
879 ) -> impl '_ + Iterator<Item = (Arc<Path>, String, Option<Editorconfig>)> {
880 self.raw_editorconfig_settings
881 .range(
882 (root_id, Path::new("").into())
883 ..(
884 WorktreeId::from_usize(root_id.to_usize() + 1),
885 Path::new("").into(),
886 ),
887 )
888 .map(|((_, path), (content, parsed_content))| {
889 (path.clone(), content.clone(), parsed_content.clone())
890 })
891 }
892
893 pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value {
894 todo!()
895 // let mut generator = schemars::generate::SchemaSettings::draft2019_09()
896 // .with_transform(DefaultDenyUnknownFields)
897 // .into_generator();
898 // let mut combined_schema = json!({
899 // "type": "object",
900 // "properties": {}
901 // });
902
903 // // Merge together settings schemas, similarly to json schema's "allOf". This merging is
904 // // recursive, though at time of writing this recursive nature isn't used very much. An
905 // // example of it is the schema for `jupyter` having contribution from both `EditorSettings`
906 // // and `JupyterSettings`.
907 // //
908 // // This logic could be removed in favor of "allOf", but then there isn't the opportunity to
909 // // validate and fully control the merge.
910 // for setting_value in self.setting_values.values() {
911 // let mut setting_schema = setting_value.json_schema(&mut generator);
912
913 // if let Some(key) = setting_value.key() {
914 // if let Some(properties) = combined_schema.get_mut("properties")
915 // && let Some(properties_obj) = properties.as_object_mut()
916 // {
917 // if let Some(target) = properties_obj.get_mut(key) {
918 // merge_schema(target, setting_schema.to_value());
919 // } else {
920 // properties_obj.insert(key.to_string(), setting_schema.to_value());
921 // }
922 // }
923 // } else {
924 // setting_schema.remove("description");
925 // setting_schema.remove("additionalProperties");
926 // merge_schema(&mut combined_schema, setting_schema.to_value());
927 // }
928 // }
929
930 // fn merge_schema(target: &mut serde_json::Value, source: serde_json::Value) {
931 // let (Some(target_obj), serde_json::Value::Object(source_obj)) =
932 // (target.as_object_mut(), source)
933 // else {
934 // return;
935 // };
936
937 // for (source_key, source_value) in source_obj {
938 // match source_key.as_str() {
939 // "properties" => {
940 // let serde_json::Value::Object(source_properties) = source_value else {
941 // log::error!(
942 // "bug: expected object for `{}` json schema field, but got: {}",
943 // source_key,
944 // source_value
945 // );
946 // continue;
947 // };
948 // let target_properties =
949 // target_obj.entry(source_key.clone()).or_insert(json!({}));
950 // let Some(target_properties) = target_properties.as_object_mut() else {
951 // log::error!(
952 // "bug: expected object for `{}` json schema field, but got: {}",
953 // source_key,
954 // target_properties
955 // );
956 // continue;
957 // };
958 // for (key, value) in source_properties {
959 // if let Some(existing) = target_properties.get_mut(&key) {
960 // merge_schema(existing, value);
961 // } else {
962 // target_properties.insert(key, value);
963 // }
964 // }
965 // }
966 // "allOf" | "anyOf" | "oneOf" => {
967 // let serde_json::Value::Array(source_array) = source_value else {
968 // log::error!(
969 // "bug: expected array for `{}` json schema field, but got: {}",
970 // source_key,
971 // source_value,
972 // );
973 // continue;
974 // };
975 // let target_array =
976 // target_obj.entry(source_key.clone()).or_insert(json!([]));
977 // let Some(target_array) = target_array.as_array_mut() else {
978 // log::error!(
979 // "bug: expected array for `{}` json schema field, but got: {}",
980 // source_key,
981 // target_array,
982 // );
983 // continue;
984 // };
985 // target_array.extend(source_array);
986 // }
987 // "type"
988 // | "$ref"
989 // | "enum"
990 // | "minimum"
991 // | "maximum"
992 // | "pattern"
993 // | "description"
994 // | "additionalProperties" => {
995 // if let Some(old_value) =
996 // target_obj.insert(source_key.clone(), source_value.clone())
997 // && old_value != source_value
998 // {
999 // log::error!(
1000 // "bug: while merging JSON schemas, \
1001 // mismatch `\"{}\": {}` (before was `{}`)",
1002 // source_key,
1003 // old_value,
1004 // source_value
1005 // );
1006 // }
1007 // }
1008 // _ => {
1009 // log::error!(
1010 // "bug: while merging settings JSON schemas, \
1011 // encountered unexpected `\"{}\": {}`",
1012 // source_key,
1013 // source_value
1014 // );
1015 // }
1016 // }
1017 // }
1018 // }
1019
1020 // // add schemas which are determined at runtime
1021 // for parameterized_json_schema in inventory::iter::<ParameterizedJsonSchema>() {
1022 // (parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx);
1023 // }
1024
1025 // // add merged settings schema to the definitions
1026 // const ZED_SETTINGS: &str = "ZedSettings";
1027 // let zed_settings_ref = add_new_subschema(&mut generator, ZED_SETTINGS, combined_schema);
1028
1029 // // add `ZedSettingsOverride` which is the same as `ZedSettings` except that unknown
1030 // // fields are rejected. This is used for release stage settings and profiles.
1031 // let mut zed_settings_override = zed_settings_ref.clone();
1032 // zed_settings_override.insert("unevaluatedProperties".to_string(), false.into());
1033 // let zed_settings_override_ref = add_new_subschema(
1034 // &mut generator,
1035 // "ZedSettingsOverride",
1036 // zed_settings_override.to_value(),
1037 // );
1038
1039 // // Remove `"additionalProperties": false` added by `DefaultDenyUnknownFields` so that
1040 // // unknown fields can be handled by the root schema and `ZedSettingsOverride`.
1041 // let mut definitions = generator.take_definitions(true);
1042 // definitions
1043 // .get_mut(ZED_SETTINGS)
1044 // .unwrap()
1045 // .as_object_mut()
1046 // .unwrap()
1047 // .remove("additionalProperties");
1048
1049 // let meta_schema = generator
1050 // .settings()
1051 // .meta_schema
1052 // .as_ref()
1053 // .expect("meta_schema should be present in schemars settings")
1054 // .to_string();
1055
1056 // json!({
1057 // "$schema": meta_schema,
1058 // "title": "Zed Settings",
1059 // "unevaluatedProperties": false,
1060 // // ZedSettings + settings overrides for each release stage / OS / profiles
1061 // "allOf": [
1062 // zed_settings_ref,
1063 // {
1064 // "properties": {
1065 // "dev": zed_settings_override_ref,
1066 // "nightly": zed_settings_override_ref,
1067 // "stable": zed_settings_override_ref,
1068 // "preview": zed_settings_override_ref,
1069 // "linux": zed_settings_override_ref,
1070 // "macos": zed_settings_override_ref,
1071 // "windows": zed_settings_override_ref,
1072 // "profiles": {
1073 // "type": "object",
1074 // "description": "Configures any number of settings profiles.",
1075 // "additionalProperties": zed_settings_override_ref
1076 // }
1077 // }
1078 // }
1079 // ],
1080 // "$defs": definitions,
1081 // })
1082 }
1083
1084 fn recompute_values(
1085 &mut self,
1086 changed_local_path: Option<(WorktreeId, &Path)>,
1087 cx: &mut App,
1088 ) -> std::result::Result<(), InvalidSettingsError> {
1089 // Reload the global and local values for every setting.
1090 let mut project_settings_stack = Vec::<&SettingsContent>::new();
1091 let mut paths_stack = Vec::<Option<(WorktreeId, &Path)>>::new();
1092
1093 let mut refinements = Vec::default();
1094
1095 if let Some(extension_settings) = self.extension_settings.as_ref() {
1096 refinements.push(extension_settings)
1097 }
1098
1099 if let Some(user_settings) = self.user_settings.as_ref() {
1100 refinements.push(&user_settings.content);
1101 if let Some(release_channel) = user_settings.for_release_channel() {
1102 refinements.push(release_channel)
1103 }
1104 if let Some(os) = user_settings.for_os() {
1105 refinements.push(os)
1106 }
1107 if let Some(profile) = user_settings.for_profile(cx) {
1108 refinements.push(profile)
1109 }
1110 }
1111
1112 if let Some(server_settings) = self.server_settings.as_ref() {
1113 refinements.push(server_settings)
1114 }
1115
1116 for setting_value in self.setting_values.values_mut() {
1117 // If the global settings file changed, reload the global value for the field.
1118 if changed_local_path.is_none() {
1119 let mut value = setting_value.from_file(&self.default_settings).unwrap();
1120 setting_value.refine(value.as_mut(), &refinements);
1121 setting_value.set_global_value(value);
1122 }
1123
1124 // Reload the local values for the setting.
1125 paths_stack.clear();
1126 project_settings_stack.clear();
1127 for ((root_id, directory_path), local_settings) in &self.local_settings {
1128 // Build a stack of all of the local values for that setting.
1129 while let Some(prev_entry) = paths_stack.last() {
1130 if let Some((prev_root_id, prev_path)) = prev_entry
1131 && (root_id != prev_root_id || !directory_path.starts_with(prev_path))
1132 {
1133 paths_stack.pop();
1134 project_settings_stack.pop();
1135 continue;
1136 }
1137 break;
1138 }
1139
1140 // NOTE: this kind of condition existing in the old code too,
1141 // but is there a problem when a setting is removed from a file?
1142 if setting_value.from_file(local_settings).is_some() {
1143 paths_stack.push(Some((*root_id, directory_path.as_ref())));
1144 project_settings_stack.push(local_settings);
1145
1146 // If a local settings file changed, then avoid recomputing local
1147 // settings for any path outside of that directory.
1148 if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| {
1149 *root_id != changed_root_id
1150 || !directory_path.starts_with(changed_local_path)
1151 }) {
1152 continue;
1153 }
1154
1155 let mut value = setting_value.from_file(&self.default_settings).unwrap();
1156 setting_value.refine(value.as_mut(), &refinements);
1157 setting_value.refine(value.as_mut(), &project_settings_stack);
1158 setting_value.set_local_value(*root_id, directory_path.clone(), value);
1159 }
1160 }
1161 }
1162 Ok(())
1163 }
1164
1165 pub fn editorconfig_properties(
1166 &self,
1167 for_worktree: WorktreeId,
1168 for_path: &Path,
1169 ) -> Option<EditorconfigProperties> {
1170 let mut properties = EditorconfigProperties::new();
1171
1172 for (directory_with_config, _, parsed_editorconfig) in
1173 self.local_editorconfig_settings(for_worktree)
1174 {
1175 if !for_path.starts_with(&directory_with_config) {
1176 properties.use_fallbacks();
1177 return Some(properties);
1178 }
1179 let parsed_editorconfig = parsed_editorconfig?;
1180 if parsed_editorconfig.is_root {
1181 properties = EditorconfigProperties::new();
1182 }
1183 for section in parsed_editorconfig.sections {
1184 section.apply_to(&mut properties, for_path).log_err()?;
1185 }
1186 }
1187
1188 properties.use_fallbacks();
1189 Some(properties)
1190 }
1191}
1192
1193#[derive(Debug, Clone, PartialEq)]
1194pub enum InvalidSettingsError {
1195 LocalSettings { path: PathBuf, message: String },
1196 UserSettings { message: String },
1197 ServerSettings { message: String },
1198 DefaultSettings { message: String },
1199 Editorconfig { path: PathBuf, message: String },
1200 Tasks { path: PathBuf, message: String },
1201 Debug { path: PathBuf, message: String },
1202}
1203
1204impl std::fmt::Display for InvalidSettingsError {
1205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1206 match self {
1207 InvalidSettingsError::LocalSettings { message, .. }
1208 | InvalidSettingsError::UserSettings { message }
1209 | InvalidSettingsError::ServerSettings { message }
1210 | InvalidSettingsError::DefaultSettings { message }
1211 | InvalidSettingsError::Tasks { message, .. }
1212 | InvalidSettingsError::Editorconfig { message, .. }
1213 | InvalidSettingsError::Debug { message, .. } => {
1214 write!(f, "{message}")
1215 }
1216 }
1217 }
1218}
1219impl std::error::Error for InvalidSettingsError {}
1220
1221impl Debug for SettingsStore {
1222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1223 f.debug_struct("SettingsStore")
1224 .field(
1225 "types",
1226 &self
1227 .setting_values
1228 .values()
1229 .map(|value| value.setting_type_name())
1230 .collect::<Vec<_>>(),
1231 )
1232 .field("default_settings", &self.default_settings)
1233 .field("user_settings", &self.user_settings)
1234 .field("local_settings", &self.local_settings)
1235 .finish_non_exhaustive()
1236 }
1237}
1238
1239impl<T: Settings> AnySettingValue for SettingValue<T> {
1240 fn from_file(&self, s: &SettingsContent) -> Option<Box<dyn Any>> {
1241 dbg!(type_name::<T>(), TypeId::of::<T>());
1242 T::from_file(s).map(|result| Box::new(result) as _)
1243 }
1244
1245 fn refine(&self, value: &mut dyn Any, refinements: &[&SettingsContent]) {
1246 dbg!(type_name::<T>(), TypeId::of::<T>());
1247 let value = value.downcast_mut::<T>().unwrap();
1248 for refinement in refinements {
1249 value.refine(refinement)
1250 }
1251 }
1252
1253 fn setting_type_name(&self) -> &'static str {
1254 type_name::<T>()
1255 }
1256
1257 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)> {
1258 self.local_values
1259 .iter()
1260 .map(|(id, path, value)| (*id, path.clone(), value as _))
1261 .collect()
1262 }
1263
1264 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
1265 if let Some(SettingsLocation { worktree_id, path }) = path {
1266 for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
1267 if worktree_id == *settings_root_id && path.starts_with(settings_path) {
1268 return value;
1269 }
1270 }
1271 }
1272
1273 self.global_value
1274 .as_ref()
1275 .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
1276 }
1277
1278 fn set_global_value(&mut self, value: Box<dyn Any>) {
1279 self.global_value = Some(*value.downcast().unwrap());
1280 }
1281
1282 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>) {
1283 let value = *value.downcast().unwrap();
1284 match self
1285 .local_values
1286 .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
1287 {
1288 Ok(ix) => self.local_values[ix].2 = value,
1289 Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
1290 }
1291 }
1292
1293 fn json_schema(&self, generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1294 todo!()
1295 // T::FileContent::json_schema(generator)
1296 }
1297
1298 fn edits_for_update(
1299 &self,
1300 raw_settings: &serde_json::Value,
1301 tab_size: usize,
1302 vscode_settings: &VsCodeSettings,
1303 text: &mut String,
1304 edits: &mut Vec<(Range<usize>, String)>,
1305 ) {
1306 todo!()
1307 // let (key, deserialized_setting) = self.deserialize_setting_with_key(raw_settings);
1308 // let old_content = match deserialized_setting {
1309 // Ok(content) => content.0.downcast::<T::FileContent>().unwrap(),
1310 // Err(_) => Box::<<T as Settings>::FileContent>::default(),
1311 // };
1312 // let mut new_content = old_content.clone();
1313 // T::import_from_vscode(vscode_settings, &mut new_content);
1314
1315 // let old_value = serde_json::to_value(&old_content).unwrap();
1316 // let new_value = serde_json::to_value(new_content).unwrap();
1317
1318 // let mut key_path = Vec::new();
1319 // if let Some(key) = key {
1320 // key_path.push(key);
1321 // }
1322
1323 // update_value_in_json_text(
1324 // text,
1325 // &mut key_path,
1326 // tab_size,
1327 // &old_value,
1328 // &new_value,
1329 // T::PRESERVED_KEYS.unwrap_or_default(),
1330 // edits,
1331 // );
1332 }
1333
1334 fn settings_ui_item(&self) -> SettingsUiEntry {
1335 todo!()
1336 // <<T as Settings>::FileContent as SettingsUi>::settings_ui_entry()
1337 }
1338}
1339
1340#[cfg(test)]
1341mod tests {
1342 use crate::{
1343 TitleBarSettingsContent, TitleBarVisibilityContent, VsCodeSettingsSource,
1344 settings_content::LanguageSettingsContent, test_settings,
1345 };
1346
1347 use super::*;
1348 // This is so the SettingsUi macro can still work properly
1349 use crate as settings;
1350 use serde::Deserialize;
1351 use settings_ui_macros::{SettingsKey, SettingsUi};
1352 use unindent::Unindent;
1353 use util::Refine;
1354
1355 #[derive(Debug, PartialEq)]
1356 struct AutoUpdateSetting {
1357 auto_update: bool,
1358 }
1359
1360 impl Settings for AutoUpdateSetting {
1361 fn from_file(content: &SettingsContent) -> Option<Self> {
1362 content
1363 .auto_update
1364 .map(|auto_update| AutoUpdateSetting { auto_update })
1365 }
1366
1367 fn refine(&mut self, content: &SettingsContent) {
1368 if let Some(auto_update) = content.auto_update {
1369 self.auto_update = auto_update;
1370 }
1371 }
1372
1373 fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {}
1374 }
1375
1376 #[derive(Debug, PartialEq)]
1377 struct TitleBarSettings {
1378 show: TitleBarVisibilityContent,
1379 }
1380
1381 impl Settings for TitleBarSettings {
1382 fn from_file(content: &SettingsContent) -> Option<Self> {
1383 let content = content.title_bar?;
1384 Some(TitleBarSettings {
1385 show: content.show?,
1386 })
1387 }
1388
1389 fn refine(&mut self, content: &SettingsContent) {
1390 let Some(content) = content.title_bar else {
1391 return;
1392 };
1393 self.show.refine(&content.show)
1394 }
1395
1396 fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {}
1397 }
1398
1399 #[gpui::test]
1400 fn test_settings_store_basic(cx: &mut App) {
1401 let mut store = SettingsStore::new(
1402 cx,
1403 r#"{
1404 "auto_update": false,
1405 "user": {
1406 "name": "John Doe",
1407 "age": 30,
1408 "staff": false
1409 }
1410 }"#,
1411 );
1412 store.register_setting::<AutoUpdateSetting>(cx);
1413 store.register_setting::<TitleBarSettings>(cx);
1414 // store.register_setting::<MultiKeySettings>(cx);
1415
1416 assert_eq!(
1417 store.get::<AutoUpdateSetting>(None),
1418 &AutoUpdateSetting { auto_update: false }
1419 );
1420 // assert_eq!(
1421 // store.get::<UserSettings>(None),
1422 // &UserSettings {
1423 // name: "John Doe".to_string(),
1424 // age: 30,
1425 // staff: false,
1426 // }
1427 // );
1428 // assert_eq!(
1429 // store.get::<MultiKeySettings>(None),
1430 // &MultiKeySettings {
1431 // key1: String::new(),
1432 // key2: String::new(),
1433 // }
1434 // );
1435
1436 store
1437 .set_user_settings(
1438 r#"{
1439 "auto_update": true,
1440 "user": { "age": 31 },
1441 "key1": "a"
1442 }"#,
1443 cx,
1444 )
1445 .unwrap();
1446
1447 assert_eq!(
1448 store.get::<AutoUpdateSetting>(None),
1449 &AutoUpdateSetting { auto_update: true }
1450 );
1451 // assert_eq!(
1452 // store.get::<UserSettings>(None),
1453 // &UserSettings {
1454 // name: "John Doe".to_string(),
1455 // age: 31,
1456 // staff: false
1457 // }
1458 // );
1459
1460 store
1461 .set_local_settings(
1462 WorktreeId::from_usize(1),
1463 Path::new("/root1").into(),
1464 LocalSettingsKind::Settings,
1465 Some(r#"{ "user": { "staff": true } }"#),
1466 cx,
1467 )
1468 .unwrap();
1469 store
1470 .set_local_settings(
1471 WorktreeId::from_usize(1),
1472 Path::new("/root1/subdir").into(),
1473 LocalSettingsKind::Settings,
1474 Some(r#"{ "user": { "name": "Jane Doe" } }"#),
1475 cx,
1476 )
1477 .unwrap();
1478
1479 store
1480 .set_local_settings(
1481 WorktreeId::from_usize(1),
1482 Path::new("/root2").into(),
1483 LocalSettingsKind::Settings,
1484 Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
1485 cx,
1486 )
1487 .unwrap();
1488
1489 // assert_eq!(
1490 // store.get::<UserSettings>(Some(SettingsLocation {
1491 // worktree_id: WorktreeId::from_usize(1),
1492 // path: Path::new("/root1/something"),
1493 // })),
1494 // &UserSettings {
1495 // name: "John Doe".to_string(),
1496 // age: 31,
1497 // staff: true
1498 // }
1499 // );
1500 // assert_eq!(
1501 // store.get::<UserSettings>(Some(SettingsLocation {
1502 // worktree_id: WorktreeId::from_usize(1),
1503 // path: Path::new("/root1/subdir/something")
1504 // })),
1505 // &UserSettings {
1506 // name: "Jane Doe".to_string(),
1507 // age: 31,
1508 // staff: true
1509 // }
1510 // );
1511 // assert_eq!(
1512 // store.get::<UserSettings>(Some(SettingsLocation {
1513 // worktree_id: WorktreeId::from_usize(1),
1514 // path: Path::new("/root2/something")
1515 // })),
1516 // &UserSettings {
1517 // name: "John Doe".to_string(),
1518 // age: 42,
1519 // staff: false
1520 // }
1521 // );
1522 // assert_eq!(
1523 // store.get::<MultiKeySettings>(Some(SettingsLocation {
1524 // worktree_id: WorktreeId::from_usize(1),
1525 // path: Path::new("/root2/something")
1526 // })),
1527 // &MultiKeySettings {
1528 // key1: "a".to_string(),
1529 // key2: "b".to_string(),
1530 // }
1531 // );
1532 }
1533
1534 // #[gpui::test]
1535 // fn test_setting_store_assign_json_before_register(cx: &mut App) {
1536 // let mut store = SettingsStore::new(cx);
1537 // store
1538 // .set_default_settings(
1539 // r#"{
1540 // "turbo": true,
1541 // "user": {
1542 // "name": "John Doe",
1543 // "age": 30,
1544 // "staff": false
1545 // },
1546 // "key1": "x"
1547 // }"#,
1548 // cx,
1549 // )
1550 // .unwrap();
1551 // store
1552 // .set_user_settings(r#"{ "turbo": false }"#, cx)
1553 // .unwrap();
1554 // store.register_setting::<UserSettings>(cx);
1555 // store.register_setting::<TurboSetting>(cx);
1556
1557 // assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
1558 // assert_eq!(
1559 // store.get::<UserSettings>(None),
1560 // &UserSettings {
1561 // name: "John Doe".to_string(),
1562 // age: 30,
1563 // staff: false,
1564 // }
1565 // );
1566
1567 // store.register_setting::<MultiKeySettings>(cx);
1568 // assert_eq!(
1569 // store.get::<MultiKeySettings>(None),
1570 // &MultiKeySettings {
1571 // key1: "x".into(),
1572 // key2: String::new(),
1573 // }
1574 // );
1575 // }
1576
1577 fn check_settings_update(
1578 store: &mut SettingsStore,
1579 old_json: String,
1580 update: fn(&mut SettingsContent),
1581 expected_new_json: String,
1582 cx: &mut App,
1583 ) {
1584 store.set_user_settings(&old_json, cx).ok();
1585 let edits = store.edits_for_update(&old_json, update);
1586 let mut new_json = old_json;
1587 for (range, replacement) in edits.into_iter() {
1588 new_json.replace_range(range, &replacement);
1589 }
1590 pretty_assertions::assert_eq!(new_json, expected_new_json);
1591 }
1592
1593 #[gpui::test]
1594 fn test_setting_store_update(cx: &mut App) {
1595 let mut store = SettingsStore::new(cx, &test_settings());
1596 // store.register_setting::<MultiKeySettings>(cx);
1597 // store.register_setting::<UserSettings>(cx);
1598 // store.register_setting::<LanguageSettings>(cx);
1599
1600 // entries added and updated
1601 check_settings_update(
1602 &mut store,
1603 r#"{
1604 "languages": {
1605 "JSON": {
1606 "auto_indent": true
1607 }
1608 }
1609 }"#
1610 .unindent(),
1611 |settings| {
1612 settings
1613 .languages_mut()
1614 .get_mut("JSON")
1615 .unwrap()
1616 .auto_indent = Some(false);
1617
1618 settings.languages_mut().insert(
1619 "Rust".into(),
1620 LanguageSettingsContent {
1621 auto_indent: Some(true),
1622 ..Default::default()
1623 },
1624 );
1625 },
1626 r#"{
1627 "languages": {
1628 "Rust": {
1629 "auto_indent": true
1630 },
1631 "JSON": {
1632 "auto_indent": false
1633 }
1634 }
1635 }"#
1636 .unindent(),
1637 cx,
1638 );
1639
1640 // entries removed
1641 check_settings_update(
1642 &mut store,
1643 r#"{
1644 "languages": {
1645 "Rust": {
1646 "language_setting_2": true
1647 },
1648 "JSON": {
1649 "language_setting_1": false
1650 }
1651 }
1652 }"#
1653 .unindent(),
1654 |settings| {
1655 settings.languages_mut().remove("JSON").unwrap();
1656 },
1657 r#"{
1658 "languages": {
1659 "Rust": {
1660 "language_setting_2": true
1661 }
1662 }
1663 }"#
1664 .unindent(),
1665 cx,
1666 );
1667
1668 check_settings_update(
1669 &mut store,
1670 r#"{
1671 "languages": {
1672 "Rust": {
1673 "language_setting_2": true
1674 },
1675 "JSON": {
1676 "language_setting_1": false
1677 }
1678 }
1679 }"#
1680 .unindent(),
1681 |settings| {
1682 settings.languages_mut().remove("Rust").unwrap();
1683 },
1684 r#"{
1685 "languages": {
1686 "JSON": {
1687 "language_setting_1": false
1688 }
1689 }
1690 }"#
1691 .unindent(),
1692 cx,
1693 );
1694
1695 // // weird formatting
1696 // check_settings_update(
1697 // &mut store,
1698 // r#"{
1699 // "user": { "age": 36, "name": "Max", "staff": true }
1700 // }"#
1701 // .unindent(),
1702 // |settings| settings.age = Some(37),
1703 // r#"{
1704 // "user": { "age": 37, "name": "Max", "staff": true }
1705 // }"#
1706 // .unindent(),
1707 // cx,
1708 // );
1709
1710 // // single-line formatting, other keys
1711 // check_settings_update::<MultiKeySettings>(
1712 // &mut store,
1713 // r#"{ "one": 1, "two": 2 }"#.unindent(),
1714 // |settings| settings.key1 = Some("x".into()),
1715 // r#"{ "key1": "x", "one": 1, "two": 2 }"#.unindent(),
1716 // cx,
1717 // );
1718
1719 // // empty object
1720 // check_settings_update::<UserSettings>(
1721 // &mut store,
1722 // r#"{
1723 // "user": {}
1724 // }"#
1725 // .unindent(),
1726 // |settings| settings.age = Some(37),
1727 // r#"{
1728 // "user": {
1729 // "age": 37
1730 // }
1731 // }"#
1732 // .unindent(),
1733 // cx,
1734 // );
1735
1736 // // no content
1737 // check_settings_update::<UserSettings>(
1738 // &mut store,
1739 // r#""#.unindent(),
1740 // |settings| settings.age = Some(37),
1741 // r#"{
1742 // "user": {
1743 // "age": 37
1744 // }
1745 // }
1746 // "#
1747 // .unindent(),
1748 // cx,
1749 // );
1750
1751 // check_settings_update::<UserSettings>(
1752 // &mut store,
1753 // r#"{
1754 // }
1755 // "#
1756 // .unindent(),
1757 // |settings| settings.age = Some(37),
1758 // r#"{
1759 // "user": {
1760 // "age": 37
1761 // }
1762 // }
1763 // "#
1764 // .unindent(),
1765 // cx,
1766 // );
1767 }
1768
1769 // #[gpui::test]
1770 // fn test_vscode_import(cx: &mut App) {
1771 // let mut store = SettingsStore::new(cx);
1772 // store.register_setting::<UserSettings>(cx);
1773 // store.register_setting::<JournalSettings>(cx);
1774 // store.register_setting::<LanguageSettings>(cx);
1775 // store.register_setting::<MultiKeySettings>(cx);
1776
1777 // // create settings that werent present
1778 // check_vscode_import(
1779 // &mut store,
1780 // r#"{
1781 // }
1782 // "#
1783 // .unindent(),
1784 // r#" { "user.age": 37 } "#.to_owned(),
1785 // r#"{
1786 // "user": {
1787 // "age": 37
1788 // }
1789 // }
1790 // "#
1791 // .unindent(),
1792 // cx,
1793 // );
1794
1795 // // persist settings that were present
1796 // check_vscode_import(
1797 // &mut store,
1798 // r#"{
1799 // "user": {
1800 // "staff": true,
1801 // "age": 37
1802 // }
1803 // }
1804 // "#
1805 // .unindent(),
1806 // r#"{ "user.age": 42 }"#.to_owned(),
1807 // r#"{
1808 // "user": {
1809 // "staff": true,
1810 // "age": 42
1811 // }
1812 // }
1813 // "#
1814 // .unindent(),
1815 // cx,
1816 // );
1817
1818 // // don't clobber settings that aren't present in vscode
1819 // check_vscode_import(
1820 // &mut store,
1821 // r#"{
1822 // "user": {
1823 // "staff": true,
1824 // "age": 37
1825 // }
1826 // }
1827 // "#
1828 // .unindent(),
1829 // r#"{}"#.to_owned(),
1830 // r#"{
1831 // "user": {
1832 // "staff": true,
1833 // "age": 37
1834 // }
1835 // }
1836 // "#
1837 // .unindent(),
1838 // cx,
1839 // );
1840
1841 // // custom enum
1842 // check_vscode_import(
1843 // &mut store,
1844 // r#"{
1845 // "journal": {
1846 // "hour_format": "hour12"
1847 // }
1848 // }
1849 // "#
1850 // .unindent(),
1851 // r#"{ "time_format": "24" }"#.to_owned(),
1852 // r#"{
1853 // "journal": {
1854 // "hour_format": "hour24"
1855 // }
1856 // }
1857 // "#
1858 // .unindent(),
1859 // cx,
1860 // );
1861
1862 // // Multiple keys for one setting
1863 // check_vscode_import(
1864 // &mut store,
1865 // r#"{
1866 // "key1": "value"
1867 // }
1868 // "#
1869 // .unindent(),
1870 // r#"{
1871 // "key_1_first": "hello",
1872 // "key_1_second": "world"
1873 // }"#
1874 // .to_owned(),
1875 // r#"{
1876 // "key1": "hello world"
1877 // }
1878 // "#
1879 // .unindent(),
1880 // cx,
1881 // );
1882
1883 // // Merging lists together entries added and updated
1884 // check_vscode_import(
1885 // &mut store,
1886 // r#"{
1887 // "languages": {
1888 // "JSON": {
1889 // "language_setting_1": true
1890 // },
1891 // "Rust": {
1892 // "language_setting_2": true
1893 // }
1894 // }
1895 // }"#
1896 // .unindent(),
1897 // r#"{
1898 // "vscode_languages": [
1899 // {
1900 // "name": "JavaScript",
1901 // "language_setting_1": true
1902 // },
1903 // {
1904 // "name": "Rust",
1905 // "language_setting_2": false
1906 // }
1907 // ]
1908 // }"#
1909 // .to_owned(),
1910 // r#"{
1911 // "languages": {
1912 // "JavaScript": {
1913 // "language_setting_1": true
1914 // },
1915 // "JSON": {
1916 // "language_setting_1": true
1917 // },
1918 // "Rust": {
1919 // "language_setting_2": false
1920 // }
1921 // }
1922 // }"#
1923 // .unindent(),
1924 // cx,
1925 // );
1926 // }
1927
1928 // fn check_vscode_import(
1929 // store: &mut SettingsStore,
1930 // old: String,
1931 // vscode: String,
1932 // expected: String,
1933 // cx: &mut App,
1934 // ) {
1935 // store.set_user_settings(&old, cx).ok();
1936 // let new = store.get_vscode_edits(
1937 // old,
1938 // &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
1939 // );
1940 // pretty_assertions::assert_eq!(new, expected);
1941 // }
1942
1943 // #[derive(Debug, PartialEq, Deserialize, SettingsUi)]
1944 // struct UserSettings {
1945 // name: String,
1946 // age: u32,
1947 // staff: bool,
1948 // }
1949
1950 // #[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
1951 // #[settings_key(key = "user")]
1952 // struct UserSettingsContent {
1953 // name: Option<String>,
1954 // age: Option<u32>,
1955 // staff: Option<bool>,
1956 // }
1957
1958 // impl Settings for UserSettings {
1959 // type FileContent = UserSettingsContent;
1960
1961 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1962 // sources.json_merge()
1963 // }
1964
1965 // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
1966 // vscode.u32_setting("user.age", &mut current.age);
1967 // }
1968 // }
1969
1970 // #[derive(Debug, Deserialize, PartialEq)]
1971 // struct TurboSetting(bool);
1972
1973 // #[derive(
1974 // Copy,
1975 // Clone,
1976 // PartialEq,
1977 // Eq,
1978 // Debug,
1979 // Default,
1980 // serde::Serialize,
1981 // serde::Deserialize,
1982 // SettingsUi,
1983 // SettingsKey,
1984 // JsonSchema,
1985 // )]
1986 // #[serde(default)]
1987 // #[settings_key(None)]
1988 // pub struct TurboSettingContent {
1989 // turbo: Option<bool>,
1990 // }
1991
1992 // impl Settings for TurboSetting {
1993 // type FileContent = TurboSettingContent;
1994
1995 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1996 // Ok(Self(
1997 // sources
1998 // .user
1999 // .or(sources.server)
2000 // .unwrap_or(sources.default)
2001 // .turbo
2002 // .unwrap_or_default(),
2003 // ))
2004 // }
2005
2006 // fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut Self::FileContent) {}
2007 // }
2008
2009 // #[derive(Clone, Debug, PartialEq, Deserialize)]
2010 // struct MultiKeySettings {
2011 // #[serde(default)]
2012 // key1: String,
2013 // #[serde(default)]
2014 // key2: String,
2015 // }
2016
2017 // #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
2018 // #[settings_key(None)]
2019 // struct MultiKeySettingsJson {
2020 // key1: Option<String>,
2021 // key2: Option<String>,
2022 // }
2023
2024 // impl Settings for MultiKeySettings {
2025 // type FileContent = MultiKeySettingsJson;
2026
2027 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
2028 // sources.json_merge()
2029 // }
2030
2031 // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
2032 // let first_value = vscode.read_string("key_1_first");
2033 // let second_value = vscode.read_string("key_1_second");
2034
2035 // if let Some((first, second)) = first_value.zip(second_value) {
2036 // current.key1 = Some(format!("{} {}", first, second));
2037 // }
2038 // }
2039 // }
2040
2041 // #[derive(Debug, Deserialize)]
2042 // struct JournalSettings {
2043 // #[expect(unused)]
2044 // pub path: String,
2045 // #[expect(unused)]
2046 // pub hour_format: HourFormat,
2047 // }
2048
2049 // #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
2050 // #[serde(rename_all = "snake_case")]
2051 // enum HourFormat {
2052 // Hour12,
2053 // Hour24,
2054 // }
2055
2056 // #[derive(
2057 // Clone, Default, Debug, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey,
2058 // )]
2059 // #[settings_key(key = "journal")]
2060 // struct JournalSettingsJson {
2061 // pub path: Option<String>,
2062 // pub hour_format: Option<HourFormat>,
2063 // }
2064
2065 // impl Settings for JournalSettings {
2066 // type FileContent = JournalSettingsJson;
2067
2068 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
2069 // sources.json_merge()
2070 // }
2071
2072 // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
2073 // vscode.enum_setting("time_format", &mut current.hour_format, |s| match s {
2074 // "12" => Some(HourFormat::Hour12),
2075 // "24" => Some(HourFormat::Hour24),
2076 // _ => None,
2077 // });
2078 // }
2079 // }
2080
2081 // #[gpui::test]
2082 // fn test_global_settings(cx: &mut App) {
2083 // let mut store = SettingsStore::new(cx);
2084 // store.register_setting::<UserSettings>(cx);
2085 // store
2086 // .set_default_settings(
2087 // r#"{
2088 // "user": {
2089 // "name": "John Doe",
2090 // "age": 30,
2091 // "staff": false
2092 // }
2093 // }"#,
2094 // cx,
2095 // )
2096 // .unwrap();
2097
2098 // // Set global settings - these should override defaults but not user settings
2099 // store
2100 // .set_global_settings(
2101 // r#"{
2102 // "user": {
2103 // "name": "Global User",
2104 // "age": 35,
2105 // "staff": true
2106 // }
2107 // }"#,
2108 // cx,
2109 // )
2110 // .unwrap();
2111
2112 // // Before user settings, global settings should apply
2113 // assert_eq!(
2114 // store.get::<UserSettings>(None),
2115 // &UserSettings {
2116 // name: "Global User".to_string(),
2117 // age: 35,
2118 // staff: true,
2119 // }
2120 // );
2121
2122 // // Set user settings - these should override both defaults and global
2123 // store
2124 // .set_user_settings(
2125 // r#"{
2126 // "user": {
2127 // "age": 40
2128 // }
2129 // }"#,
2130 // cx,
2131 // )
2132 // .unwrap();
2133
2134 // // User settings should override global settings
2135 // assert_eq!(
2136 // store.get::<UserSettings>(None),
2137 // &UserSettings {
2138 // name: "Global User".to_string(), // Name from global settings
2139 // age: 40, // Age from user settings
2140 // staff: true, // Staff from global settings
2141 // }
2142 // );
2143 // }
2144
2145 // #[derive(
2146 // Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey,
2147 // )]
2148 // #[settings_key(None)]
2149 // struct LanguageSettings {
2150 // #[serde(default)]
2151 // languages: HashMap<String, LanguageSettingEntry>,
2152 // }
2153
2154 // #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
2155 // struct LanguageSettingEntry {
2156 // language_setting_1: Option<bool>,
2157 // language_setting_2: Option<bool>,
2158 // }
2159
2160 // impl Settings for LanguageSettings {
2161 // type FileContent = Self;
2162
2163 // fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
2164 // sources.json_merge()
2165 // }
2166
2167 // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
2168 // current.languages.extend(
2169 // vscode
2170 // .read_value("vscode_languages")
2171 // .and_then(|value| value.as_array())
2172 // .map(|languages| {
2173 // languages
2174 // .iter()
2175 // .filter_map(|value| value.as_object())
2176 // .filter_map(|item| {
2177 // let mut rest = item.clone();
2178 // let name = rest.remove("name")?.as_str()?.to_string();
2179 // let entry = serde_json::from_value::<LanguageSettingEntry>(
2180 // serde_json::Value::Object(rest),
2181 // )
2182 // .ok()?;
2183
2184 // Some((name, entry))
2185 // })
2186 // })
2187 // .into_iter()
2188 // .flatten(),
2189 // );
2190 // }
2191 // }
2192}