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