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