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