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