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