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 /// Get the configured settings profile names.
366 pub fn configured_settings_profiles(&self) -> impl Iterator<Item = &str> {
367 self.user_settings
368 .iter()
369 .flat_map(|settings| settings.profiles.keys().map(|k| k.as_str()))
370 }
371
372 /// Access the raw JSON value of the default settings.
373 pub fn raw_default_settings(&self) -> &SettingsContent {
374 &self.default_settings
375 }
376
377 #[cfg(any(test, feature = "test-support"))]
378 pub fn test(cx: &mut App) -> Self {
379 Self::new(cx, &crate::test_settings())
380 }
381
382 /// Updates the value of a setting in the user's global configuration.
383 ///
384 /// This is only for tests. Normally, settings are only loaded from
385 /// JSON files.
386 #[cfg(any(test, feature = "test-support"))]
387 pub fn update_user_settings(
388 &mut self,
389 cx: &mut App,
390 update: impl FnOnce(&mut SettingsContent),
391 ) {
392 let mut content = self.user_settings.clone().unwrap_or_default().content;
393 update(&mut content);
394 let new_text = serde_json::to_string(&UserSettingsContent {
395 content,
396 ..Default::default()
397 })
398 .unwrap();
399 self.set_user_settings(&new_text, cx).unwrap();
400 }
401
402 pub async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
403 match fs.load(paths::settings_file()).await {
404 result @ Ok(_) => result,
405 Err(err) => {
406 if let Some(e) = err.downcast_ref::<std::io::Error>()
407 && e.kind() == std::io::ErrorKind::NotFound
408 {
409 return Ok(crate::initial_user_settings_content().to_string());
410 }
411 Err(err)
412 }
413 }
414 }
415
416 fn update_settings_file_inner(
417 &self,
418 fs: Arc<dyn Fs>,
419 update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>,
420 ) -> oneshot::Receiver<Result<()>> {
421 let (tx, rx) = oneshot::channel::<Result<()>>();
422 self.setting_file_updates_tx
423 .unbounded_send(Box::new(move |cx: AsyncApp| {
424 async move {
425 let res = async move {
426 let old_text = Self::load_settings(&fs).await?;
427 let new_text = update(old_text, cx)?;
428 let settings_path = paths::settings_file().as_path();
429 if fs.is_file(settings_path).await {
430 let resolved_path =
431 fs.canonicalize(settings_path).await.with_context(|| {
432 format!(
433 "Failed to canonicalize settings path {:?}",
434 settings_path
435 )
436 })?;
437
438 fs.atomic_write(resolved_path.clone(), new_text)
439 .await
440 .with_context(|| {
441 format!("Failed to write settings to file {:?}", resolved_path)
442 })?;
443 } else {
444 fs.atomic_write(settings_path.to_path_buf(), new_text)
445 .await
446 .with_context(|| {
447 format!("Failed to write settings to file {:?}", settings_path)
448 })?;
449 }
450 anyhow::Ok(())
451 }
452 .await;
453
454 let new_res = match &res {
455 Ok(_) => anyhow::Ok(()),
456 Err(e) => Err(anyhow::anyhow!("Failed to write settings to file {:?}", e)),
457 };
458
459 _ = tx.send(new_res);
460 res
461 }
462 .boxed_local()
463 }))
464 .map_err(|err| anyhow::format_err!("Failed to update settings file: {}", err))
465 .log_with_level(log::Level::Warn);
466 return rx;
467 }
468
469 pub fn update_settings_file_at_path(
470 &self,
471 fs: Arc<dyn Fs>,
472 path: &[impl AsRef<str>],
473 new_value: serde_json::Value,
474 ) -> oneshot::Receiver<Result<()>> {
475 let key_path = path
476 .into_iter()
477 .map(AsRef::as_ref)
478 .map(SharedString::new)
479 .collect::<Vec<_>>();
480 let update = move |mut old_text: String, cx: AsyncApp| {
481 cx.read_global(|store: &SettingsStore, _cx| {
482 // todo(settings_ui) use `update_value_in_json_text` for merging new and old objects with comment preservation, needs old value though...
483 let (range, replacement) = replace_value_in_json_text(
484 &old_text,
485 key_path.as_slice(),
486 store.json_tab_size(),
487 Some(&new_value),
488 None,
489 );
490 old_text.replace_range(range, &replacement);
491 old_text
492 })
493 };
494 self.update_settings_file_inner(fs, update)
495 }
496
497 pub fn update_settings_file(
498 &self,
499 fs: Arc<dyn Fs>,
500 update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
501 ) {
502 _ = self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
503 cx.read_global(|store: &SettingsStore, cx| {
504 store.new_text_for_update(old_text, |content| update(content, cx))
505 })
506 });
507 }
508
509 pub fn import_vscode_settings(
510 &self,
511 fs: Arc<dyn Fs>,
512 vscode_settings: VsCodeSettings,
513 ) -> oneshot::Receiver<Result<()>> {
514 self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
515 cx.read_global(|store: &SettingsStore, _cx| {
516 store.get_vscode_edits(old_text, &vscode_settings)
517 })
518 })
519 }
520
521 pub fn settings_ui_items(&self) -> impl IntoIterator<Item = SettingsUiEntry> {
522 [].into_iter()
523 }
524}
525
526impl SettingsStore {
527 /// Updates the value of a setting in a JSON file, returning the new text
528 /// for that JSON file.
529 pub fn new_text_for_update(
530 &self,
531 old_text: String,
532 update: impl FnOnce(&mut SettingsContent),
533 ) -> String {
534 let edits = self.edits_for_update(&old_text, update);
535 let mut new_text = old_text;
536 for (range, replacement) in edits.into_iter() {
537 new_text.replace_range(range, &replacement);
538 }
539 new_text
540 }
541
542 pub fn get_vscode_edits(&self, old_text: String, vscode: &VsCodeSettings) -> String {
543 self.new_text_for_update(old_text, |settings_content| {
544 for v in self.setting_values.values() {
545 v.import_from_vscode(vscode, settings_content)
546 }
547 })
548 }
549
550 /// Updates the value of a setting in a JSON file, returning a list
551 /// of edits to apply to the JSON file.
552 pub fn edits_for_update(
553 &self,
554 text: &str,
555 update: impl FnOnce(&mut SettingsContent),
556 ) -> Vec<(Range<usize>, String)> {
557 let old_content: UserSettingsContent =
558 parse_json_with_comments(text).log_err().unwrap_or_default();
559 let mut new_content = old_content.clone();
560 update(&mut new_content.content);
561
562 let old_value = serde_json::to_value(&old_content).unwrap();
563 let new_value = serde_json::to_value(new_content).unwrap();
564
565 let mut key_path = Vec::new();
566 let mut edits = Vec::new();
567 let tab_size = self.json_tab_size();
568 let mut text = text.to_string();
569 update_value_in_json_text(
570 &mut text,
571 &mut key_path,
572 tab_size,
573 &old_value,
574 &new_value,
575 &[], // todo!() is this still needed?
576 &mut edits,
577 );
578 edits
579 }
580
581 pub fn json_tab_size(&self) -> usize {
582 2
583 }
584
585 /// Sets the default settings via a JSON string.
586 ///
587 /// The string should contain a JSON object with a default value for every setting.
588 pub fn set_default_settings(
589 &mut self,
590 default_settings_content: &str,
591 cx: &mut App,
592 ) -> Result<()> {
593 self.default_settings = parse_json_with_comments(default_settings_content)?;
594 self.recompute_values(None, cx)?;
595 Ok(())
596 }
597
598 /// Sets the user settings via a JSON string.
599 pub fn set_user_settings(&mut self, user_settings_content: &str, cx: &mut App) -> Result<()> {
600 let settings: UserSettingsContent = if user_settings_content.is_empty() {
601 parse_json_with_comments("{}")?
602 } else {
603 parse_json_with_comments(user_settings_content)?
604 };
605
606 self.user_settings = Some(settings);
607 self.recompute_values(None, cx)?;
608 Ok(())
609 }
610
611 /// Sets the global settings via a JSON string.
612 pub fn set_global_settings(
613 &mut self,
614 global_settings_content: &str,
615 cx: &mut App,
616 ) -> Result<()> {
617 let settings: SettingsContent = if global_settings_content.is_empty() {
618 parse_json_with_comments("{}")?
619 } else {
620 parse_json_with_comments(global_settings_content)?
621 };
622
623 self.global_settings = Some(Box::new(settings));
624 self.recompute_values(None, cx)?;
625 Ok(())
626 }
627
628 pub fn set_server_settings(
629 &mut self,
630 server_settings_content: &str,
631 cx: &mut App,
632 ) -> Result<()> {
633 let settings: Option<ServerSettingsContent> = if server_settings_content.is_empty() {
634 None
635 } else {
636 parse_json_with_comments(server_settings_content)?
637 };
638
639 // Rewrite the server settings into a content type
640 self.server_settings = settings.map(|settings| {
641 Box::new(SettingsContent {
642 project: settings.project,
643 ..Default::default()
644 })
645 });
646
647 self.recompute_values(None, cx)?;
648 Ok(())
649 }
650
651 /// Add or remove a set of local settings via a JSON string.
652 pub fn set_local_settings(
653 &mut self,
654 root_id: WorktreeId,
655 directory_path: Arc<Path>,
656 kind: LocalSettingsKind,
657 settings_content: Option<&str>,
658 cx: &mut App,
659 ) -> std::result::Result<(), InvalidSettingsError> {
660 let mut zed_settings_changed = false;
661 match (
662 kind,
663 settings_content
664 .map(|content| content.trim())
665 .filter(|content| !content.is_empty()),
666 ) {
667 (LocalSettingsKind::Tasks, _) => {
668 return Err(InvalidSettingsError::Tasks {
669 message: "Attempted to submit tasks into the settings store".to_string(),
670 path: directory_path.join(task_file_name()),
671 });
672 }
673 (LocalSettingsKind::Debug, _) => {
674 return Err(InvalidSettingsError::Debug {
675 message: "Attempted to submit debugger config into the settings store"
676 .to_string(),
677 path: directory_path.join(task_file_name()),
678 });
679 }
680 (LocalSettingsKind::Settings, None) => {
681 zed_settings_changed = self
682 .local_settings
683 .remove(&(root_id, directory_path.clone()))
684 .is_some()
685 }
686 (LocalSettingsKind::Editorconfig, None) => {
687 self.raw_editorconfig_settings
688 .remove(&(root_id, directory_path.clone()));
689 }
690 (LocalSettingsKind::Settings, Some(settings_contents)) => {
691 let new_settings = parse_json_with_comments::<ProjectSettingsContent>(
692 settings_contents,
693 )
694 .map_err(|e| InvalidSettingsError::LocalSettings {
695 path: directory_path.join(local_settings_file_relative_path()),
696 message: e.to_string(),
697 })?;
698 match self.local_settings.entry((root_id, directory_path.clone())) {
699 btree_map::Entry::Vacant(v) => {
700 v.insert(SettingsContent {
701 project: new_settings,
702 ..Default::default()
703 });
704 zed_settings_changed = true;
705 }
706 btree_map::Entry::Occupied(mut o) => {
707 if &o.get().project != &new_settings {
708 o.insert(SettingsContent {
709 project: new_settings,
710 ..Default::default()
711 });
712 zed_settings_changed = true;
713 }
714 }
715 }
716 }
717 (LocalSettingsKind::Editorconfig, Some(editorconfig_contents)) => {
718 match self
719 .raw_editorconfig_settings
720 .entry((root_id, directory_path.clone()))
721 {
722 btree_map::Entry::Vacant(v) => match editorconfig_contents.parse() {
723 Ok(new_contents) => {
724 v.insert((editorconfig_contents.to_owned(), Some(new_contents)));
725 }
726 Err(e) => {
727 v.insert((editorconfig_contents.to_owned(), None));
728 return Err(InvalidSettingsError::Editorconfig {
729 message: e.to_string(),
730 path: directory_path.join(EDITORCONFIG_NAME),
731 });
732 }
733 },
734 btree_map::Entry::Occupied(mut o) => {
735 if o.get().0 != editorconfig_contents {
736 match editorconfig_contents.parse() {
737 Ok(new_contents) => {
738 o.insert((
739 editorconfig_contents.to_owned(),
740 Some(new_contents),
741 ));
742 }
743 Err(e) => {
744 o.insert((editorconfig_contents.to_owned(), None));
745 return Err(InvalidSettingsError::Editorconfig {
746 message: e.to_string(),
747 path: directory_path.join(EDITORCONFIG_NAME),
748 });
749 }
750 }
751 }
752 }
753 }
754 }
755 };
756
757 if zed_settings_changed {
758 self.recompute_values(Some((root_id, &directory_path)), cx)?;
759 }
760 Ok(())
761 }
762
763 pub fn set_extension_settings(
764 &mut self,
765 content: ExtensionsSettingsContent,
766 cx: &mut App,
767 ) -> Result<()> {
768 self.extension_settings = Some(Box::new(SettingsContent {
769 project: ProjectSettingsContent {
770 all_languages: content.all_languages,
771 ..Default::default()
772 },
773 ..Default::default()
774 }));
775 self.recompute_values(None, cx)?;
776 Ok(())
777 }
778
779 /// Add or remove a set of local settings via a JSON string.
780 pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut App) -> Result<()> {
781 self.local_settings
782 .retain(|(worktree_id, _), _| worktree_id != &root_id);
783 self.recompute_values(Some((root_id, "".as_ref())), cx)?;
784 Ok(())
785 }
786
787 pub fn local_settings(
788 &self,
789 root_id: WorktreeId,
790 ) -> impl '_ + Iterator<Item = (Arc<Path>, String)> {
791 self.local_settings
792 .range(
793 (root_id, Path::new("").into())
794 ..(
795 WorktreeId::from_usize(root_id.to_usize() + 1),
796 Path::new("").into(),
797 ),
798 )
799 .map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap()))
800 }
801
802 pub fn local_editorconfig_settings(
803 &self,
804 root_id: WorktreeId,
805 ) -> impl '_ + Iterator<Item = (Arc<Path>, String, Option<Editorconfig>)> {
806 self.raw_editorconfig_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, parsed_content))| {
815 (path.clone(), content.clone(), parsed_content.clone())
816 })
817 }
818
819 pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value {
820 let mut generator = schemars::generate::SchemaSettings::draft2019_09()
821 .with_transform(DefaultDenyUnknownFields)
822 .into_generator();
823
824 let schema = UserSettingsContent::json_schema(&mut generator);
825
826 // add schemas which are determined at runtime
827 for parameterized_json_schema in inventory::iter::<ParameterizedJsonSchema>() {
828 (parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx);
829 }
830
831 schema.to_value()
832 }
833
834 fn recompute_values(
835 &mut self,
836 changed_local_path: Option<(WorktreeId, &Path)>,
837 cx: &mut App,
838 ) -> std::result::Result<(), InvalidSettingsError> {
839 // Reload the global and local values for every setting.
840 let mut project_settings_stack = Vec::<&SettingsContent>::new();
841 let mut paths_stack = Vec::<Option<(WorktreeId, &Path)>>::new();
842
843 let mut refinements = Vec::default();
844
845 if let Some(extension_settings) = self.extension_settings.as_deref() {
846 refinements.push(extension_settings)
847 }
848
849 if let Some(global_settings) = self.global_settings.as_deref() {
850 refinements.push(global_settings)
851 }
852
853 if let Some(user_settings) = self.user_settings.as_ref() {
854 refinements.push(&user_settings.content);
855 if let Some(release_channel) = user_settings.for_release_channel() {
856 refinements.push(release_channel)
857 }
858 if let Some(os) = user_settings.for_os() {
859 refinements.push(os)
860 }
861 if let Some(profile) = user_settings.for_profile(cx) {
862 refinements.push(profile)
863 }
864 }
865
866 if let Some(server_settings) = self.server_settings.as_ref() {
867 refinements.push(server_settings)
868 }
869
870 for setting_value in self.setting_values.values_mut() {
871 // If the global settings file changed, reload the global value for the field.
872 if changed_local_path.is_none() {
873 let mut value = setting_value.from_default(&self.default_settings, cx);
874 setting_value.refine(value.as_mut(), &refinements, cx);
875 setting_value.set_global_value(value);
876 }
877
878 // Reload the local values for the setting.
879 paths_stack.clear();
880 project_settings_stack.clear();
881 for ((root_id, directory_path), local_settings) in &self.local_settings {
882 // Build a stack of all of the local values for that setting.
883 while let Some(prev_entry) = paths_stack.last() {
884 if let Some((prev_root_id, prev_path)) = prev_entry
885 && (root_id != prev_root_id || !directory_path.starts_with(prev_path))
886 {
887 paths_stack.pop();
888 project_settings_stack.pop();
889 continue;
890 }
891 break;
892 }
893
894 paths_stack.push(Some((*root_id, directory_path.as_ref())));
895 project_settings_stack.push(local_settings);
896
897 // If a local settings file changed, then avoid recomputing local
898 // settings for any path outside of that directory.
899 if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| {
900 *root_id != changed_root_id || !directory_path.starts_with(changed_local_path)
901 }) {
902 continue;
903 }
904
905 let mut value = setting_value.from_default(&self.default_settings, cx);
906 setting_value.refine(value.as_mut(), &refinements, cx);
907 setting_value.refine(value.as_mut(), &project_settings_stack, cx);
908 setting_value.set_local_value(*root_id, directory_path.clone(), value);
909 }
910 }
911 Ok(())
912 }
913
914 pub fn editorconfig_properties(
915 &self,
916 for_worktree: WorktreeId,
917 for_path: &Path,
918 ) -> Option<EditorconfigProperties> {
919 let mut properties = EditorconfigProperties::new();
920
921 for (directory_with_config, _, parsed_editorconfig) in
922 self.local_editorconfig_settings(for_worktree)
923 {
924 if !for_path.starts_with(&directory_with_config) {
925 properties.use_fallbacks();
926 return Some(properties);
927 }
928 let parsed_editorconfig = parsed_editorconfig?;
929 if parsed_editorconfig.is_root {
930 properties = EditorconfigProperties::new();
931 }
932 for section in parsed_editorconfig.sections {
933 section.apply_to(&mut properties, for_path).log_err()?;
934 }
935 }
936
937 properties.use_fallbacks();
938 Some(properties)
939 }
940}
941
942#[derive(Debug, Clone, PartialEq)]
943pub enum InvalidSettingsError {
944 LocalSettings { path: PathBuf, message: String },
945 UserSettings { message: String },
946 ServerSettings { message: String },
947 DefaultSettings { message: String },
948 Editorconfig { path: PathBuf, message: String },
949 Tasks { path: PathBuf, message: String },
950 Debug { path: PathBuf, message: String },
951}
952
953impl std::fmt::Display for InvalidSettingsError {
954 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
955 match self {
956 InvalidSettingsError::LocalSettings { message, .. }
957 | InvalidSettingsError::UserSettings { message }
958 | InvalidSettingsError::ServerSettings { message }
959 | InvalidSettingsError::DefaultSettings { message }
960 | InvalidSettingsError::Tasks { message, .. }
961 | InvalidSettingsError::Editorconfig { message, .. }
962 | InvalidSettingsError::Debug { message, .. } => {
963 write!(f, "{message}")
964 }
965 }
966 }
967}
968impl std::error::Error for InvalidSettingsError {}
969
970impl Debug for SettingsStore {
971 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
972 f.debug_struct("SettingsStore")
973 .field(
974 "types",
975 &self
976 .setting_values
977 .values()
978 .map(|value| value.setting_type_name())
979 .collect::<Vec<_>>(),
980 )
981 .field("default_settings", &self.default_settings)
982 .field("user_settings", &self.user_settings)
983 .field("local_settings", &self.local_settings)
984 .finish_non_exhaustive()
985 }
986}
987
988impl<T: Settings> AnySettingValue for SettingValue<T> {
989 fn from_default(&self, s: &SettingsContent, cx: &mut App) -> Box<dyn Any> {
990 Box::new(T::from_defaults(s, cx)) as _
991 }
992
993 fn refine(&self, value: &mut dyn Any, refinements: &[&SettingsContent], cx: &mut App) {
994 let value = value.downcast_mut::<T>().unwrap();
995 for refinement in refinements {
996 value.refine(refinement, cx)
997 }
998 }
999
1000 fn setting_type_name(&self) -> &'static str {
1001 type_name::<T>()
1002 }
1003
1004 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)> {
1005 self.local_values
1006 .iter()
1007 .map(|(id, path, value)| (*id, path.clone(), value as _))
1008 .collect()
1009 }
1010
1011 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
1012 if let Some(SettingsLocation { worktree_id, path }) = path {
1013 for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
1014 if worktree_id == *settings_root_id && path.starts_with(settings_path) {
1015 return value;
1016 }
1017 }
1018 }
1019
1020 self.global_value
1021 .as_ref()
1022 .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
1023 }
1024
1025 fn set_global_value(&mut self, value: Box<dyn Any>) {
1026 self.global_value = Some(*value.downcast().unwrap());
1027 }
1028
1029 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>) {
1030 let value = *value.downcast().unwrap();
1031 match self
1032 .local_values
1033 .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
1034 {
1035 Ok(ix) => self.local_values[ix].2 = value,
1036 Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
1037 }
1038 }
1039
1040 fn import_from_vscode(
1041 &self,
1042 vscode_settings: &VsCodeSettings,
1043 settings_content: &mut SettingsContent,
1044 ) {
1045 T::import_from_vscode(vscode_settings, settings_content);
1046 }
1047}
1048
1049#[cfg(test)]
1050mod tests {
1051 use std::num::NonZeroU32;
1052
1053 use crate::{
1054 TitleBarSettingsContent, TitleBarVisibility, VsCodeSettingsSource, default_settings,
1055 settings_content::LanguageSettingsContent, test_settings,
1056 };
1057
1058 use super::*;
1059 use unindent::Unindent;
1060 use util::MergeFrom;
1061
1062 #[derive(Debug, PartialEq)]
1063 struct AutoUpdateSetting {
1064 auto_update: bool,
1065 }
1066
1067 impl Settings for AutoUpdateSetting {
1068 fn from_defaults(content: &SettingsContent, _: &mut App) -> Self {
1069 AutoUpdateSetting {
1070 auto_update: content.auto_update.unwrap(),
1071 }
1072 }
1073
1074 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1075 if let Some(auto_update) = content.auto_update {
1076 self.auto_update = auto_update;
1077 }
1078 }
1079
1080 fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {}
1081 }
1082
1083 #[derive(Debug, PartialEq)]
1084 struct TitleBarSettings {
1085 show: TitleBarVisibility,
1086 show_branch_name: bool,
1087 }
1088
1089 impl Settings for TitleBarSettings {
1090 fn from_defaults(content: &SettingsContent, _: &mut App) -> Self {
1091 let content = content.title_bar.clone().unwrap();
1092 TitleBarSettings {
1093 show: content.show.unwrap(),
1094 show_branch_name: content.show_branch_name.unwrap(),
1095 }
1096 }
1097
1098 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1099 let Some(content) = content.title_bar.as_ref() else {
1100 return;
1101 };
1102 self.show.merge_from(&content.show)
1103 }
1104
1105 fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
1106 let mut show = None;
1107
1108 vscode.enum_setting("window.titleBarStyle", &mut show, |value| match value {
1109 "never" => Some(TitleBarVisibility::Never),
1110 "always" => Some(TitleBarVisibility::Always),
1111 _ => None,
1112 });
1113 if let Some(show) = show {
1114 content.title_bar.get_or_insert_default().show.replace(show);
1115 }
1116 }
1117 }
1118
1119 #[derive(Debug, PartialEq)]
1120 struct DefaultLanguageSettings {
1121 tab_size: NonZeroU32,
1122 preferred_line_length: u32,
1123 }
1124
1125 impl Settings for DefaultLanguageSettings {
1126 fn from_defaults(content: &SettingsContent, _: &mut App) -> Self {
1127 let content = &content.project.all_languages.defaults;
1128 DefaultLanguageSettings {
1129 tab_size: content.tab_size.unwrap(),
1130 preferred_line_length: content.preferred_line_length.unwrap(),
1131 }
1132 }
1133
1134 fn refine(&mut self, content: &SettingsContent, _: &mut App) {
1135 let content = &content.project.all_languages.defaults;
1136 self.tab_size.merge_from(&content.tab_size);
1137 self.preferred_line_length
1138 .merge_from(&content.preferred_line_length);
1139 }
1140
1141 fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
1142 let content = &mut content.project.all_languages.defaults;
1143
1144 if let Some(size) = vscode
1145 .read_value("editor.tabSize")
1146 .and_then(|v| v.as_u64())
1147 .and_then(|n| NonZeroU32::new(n as u32))
1148 {
1149 content.tab_size = Some(size);
1150 }
1151 }
1152 }
1153
1154 #[gpui::test]
1155 fn test_settings_store_basic(cx: &mut App) {
1156 let mut store = SettingsStore::new(cx, &default_settings());
1157 store.register_setting::<AutoUpdateSetting>(cx);
1158 store.register_setting::<TitleBarSettings>(cx);
1159 store.register_setting::<DefaultLanguageSettings>(cx);
1160
1161 assert_eq!(
1162 store.get::<AutoUpdateSetting>(None),
1163 &AutoUpdateSetting { auto_update: true }
1164 );
1165 assert_eq!(
1166 store.get::<TitleBarSettings>(None).show,
1167 TitleBarVisibility::Always
1168 );
1169
1170 store
1171 .set_user_settings(
1172 r#"{
1173 "auto_update": false,
1174 "title_bar": {
1175 "show": "never"
1176 }
1177 }"#,
1178 cx,
1179 )
1180 .unwrap();
1181
1182 assert_eq!(
1183 store.get::<AutoUpdateSetting>(None),
1184 &AutoUpdateSetting { auto_update: false }
1185 );
1186 assert_eq!(
1187 store.get::<TitleBarSettings>(None).show,
1188 TitleBarVisibility::Never
1189 );
1190
1191 // todo!()
1192 store
1193 .set_local_settings(
1194 WorktreeId::from_usize(1),
1195 Path::new("/root1").into(),
1196 LocalSettingsKind::Settings,
1197 Some(r#"{ "tab_size": 5 }"#),
1198 cx,
1199 )
1200 .unwrap();
1201 store
1202 .set_local_settings(
1203 WorktreeId::from_usize(1),
1204 Path::new("/root1/subdir").into(),
1205 LocalSettingsKind::Settings,
1206 Some(r#"{ "preferred_line_length": 50 }"#),
1207 cx,
1208 )
1209 .unwrap();
1210
1211 store
1212 .set_local_settings(
1213 WorktreeId::from_usize(1),
1214 Path::new("/root2").into(),
1215 LocalSettingsKind::Settings,
1216 Some(r#"{ "tab_size": 9, "title_bar": { "show_branch_name": false } }"#),
1217 cx,
1218 )
1219 .unwrap();
1220
1221 assert_eq!(
1222 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1223 worktree_id: WorktreeId::from_usize(1),
1224 path: Path::new("/root1/something"),
1225 })),
1226 &DefaultLanguageSettings {
1227 preferred_line_length: 80,
1228 tab_size: 5.try_into().unwrap(),
1229 }
1230 );
1231 assert_eq!(
1232 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1233 worktree_id: WorktreeId::from_usize(1),
1234 path: Path::new("/root1/subdir/something")
1235 })),
1236 &DefaultLanguageSettings {
1237 preferred_line_length: 50,
1238 tab_size: 5.try_into().unwrap(),
1239 }
1240 );
1241 assert_eq!(
1242 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1243 worktree_id: WorktreeId::from_usize(1),
1244 path: Path::new("/root2/something")
1245 })),
1246 &DefaultLanguageSettings {
1247 preferred_line_length: 80,
1248 tab_size: 9.try_into().unwrap(),
1249 }
1250 );
1251 assert_eq!(
1252 store.get::<TitleBarSettings>(Some(SettingsLocation {
1253 worktree_id: WorktreeId::from_usize(1),
1254 path: Path::new("/root2/something")
1255 })),
1256 &TitleBarSettings {
1257 show: TitleBarVisibility::Never,
1258 show_branch_name: true,
1259 }
1260 );
1261 }
1262
1263 #[gpui::test]
1264 fn test_setting_store_assign_json_before_register(cx: &mut App) {
1265 let mut store = SettingsStore::new(cx, &test_settings());
1266 store
1267 .set_user_settings(r#"{ "auto_update": false }"#, cx)
1268 .unwrap();
1269 store.register_setting::<AutoUpdateSetting>(cx);
1270 store.register_setting::<TitleBarSettings>(cx);
1271
1272 assert_eq!(
1273 store.get::<AutoUpdateSetting>(None),
1274 &AutoUpdateSetting { auto_update: false }
1275 );
1276 assert_eq!(
1277 store.get::<TitleBarSettings>(None).show,
1278 TitleBarVisibility::Always,
1279 );
1280 }
1281
1282 #[track_caller]
1283 fn check_settings_update(
1284 store: &mut SettingsStore,
1285 old_json: String,
1286 update: fn(&mut SettingsContent),
1287 expected_new_json: String,
1288 cx: &mut App,
1289 ) {
1290 store.set_user_settings(&old_json, cx).ok();
1291 let edits = store.edits_for_update(&old_json, update);
1292 let mut new_json = old_json;
1293 for (range, replacement) in edits.into_iter() {
1294 new_json.replace_range(range, &replacement);
1295 }
1296 pretty_assertions::assert_eq!(new_json, expected_new_json);
1297 }
1298
1299 #[gpui::test]
1300 fn test_setting_store_update(cx: &mut App) {
1301 let mut store = SettingsStore::new(cx, &test_settings());
1302
1303 // entries added and updated
1304 check_settings_update(
1305 &mut store,
1306 r#"{
1307 "languages": {
1308 "JSON": {
1309 "auto_indent": true
1310 }
1311 }
1312 }"#
1313 .unindent(),
1314 |settings| {
1315 settings
1316 .languages_mut()
1317 .get_mut("JSON")
1318 .unwrap()
1319 .auto_indent = Some(false);
1320
1321 settings.languages_mut().insert(
1322 "Rust".into(),
1323 LanguageSettingsContent {
1324 auto_indent: Some(true),
1325 ..Default::default()
1326 },
1327 );
1328 },
1329 r#"{
1330 "languages": {
1331 "Rust": {
1332 "auto_indent": true
1333 },
1334 "JSON": {
1335 "auto_indent": false
1336 }
1337 }
1338 }"#
1339 .unindent(),
1340 cx,
1341 );
1342
1343 // entries removed
1344 check_settings_update(
1345 &mut store,
1346 r#"{
1347 "languages": {
1348 "Rust": {
1349 "language_setting_2": true
1350 },
1351 "JSON": {
1352 "language_setting_1": false
1353 }
1354 }
1355 }"#
1356 .unindent(),
1357 |settings| {
1358 settings.languages_mut().remove("JSON").unwrap();
1359 },
1360 r#"{
1361 "languages": {
1362 "Rust": {
1363 "language_setting_2": true
1364 }
1365 }
1366 }"#
1367 .unindent(),
1368 cx,
1369 );
1370
1371 check_settings_update(
1372 &mut store,
1373 r#"{
1374 "languages": {
1375 "Rust": {
1376 "language_setting_2": true
1377 },
1378 "JSON": {
1379 "language_setting_1": false
1380 }
1381 }
1382 }"#
1383 .unindent(),
1384 |settings| {
1385 settings.languages_mut().remove("Rust").unwrap();
1386 },
1387 r#"{
1388 "languages": {
1389 "JSON": {
1390 "language_setting_1": false
1391 }
1392 }
1393 }"#
1394 .unindent(),
1395 cx,
1396 );
1397
1398 // weird formatting
1399 check_settings_update(
1400 &mut store,
1401 r#"{
1402 "title_bar": { "show": "always", "name": "Max" }
1403 }"#
1404 .unindent(),
1405 |settings| {
1406 settings.title_bar.as_mut().unwrap().show = Some(TitleBarVisibility::Never);
1407 },
1408 r#"{
1409 "title_bar": { "show": "never", "name": "Max" }
1410 }"#
1411 .unindent(),
1412 cx,
1413 );
1414
1415 // single-line formatting, other keys
1416 check_settings_update(
1417 &mut store,
1418 r#"{ "one": 1, "two": 2 }"#.to_owned(),
1419 |settings| settings.auto_update = Some(true),
1420 r#"{ "auto_update": true, "one": 1, "two": 2 }"#.to_owned(),
1421 cx,
1422 );
1423
1424 // empty object
1425 check_settings_update(
1426 &mut store,
1427 r#"{
1428 "title_bar": {}
1429 }"#
1430 .unindent(),
1431 |settings| settings.title_bar.as_mut().unwrap().show_menus = Some(true),
1432 r#"{
1433 "title_bar": {
1434 "show_menus": true
1435 }
1436 }"#
1437 .unindent(),
1438 cx,
1439 );
1440
1441 // no content
1442 check_settings_update(
1443 &mut store,
1444 r#""#.unindent(),
1445 |settings| {
1446 settings.title_bar = Some(TitleBarSettingsContent {
1447 show_branch_name: Some(true),
1448 ..Default::default()
1449 })
1450 },
1451 r#"{
1452 "title_bar": {
1453 "show_branch_name": true
1454 }
1455 }
1456 "#
1457 .unindent(),
1458 cx,
1459 );
1460
1461 check_settings_update(
1462 &mut store,
1463 r#"{
1464 }
1465 "#
1466 .unindent(),
1467 |settings| settings.title_bar.get_or_insert_default().show_branch_name = Some(true),
1468 r#"{
1469 "title_bar": {
1470 "show_branch_name": true
1471 }
1472 }
1473 "#
1474 .unindent(),
1475 cx,
1476 );
1477 }
1478
1479 #[gpui::test]
1480 fn test_vscode_import(cx: &mut App) {
1481 let mut store = SettingsStore::new(cx, &test_settings());
1482 store.register_setting::<DefaultLanguageSettings>(cx);
1483 store.register_setting::<TitleBarSettings>(cx);
1484 store.register_setting::<AutoUpdateSetting>(cx);
1485
1486 // create settings that werent present
1487 check_vscode_import(
1488 &mut store,
1489 r#"{
1490 }
1491 "#
1492 .unindent(),
1493 r#" { "editor.tabSize": 37 } "#.to_owned(),
1494 r#"{
1495 "tab_size": 37
1496 }
1497 "#
1498 .unindent(),
1499 cx,
1500 );
1501
1502 // persist settings that were present
1503 check_vscode_import(
1504 &mut store,
1505 r#"{
1506 "preferred_line_length": 99,
1507 }
1508 "#
1509 .unindent(),
1510 r#"{ "editor.tabSize": 42 }"#.to_owned(),
1511 r#"{
1512 "tab_size": 42,
1513 "preferred_line_length": 99,
1514 }
1515 "#
1516 .unindent(),
1517 cx,
1518 );
1519
1520 // don't clobber settings that aren't present in vscode
1521 check_vscode_import(
1522 &mut store,
1523 r#"{
1524 "preferred_line_length": 99,
1525 "tab_size": 42
1526 }
1527 "#
1528 .unindent(),
1529 r#"{}"#.to_owned(),
1530 r#"{
1531 "preferred_line_length": 99,
1532 "tab_size": 42
1533 }
1534 "#
1535 .unindent(),
1536 cx,
1537 );
1538
1539 // custom enum
1540 check_vscode_import(
1541 &mut store,
1542 r#"{
1543 "title_bar": {
1544 "show": "always"
1545 }
1546 }
1547 "#
1548 .unindent(),
1549 r#"{ "window.titleBarStyle": "never" }"#.to_owned(),
1550 r#"{
1551 "title_bar": {
1552 "show": "never"
1553 }
1554 }
1555 "#
1556 .unindent(),
1557 cx,
1558 );
1559 }
1560
1561 #[track_caller]
1562 fn check_vscode_import(
1563 store: &mut SettingsStore,
1564 old: String,
1565 vscode: String,
1566 expected: String,
1567 cx: &mut App,
1568 ) {
1569 store.set_user_settings(&old, cx).ok();
1570 let new = store.get_vscode_edits(
1571 old,
1572 &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
1573 );
1574 pretty_assertions::assert_eq!(new, expected);
1575 }
1576
1577 #[gpui::test]
1578 fn test_update_git_settings(cx: &mut App) {
1579 let store = SettingsStore::new(cx, &test_settings());
1580
1581 let actual = store.new_text_for_update("{}".to_string(), |current| {
1582 current
1583 .git
1584 .get_or_insert_default()
1585 .inline_blame
1586 .get_or_insert_default()
1587 .enabled = Some(true);
1588 });
1589 assert_eq!(
1590 actual,
1591 r#"{
1592 "git": {
1593 "inline_blame": {
1594 "enabled": true
1595 }
1596 }
1597 }
1598 "#
1599 .unindent()
1600 );
1601 }
1602
1603 #[gpui::test]
1604 fn test_global_settings(cx: &mut App) {
1605 let mut store = SettingsStore::new(cx, &test_settings());
1606 store.register_setting::<TitleBarSettings>(cx);
1607
1608 // Set global settings - these should override defaults but not user settings
1609 store
1610 .set_global_settings(
1611 r#"{
1612 "title_bar": {
1613 "show": "never",
1614 }
1615 }"#,
1616 cx,
1617 )
1618 .unwrap();
1619
1620 // Before user settings, global settings should apply
1621 assert_eq!(
1622 store.get::<TitleBarSettings>(None),
1623 &TitleBarSettings {
1624 show: TitleBarVisibility::Never,
1625 show_branch_name: true,
1626 }
1627 );
1628
1629 // Set user settings - these should override both defaults and global
1630 store
1631 .set_user_settings(
1632 r#"{
1633 "title_bar": {
1634 "show": "always"
1635 }
1636 }"#,
1637 cx,
1638 )
1639 .unwrap();
1640
1641 // User settings should override global settings
1642 assert_eq!(
1643 store.get::<TitleBarSettings>(None),
1644 &TitleBarSettings {
1645 show: TitleBarVisibility::Always,
1646 show_branch_name: true, // Staff from global settings
1647 }
1648 );
1649 }
1650}