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