1use anyhow::{Context as _, Result};
2use collections::{BTreeMap, HashMap, btree_map, hash_map};
3use fs::Fs;
4use futures::{
5 FutureExt, StreamExt,
6 channel::{mpsc, oneshot},
7 future::LocalBoxFuture,
8};
9use gpui::{
10 App, AppContext, AsyncApp, BorrowAppContext, Entity, Global, SharedString, Task, UpdateGlobal,
11};
12
13use paths::{local_settings_file_relative_path, task_file_name};
14use schemars::{JsonSchema, json_schema};
15use serde_json::Value;
16use settings_content::{ActionName, ParseStatus};
17use std::{
18 any::{Any, TypeId, type_name},
19 fmt::Debug,
20 ops::Range,
21 path::{Path, PathBuf},
22 rc::Rc,
23 str,
24 sync::Arc,
25};
26use util::{
27 ResultExt as _,
28 rel_path::RelPath,
29 schemars::{AllowTrailingCommas, DefaultDenyUnknownFields, replace_subschema},
30};
31
32use crate::editorconfig_store::EditorconfigStore;
33
34use crate::{
35 ActiveSettingsProfileName, FontFamilyName, IconThemeName, LanguageSettingsContent,
36 LanguageToSettingsMap, LspSettings, LspSettingsMap, SemanticTokenRules, ThemeName,
37 UserSettingsContentExt, VsCodeSettings, WorktreeId,
38 settings_content::{
39 ExtensionsSettingsContent, ProfileBase, ProjectSettingsContent, RootUserSettings,
40 SettingsContent, UserSettingsContent, merge_from::MergeFrom,
41 },
42};
43
44use settings_json::{infer_json_indent_size, update_value_in_json_text};
45
46pub const LSP_SETTINGS_SCHEMA_URL_PREFIX: &str = "zed://schemas/settings/lsp/";
47
48pub trait SettingsKey: 'static + Send + Sync {
49 /// The name of a key within the JSON file from which this setting should
50 /// be deserialized. If this is `None`, then the setting will be deserialized
51 /// from the root object.
52 const KEY: Option<&'static str>;
53
54 const FALLBACK_KEY: Option<&'static str> = None;
55}
56
57/// A value that can be defined as a user setting.
58///
59/// Settings can be loaded from a combination of multiple JSON files.
60pub trait Settings: 'static + Send + Sync + Sized {
61 /// The name of the keys in the [`FileContent`](Self::FileContent) that should
62 /// always be written to a settings file, even if their value matches the default
63 /// value.
64 ///
65 /// This is useful for tagged [`FileContent`](Self::FileContent)s where the tag
66 /// is a "version" field that should always be persisted, even if the current
67 /// user settings match the current version of the settings.
68 const PRESERVED_KEYS: Option<&'static [&'static str]> = None;
69
70 /// Read the value from default.json.
71 ///
72 /// This function *should* panic if default values are missing,
73 /// and you should add a default to default.json for documentation.
74 fn from_settings(content: &SettingsContent) -> Self;
75
76 #[track_caller]
77 fn register(cx: &mut App)
78 where
79 Self: Sized,
80 {
81 SettingsStore::update_global(cx, |store, _| {
82 store.register_setting::<Self>();
83 });
84 }
85
86 #[track_caller]
87 fn get<'a>(path: Option<SettingsLocation>, cx: &'a App) -> &'a Self
88 where
89 Self: Sized,
90 {
91 cx.global::<SettingsStore>().get(path)
92 }
93
94 #[track_caller]
95 fn get_global(cx: &App) -> &Self
96 where
97 Self: Sized,
98 {
99 cx.global::<SettingsStore>().get(None)
100 }
101
102 #[track_caller]
103 fn try_get(cx: &App) -> Option<&Self>
104 where
105 Self: Sized,
106 {
107 if cx.has_global::<SettingsStore>() {
108 cx.global::<SettingsStore>().try_get(None)
109 } else {
110 None
111 }
112 }
113
114 #[track_caller]
115 fn try_read_global<R>(cx: &AsyncApp, f: impl FnOnce(&Self) -> R) -> Option<R>
116 where
117 Self: Sized,
118 {
119 cx.try_read_global(|s: &SettingsStore, _| f(s.get(None)))
120 }
121
122 #[track_caller]
123 fn override_global(settings: Self, cx: &mut App)
124 where
125 Self: Sized,
126 {
127 cx.global_mut::<SettingsStore>().override_global(settings)
128 }
129}
130
131pub struct RegisteredSetting {
132 pub settings_value: fn() -> Box<dyn AnySettingValue>,
133 pub from_settings: fn(&SettingsContent) -> Box<dyn Any>,
134 pub id: fn() -> TypeId,
135}
136
137inventory::collect!(RegisteredSetting);
138
139#[derive(Clone, Copy, Debug)]
140pub struct SettingsLocation<'a> {
141 pub worktree_id: WorktreeId,
142 pub path: &'a RelPath,
143}
144
145pub struct SettingsStore {
146 setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
147 default_settings: Rc<SettingsContent>,
148 user_settings: Option<UserSettingsContent>,
149 global_settings: Option<Box<SettingsContent>>,
150
151 extension_settings: Option<Box<SettingsContent>>,
152 server_settings: Option<Box<SettingsContent>>,
153
154 language_semantic_token_rules: HashMap<SharedString, SemanticTokenRules>,
155
156 merged_settings: Rc<SettingsContent>,
157
158 last_user_settings_content: Option<String>,
159 last_global_settings_content: Option<String>,
160 local_settings: BTreeMap<(WorktreeId, Arc<RelPath>), SettingsContent>,
161 pub editorconfig_store: Entity<EditorconfigStore>,
162
163 _settings_files_watcher: Option<Task<()>>,
164 _setting_file_updates: Task<()>,
165 setting_file_updates_tx:
166 mpsc::UnboundedSender<Box<dyn FnOnce(AsyncApp) -> LocalBoxFuture<'static, Result<()>>>>,
167 file_errors: BTreeMap<SettingsFile, SettingsParseResult>,
168}
169
170#[derive(Clone, PartialEq, Eq, Debug)]
171pub enum SettingsFile {
172 Default,
173 Global,
174 User,
175 Server,
176 /// Represents project settings in ssh projects as well as local projects
177 Project((WorktreeId, Arc<RelPath>)),
178}
179
180impl PartialOrd for SettingsFile {
181 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
182 Some(self.cmp(other))
183 }
184}
185
186/// Sorted in order of precedence
187impl Ord for SettingsFile {
188 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
189 use SettingsFile::*;
190 use std::cmp::Ordering;
191 match (self, other) {
192 (User, User) => Ordering::Equal,
193 (Server, Server) => Ordering::Equal,
194 (Default, Default) => Ordering::Equal,
195 (Project((id1, rel_path1)), Project((id2, rel_path2))) => id1
196 .cmp(id2)
197 .then_with(|| rel_path1.cmp(rel_path2).reverse()),
198 (Project(_), _) => Ordering::Less,
199 (_, Project(_)) => Ordering::Greater,
200 (Server, _) => Ordering::Less,
201 (_, Server) => Ordering::Greater,
202 (User, _) => Ordering::Less,
203 (_, User) => Ordering::Greater,
204 (Global, _) => Ordering::Less,
205 (_, Global) => Ordering::Greater,
206 }
207 }
208}
209
210#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
211pub enum LocalSettingsKind {
212 Settings,
213 Tasks,
214 Editorconfig,
215 Debug,
216}
217
218#[derive(Clone, Debug, PartialEq, Eq, Hash)]
219pub enum LocalSettingsPath {
220 InWorktree(Arc<RelPath>),
221 OutsideWorktree(Arc<Path>),
222}
223
224impl LocalSettingsPath {
225 pub fn is_outside_worktree(&self) -> bool {
226 matches!(self, Self::OutsideWorktree(_))
227 }
228
229 pub fn to_proto(&self) -> String {
230 match self {
231 Self::InWorktree(path) => path.to_proto(),
232 Self::OutsideWorktree(path) => path.to_string_lossy().to_string(),
233 }
234 }
235
236 pub fn from_proto(path: &str, is_outside_worktree: bool) -> anyhow::Result<Self> {
237 if is_outside_worktree {
238 Ok(Self::OutsideWorktree(PathBuf::from(path).into()))
239 } else {
240 Ok(Self::InWorktree(RelPath::from_proto(path)?))
241 }
242 }
243}
244
245impl Global for SettingsStore {}
246
247#[derive(Default)]
248pub struct DefaultSemanticTokenRules(pub SemanticTokenRules);
249
250impl gpui::Global for DefaultSemanticTokenRules {}
251
252#[doc(hidden)]
253#[derive(Debug)]
254pub struct SettingValue<T> {
255 #[doc(hidden)]
256 pub global_value: Option<T>,
257 #[doc(hidden)]
258 pub local_values: Vec<(WorktreeId, Arc<RelPath>, T)>,
259}
260
261#[doc(hidden)]
262pub trait AnySettingValue: 'static + Send + Sync {
263 fn setting_type_name(&self) -> &'static str;
264
265 fn from_settings(&self, s: &SettingsContent) -> Box<dyn Any>;
266
267 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any;
268 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<RelPath>, &dyn Any)>;
269 fn set_global_value(&mut self, value: Box<dyn Any>);
270 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<RelPath>, value: Box<dyn Any>);
271 fn clear_local_values(&mut self, root_id: WorktreeId);
272}
273
274/// Parameters that are used when generating some JSON schemas at runtime.
275pub struct SettingsJsonSchemaParams<'a> {
276 pub language_names: &'a [String],
277 pub font_names: &'a [String],
278 pub theme_names: &'a [SharedString],
279 pub icon_theme_names: &'a [SharedString],
280 pub lsp_adapter_names: &'a [String],
281 pub action_names: &'a [&'a str],
282 pub action_documentation: &'a HashMap<&'a str, &'a str>,
283 pub deprecations: &'a HashMap<&'a str, &'a str>,
284 pub deprecation_messages: &'a HashMap<&'a str, &'a str>,
285}
286
287impl SettingsStore {
288 pub fn new(cx: &mut App, default_settings: &str) -> Self {
289 Self::new_with_semantic_tokens(cx, default_settings)
290 }
291
292 pub fn new_with_semantic_tokens(cx: &mut App, default_settings: &str) -> Self {
293 let default_settings: SettingsContent =
294 SettingsContent::parse_json_with_comments(default_settings).unwrap();
295 Self::from_settings_content(cx, default_settings)
296 }
297
298 fn from_settings_content(cx: &mut App, default_settings: SettingsContent) -> Self {
299 let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();
300 if !cx.has_global::<DefaultSemanticTokenRules>() {
301 cx.set_global::<DefaultSemanticTokenRules>(
302 crate::parse_json_with_comments::<SemanticTokenRules>(
303 &crate::default_semantic_token_rules(),
304 )
305 .map(DefaultSemanticTokenRules)
306 .unwrap_or_default(),
307 );
308 }
309 let default_settings: Rc<SettingsContent> = default_settings.into();
310 let mut this = Self {
311 setting_values: Default::default(),
312 default_settings: default_settings.clone(),
313 global_settings: None,
314 server_settings: None,
315 user_settings: None,
316 extension_settings: None,
317 language_semantic_token_rules: HashMap::default(),
318
319 merged_settings: default_settings,
320 last_user_settings_content: None,
321 last_global_settings_content: None,
322 local_settings: BTreeMap::default(),
323 editorconfig_store: cx.new(|_| EditorconfigStore::default()),
324 _settings_files_watcher: None,
325 setting_file_updates_tx,
326 _setting_file_updates: cx.spawn(async move |cx| {
327 while let Some(setting_file_update) = setting_file_updates_rx.next().await {
328 (setting_file_update)(cx.clone()).await.log_err();
329 }
330 }),
331 file_errors: BTreeMap::default(),
332 };
333
334 this.load_settings_types();
335
336 this
337 }
338
339 pub fn observe_active_settings_profile_name(cx: &mut App) -> gpui::Subscription {
340 cx.observe_global::<ActiveSettingsProfileName>(|cx| {
341 Self::update_global(cx, |store, cx| {
342 store.recompute_values(None, cx);
343 });
344 })
345 }
346
347 pub fn update<C, R>(cx: &mut C, f: impl FnOnce(&mut Self, &mut C) -> R) -> R
348 where
349 C: BorrowAppContext,
350 {
351 cx.update_global(f)
352 }
353
354 pub fn watch_settings_files(
355 &mut self,
356 fs: Arc<dyn Fs>,
357 cx: &mut App,
358 settings_changed: impl 'static + Fn(SettingsFile, SettingsParseResult, &mut App),
359 ) {
360 let (mut user_settings_file_rx, user_settings_watcher) = crate::watch_config_file(
361 cx.background_executor(),
362 fs.clone(),
363 paths::settings_file().clone(),
364 );
365 let (mut global_settings_file_rx, global_settings_watcher) = crate::watch_config_file(
366 cx.background_executor(),
367 fs,
368 paths::global_settings_file().clone(),
369 );
370
371 let global_content = cx
372 .foreground_executor()
373 .block_on(global_settings_file_rx.next())
374 .unwrap();
375 let user_content = cx
376 .foreground_executor()
377 .block_on(user_settings_file_rx.next())
378 .unwrap();
379
380 let result = self.set_user_settings(&user_content, cx);
381 settings_changed(SettingsFile::User, result, cx);
382 let result = self.set_global_settings(&global_content, cx);
383 settings_changed(SettingsFile::Global, result, cx);
384
385 self._settings_files_watcher = Some(cx.spawn(async move |cx| {
386 let _user_settings_watcher = user_settings_watcher;
387 let _global_settings_watcher = global_settings_watcher;
388 let mut settings_streams = futures::stream::select(
389 global_settings_file_rx.map(|content| (SettingsFile::Global, content)),
390 user_settings_file_rx.map(|content| (SettingsFile::User, content)),
391 );
392
393 while let Some((settings_file, content)) = settings_streams.next().await {
394 cx.update_global(|store: &mut SettingsStore, cx| {
395 let result = match settings_file {
396 SettingsFile::User => store.set_user_settings(&content, cx),
397 SettingsFile::Global => store.set_global_settings(&content, cx),
398 _ => return,
399 };
400 settings_changed(settings_file, result, cx);
401 cx.refresh_windows();
402 });
403 }
404 }));
405 }
406
407 /// Add a new type of setting to the store.
408 pub fn register_setting<T: Settings>(&mut self) {
409 self.register_setting_internal(&RegisteredSetting {
410 settings_value: || {
411 Box::new(SettingValue::<T> {
412 global_value: None,
413 local_values: Vec::new(),
414 })
415 },
416 from_settings: |content| Box::new(T::from_settings(content)),
417 id: || TypeId::of::<T>(),
418 });
419 }
420
421 fn load_settings_types(&mut self) {
422 for registered_setting in inventory::iter::<RegisteredSetting>() {
423 self.register_setting_internal(registered_setting);
424 }
425 }
426
427 fn register_setting_internal(&mut self, registered_setting: &RegisteredSetting) {
428 let entry = self.setting_values.entry((registered_setting.id)());
429
430 if matches!(entry, hash_map::Entry::Occupied(_)) {
431 return;
432 }
433
434 let setting_value = entry.or_insert((registered_setting.settings_value)());
435 let value = (registered_setting.from_settings)(&self.merged_settings);
436 setting_value.set_global_value(value);
437 }
438
439 pub fn merged_settings(&self) -> &SettingsContent {
440 &self.merged_settings
441 }
442
443 /// Get the value of a setting.
444 ///
445 /// Panics if the given setting type has not been registered, or if there is no
446 /// value for this setting.
447 pub fn get<T: Settings>(&self, path: Option<SettingsLocation>) -> &T {
448 self.setting_values
449 .get(&TypeId::of::<T>())
450 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
451 .value_for_path(path)
452 .downcast_ref::<T>()
453 .expect("no default value for setting type")
454 }
455
456 /// Get the value of a setting.
457 ///
458 /// Does not panic
459 pub fn try_get<T: Settings>(&self, path: Option<SettingsLocation>) -> Option<&T> {
460 self.setting_values
461 .get(&TypeId::of::<T>())
462 .map(|value| value.value_for_path(path))
463 .and_then(|value| value.downcast_ref::<T>())
464 }
465
466 /// Get all values from project specific settings
467 pub fn get_all_locals<T: Settings>(&self) -> Vec<(WorktreeId, Arc<RelPath>, &T)> {
468 self.setting_values
469 .get(&TypeId::of::<T>())
470 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
471 .all_local_values()
472 .into_iter()
473 .map(|(id, path, any)| {
474 (
475 id,
476 path,
477 any.downcast_ref::<T>()
478 .expect("wrong value type for setting"),
479 )
480 })
481 .collect()
482 }
483
484 /// Override the global value for a setting.
485 ///
486 /// The given value will be overwritten if the user settings file changes.
487 pub fn override_global<T: Settings>(&mut self, value: T) {
488 self.setting_values
489 .get_mut(&TypeId::of::<T>())
490 .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
491 .set_global_value(Box::new(value))
492 }
493
494 /// Get the user's settings content.
495 ///
496 /// For user-facing functionality use the typed setting interface.
497 /// (e.g. ProjectSettings::get_global(cx))
498 pub fn raw_user_settings(&self) -> Option<&UserSettingsContent> {
499 self.user_settings.as_ref()
500 }
501
502 /// Get the default settings content as a raw JSON value.
503 pub fn raw_default_settings(&self) -> &SettingsContent {
504 &self.default_settings
505 }
506
507 /// Get the configured settings profile names.
508 pub fn configured_settings_profiles(&self) -> impl Iterator<Item = &str> {
509 self.user_settings
510 .iter()
511 .flat_map(|settings| settings.profiles.keys().map(|k| k.as_str()))
512 }
513
514 #[cfg(any(test, feature = "test-support"))]
515 pub fn test(cx: &mut App) -> Self {
516 static CACHED_SETTINGS_CONTENT: std::sync::LazyLock<SettingsContent> =
517 std::sync::LazyLock::new(|| {
518 SettingsContent::parse_json_with_comments(crate::test_settings()).unwrap()
519 });
520 Self::from_settings_content(cx, CACHED_SETTINGS_CONTENT.clone())
521 }
522
523 /// Updates the value of a setting in the user's global configuration.
524 ///
525 /// This is only for tests. Normally, settings are only loaded from
526 /// JSON files.
527 #[cfg(any(test, feature = "test-support"))]
528 pub fn update_user_settings(
529 &mut self,
530 cx: &mut App,
531 update: impl FnOnce(&mut SettingsContent),
532 ) {
533 let mut content = self.user_settings.clone().unwrap_or_default().content;
534 update(&mut content);
535 fn trail(this: &mut SettingsStore, content: Box<SettingsContent>, cx: &mut App) {
536 let new_text = serde_json::to_string(&UserSettingsContent {
537 content,
538 ..Default::default()
539 })
540 .unwrap();
541 _ = this.set_user_settings(&new_text, cx);
542 }
543 trail(self, content, cx);
544 }
545
546 pub async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
547 match fs.load(paths::settings_file()).await {
548 result @ Ok(_) => result,
549 Err(err) => {
550 if let Some(e) = err.downcast_ref::<std::io::Error>()
551 && e.kind() == std::io::ErrorKind::NotFound
552 {
553 return Ok(crate::initial_user_settings_content().to_string());
554 }
555 Err(err)
556 }
557 }
558 }
559
560 fn update_settings_file_inner(
561 &self,
562 fs: Arc<dyn Fs>,
563 update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>,
564 ) -> oneshot::Receiver<Result<()>> {
565 let (tx, rx) = oneshot::channel::<Result<()>>();
566 self.setting_file_updates_tx
567 .unbounded_send(Box::new(move |cx: AsyncApp| {
568 async move {
569 let res = async move {
570 let old_text = Self::load_settings(&fs).await?;
571 let new_text = update(old_text, cx.clone())?;
572
573 let settings_path = paths::settings_file().as_path();
574 if fs.is_file(settings_path).await {
575 let resolved_path =
576 fs.canonicalize(settings_path).await.with_context(|| {
577 format!(
578 "Failed to canonicalize settings path {:?}",
579 settings_path
580 )
581 })?;
582
583 fs.atomic_write(resolved_path.clone(), new_text.clone())
584 .await
585 .with_context(|| {
586 format!("Failed to write settings to file {:?}", resolved_path)
587 })?;
588 } else {
589 fs.atomic_write(settings_path.to_path_buf(), new_text.clone())
590 .await
591 .with_context(|| {
592 format!("Failed to write settings to file {:?}", settings_path)
593 })?;
594 }
595
596 cx.update_global(|store: &mut SettingsStore, cx| {
597 store.set_user_settings(&new_text, cx).result().map(|_| ())
598 })
599 }
600 .await;
601
602 let new_res = match &res {
603 Ok(_) => anyhow::Ok(()),
604 Err(e) => Err(anyhow::anyhow!("{:?}", e)),
605 };
606
607 _ = tx.send(new_res);
608 res
609 }
610 .boxed_local()
611 }))
612 .map_err(|err| anyhow::format_err!("Failed to update settings file: {}", err))
613 .log_with_level(log::Level::Warn);
614 return rx;
615 }
616
617 pub fn update_settings_file(
618 &self,
619 fs: Arc<dyn Fs>,
620 update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
621 ) {
622 _ = self.update_settings_file_with_completion(fs, update);
623 }
624
625 pub fn update_settings_file_with_completion(
626 &self,
627 fs: Arc<dyn Fs>,
628 update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
629 ) -> oneshot::Receiver<Result<()>> {
630 self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
631 cx.read_global(|store: &SettingsStore, cx| {
632 store.new_text_for_update(old_text, |content| update(content, cx))
633 })
634 })
635 }
636
637 pub fn import_vscode_settings(
638 &self,
639 fs: Arc<dyn Fs>,
640 vscode_settings: VsCodeSettings,
641 ) -> oneshot::Receiver<Result<()>> {
642 self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
643 cx.read_global(|store: &SettingsStore, _cx| {
644 store.get_vscode_edits(old_text, &vscode_settings)
645 })
646 })
647 }
648
649 pub fn get_all_files(&self) -> Vec<SettingsFile> {
650 let mut files = Vec::from_iter(
651 self.local_settings
652 .keys()
653 // rev because these are sorted by path, so highest precedence is last
654 .rev()
655 .cloned()
656 .map(SettingsFile::Project),
657 );
658
659 if self.server_settings.is_some() {
660 files.push(SettingsFile::Server);
661 }
662 // ignoring profiles
663 // ignoring os profiles
664 // ignoring release channel profiles
665 // ignoring global
666 // ignoring extension
667
668 if self.user_settings.is_some() {
669 files.push(SettingsFile::User);
670 }
671 files.push(SettingsFile::Default);
672 files
673 }
674
675 pub fn get_content_for_file(&self, file: SettingsFile) -> Option<&SettingsContent> {
676 match file {
677 SettingsFile::User => self
678 .user_settings
679 .as_ref()
680 .map(|settings| settings.content.as_ref()),
681 SettingsFile::Default => Some(self.default_settings.as_ref()),
682 SettingsFile::Server => self.server_settings.as_deref(),
683 SettingsFile::Project(ref key) => self.local_settings.get(key),
684 SettingsFile::Global => self.global_settings.as_deref(),
685 }
686 }
687
688 pub fn get_overrides_for_field<T>(
689 &self,
690 target_file: SettingsFile,
691 get: fn(&SettingsContent) -> &Option<T>,
692 ) -> Vec<SettingsFile> {
693 let all_files = self.get_all_files();
694 let mut found_file = false;
695 let mut overrides = Vec::new();
696
697 for file in all_files.into_iter().rev() {
698 if !found_file {
699 found_file = file == target_file;
700 continue;
701 }
702
703 if let SettingsFile::Project((wt_id, ref path)) = file
704 && let SettingsFile::Project((target_wt_id, ref target_path)) = target_file
705 && (wt_id != target_wt_id || !target_path.starts_with(path))
706 {
707 // if requesting value from a local file, don't return values from local files in different worktrees
708 continue;
709 }
710
711 let Some(content) = self.get_content_for_file(file.clone()) else {
712 continue;
713 };
714 if get(content).is_some() {
715 overrides.push(file);
716 }
717 }
718
719 overrides
720 }
721
722 /// Checks the given file, and files that the passed file overrides for the given field.
723 /// Returns the first file found that contains the value.
724 /// The value will only be None if no file contains the value.
725 /// I.e. if no file contains the value, returns `(File::Default, None)`
726 pub fn get_value_from_file<'a, T: 'a>(
727 &'a self,
728 target_file: SettingsFile,
729 pick: fn(&'a SettingsContent) -> Option<T>,
730 ) -> (SettingsFile, Option<T>) {
731 self.get_value_from_file_inner(target_file, pick, true)
732 }
733
734 /// Same as `Self::get_value_from_file` except that it does not include the current file.
735 /// Therefore it returns the value that was potentially overloaded by the target file.
736 pub fn get_value_up_to_file<'a, T: 'a>(
737 &'a self,
738 target_file: SettingsFile,
739 pick: fn(&'a SettingsContent) -> Option<T>,
740 ) -> (SettingsFile, Option<T>) {
741 self.get_value_from_file_inner(target_file, pick, false)
742 }
743
744 fn get_value_from_file_inner<'a, T: 'a>(
745 &'a self,
746 target_file: SettingsFile,
747 pick: fn(&'a SettingsContent) -> Option<T>,
748 include_target_file: bool,
749 ) -> (SettingsFile, Option<T>) {
750 // todo(settings_ui): Add a metadata field for overriding the "overrides" tag, for contextually different settings
751 // e.g. disable AI isn't overridden, or a vec that gets extended instead or some such
752
753 // todo(settings_ui) cache all files
754 let all_files = self.get_all_files();
755 let mut found_file = false;
756
757 for file in all_files.into_iter() {
758 if !found_file && file != SettingsFile::Default {
759 if file != target_file {
760 continue;
761 }
762 found_file = true;
763 if !include_target_file {
764 continue;
765 }
766 }
767
768 if let SettingsFile::Project((worktree_id, ref path)) = file
769 && let SettingsFile::Project((target_worktree_id, ref target_path)) = target_file
770 && (worktree_id != target_worktree_id || !target_path.starts_with(&path))
771 {
772 // if requesting value from a local file, don't return values from local files in different worktrees
773 continue;
774 }
775
776 let Some(content) = self.get_content_for_file(file.clone()) else {
777 continue;
778 };
779 if let Some(value) = pick(content) {
780 return (file, Some(value));
781 }
782 }
783
784 (SettingsFile::Default, None)
785 }
786
787 #[inline(always)]
788 fn parse_and_migrate_zed_settings<SettingsContentType: RootUserSettings>(
789 &mut self,
790 user_settings_content: &str,
791 file: SettingsFile,
792 ) -> (Option<SettingsContentType>, SettingsParseResult) {
793 let mut migration_status = MigrationStatus::NotNeeded;
794 let (settings, parse_status) = if user_settings_content.is_empty() {
795 SettingsContentType::parse_json("{}")
796 } else {
797 let migration_res = migrator::migrate_settings(user_settings_content);
798 migration_status = match &migration_res {
799 Ok(Some(_)) => MigrationStatus::Succeeded,
800 Ok(None) => MigrationStatus::NotNeeded,
801 Err(err) => MigrationStatus::Failed {
802 error: err.to_string(),
803 },
804 };
805 let content = match &migration_res {
806 Ok(Some(content)) => content,
807 Ok(None) => user_settings_content,
808 Err(_) => user_settings_content,
809 };
810 SettingsContentType::parse_json(content)
811 };
812
813 let result = SettingsParseResult {
814 parse_status,
815 migration_status,
816 };
817 self.file_errors.insert(file, result.clone());
818 return (settings, result);
819 }
820
821 pub fn error_for_file(&self, file: SettingsFile) -> Option<SettingsParseResult> {
822 self.file_errors
823 .get(&file)
824 .filter(|parse_result| parse_result.requires_user_action())
825 .cloned()
826 }
827}
828
829impl SettingsStore {
830 /// Updates the value of a setting in a JSON file, returning the new text
831 /// for that JSON file.
832 pub fn new_text_for_update(
833 &self,
834 old_text: String,
835 update: impl FnOnce(&mut SettingsContent),
836 ) -> Result<String> {
837 let edits = self.edits_for_update(&old_text, update)?;
838 let mut new_text = old_text;
839 for (range, replacement) in edits.into_iter() {
840 new_text.replace_range(range, &replacement);
841 }
842 Ok(new_text)
843 }
844
845 pub fn get_vscode_edits(&self, old_text: String, vscode: &VsCodeSettings) -> Result<String> {
846 self.new_text_for_update(old_text, |content| {
847 content.merge_from(&vscode.settings_content())
848 })
849 }
850
851 /// Updates the value of a setting in a JSON file, returning a list
852 /// of edits to apply to the JSON file.
853 pub fn edits_for_update(
854 &self,
855 text: &str,
856 update: impl FnOnce(&mut SettingsContent),
857 ) -> Result<Vec<(Range<usize>, String)>> {
858 let old_content = if text.trim().is_empty() {
859 UserSettingsContent::default()
860 } else {
861 let (old_content, parse_status) = UserSettingsContent::parse_json(text);
862 if let ParseStatus::Failed { error } = &parse_status {
863 log::error!("Failed to parse settings for update: {error}");
864 }
865 old_content
866 .context("Settings file could not be parsed. Fix syntax errors before updating.")?
867 };
868 let mut new_content = old_content.clone();
869 update(&mut new_content.content);
870
871 let old_value = serde_json::to_value(&old_content).unwrap();
872 let new_value = serde_json::to_value(new_content).unwrap();
873
874 let mut key_path = Vec::new();
875 let mut edits = Vec::new();
876 let tab_size = infer_json_indent_size(&text);
877 let mut text = text.to_string();
878 update_value_in_json_text(
879 &mut text,
880 &mut key_path,
881 tab_size,
882 &old_value,
883 &new_value,
884 &mut edits,
885 );
886 Ok(edits)
887 }
888
889 /// Mutates the default settings in place and recomputes all setting values.
890 pub fn update_default_settings(
891 &mut self,
892 cx: &mut App,
893 update: impl FnOnce(&mut SettingsContent),
894 ) {
895 let default_settings = Rc::make_mut(&mut self.default_settings);
896 update(default_settings);
897 self.recompute_values(None, cx);
898 }
899
900 /// Sets the default settings via a JSON string.
901 ///
902 /// The string should contain a JSON object with a default value for every setting.
903 pub fn set_default_settings(
904 &mut self,
905 default_settings_content: &str,
906 cx: &mut App,
907 ) -> Result<()> {
908 self.default_settings =
909 SettingsContent::parse_json_with_comments(default_settings_content)?.into();
910 self.recompute_values(None, cx);
911 Ok(())
912 }
913
914 /// Sets the user settings via a JSON string.
915 #[must_use]
916 pub fn set_user_settings(
917 &mut self,
918 user_settings_content: &str,
919 cx: &mut App,
920 ) -> SettingsParseResult {
921 if self.last_user_settings_content.as_deref() == Some(user_settings_content) {
922 return SettingsParseResult {
923 parse_status: ParseStatus::Unchanged,
924 migration_status: MigrationStatus::NotNeeded,
925 };
926 }
927 self.last_user_settings_content = Some(user_settings_content.to_string());
928
929 let (settings, parse_result) = self.parse_and_migrate_zed_settings::<UserSettingsContent>(
930 user_settings_content,
931 SettingsFile::User,
932 );
933
934 if let Some(settings) = settings {
935 self.user_settings = Some(settings);
936 self.recompute_values(None, cx);
937 }
938 return parse_result;
939 }
940
941 /// Sets the global settings via a JSON string.
942 #[must_use]
943 pub fn set_global_settings(
944 &mut self,
945 global_settings_content: &str,
946 cx: &mut App,
947 ) -> SettingsParseResult {
948 if self.last_global_settings_content.as_deref() == Some(global_settings_content) {
949 return SettingsParseResult {
950 parse_status: ParseStatus::Unchanged,
951 migration_status: MigrationStatus::NotNeeded,
952 };
953 }
954 self.last_global_settings_content = Some(global_settings_content.to_string());
955
956 let (settings, parse_result) = self.parse_and_migrate_zed_settings::<SettingsContent>(
957 global_settings_content,
958 SettingsFile::Global,
959 );
960
961 if let Some(settings) = settings {
962 self.global_settings = Some(Box::new(settings));
963 self.recompute_values(None, cx);
964 }
965 return parse_result;
966 }
967
968 pub fn set_server_settings(
969 &mut self,
970 server_settings_content: &str,
971 cx: &mut App,
972 ) -> Result<()> {
973 let settings = if server_settings_content.is_empty() {
974 None
975 } else {
976 Option::<SettingsContent>::parse_json_with_comments(server_settings_content)?
977 };
978
979 // Rewrite the server settings into a content type
980 self.server_settings = settings.map(|settings| Box::new(settings));
981
982 self.recompute_values(None, cx);
983 Ok(())
984 }
985
986 /// Sets language-specific semantic token rules.
987 ///
988 /// These rules are registered by language modules (e.g. the Rust language module)
989 /// or by third-party extensions (via `semantic_token_rules.json` in their language
990 /// directories). They are stored separately from the global rules and are only
991 /// applied to buffers of the matching language by the `SemanticTokenStylizer`.
992 ///
993 /// This triggers a settings recomputation so that observers (e.g. `LspStore`)
994 /// are notified and can invalidate cached stylizers.
995 pub fn set_language_semantic_token_rules(
996 &mut self,
997 language: SharedString,
998 rules: SemanticTokenRules,
999 cx: &mut App,
1000 ) {
1001 self.language_semantic_token_rules.insert(language, rules);
1002 self.recompute_values(None, cx);
1003 }
1004
1005 /// Removes language-specific semantic token rules for the given language.
1006 ///
1007 /// This should be called when an extension that registered rules for a language
1008 /// is unloaded. Triggers a settings recomputation so that observers (e.g.
1009 /// `LspStore`) are notified and can invalidate cached stylizers.
1010 pub fn remove_language_semantic_token_rules(&mut self, language: &str, cx: &mut App) {
1011 self.language_semantic_token_rules.remove(language);
1012 self.recompute_values(None, cx);
1013 }
1014
1015 /// Returns the language-specific semantic token rules for the given language,
1016 /// if any have been registered.
1017 pub fn language_semantic_token_rules(&self, language: &str) -> Option<&SemanticTokenRules> {
1018 self.language_semantic_token_rules.get(language)
1019 }
1020
1021 /// Add or remove a set of local settings via a JSON string.
1022 pub fn set_local_settings(
1023 &mut self,
1024 root_id: WorktreeId,
1025 path: LocalSettingsPath,
1026 kind: LocalSettingsKind,
1027 settings_content: Option<&str>,
1028 cx: &mut App,
1029 ) -> std::result::Result<(), InvalidSettingsError> {
1030 let content = settings_content
1031 .map(|content| content.trim())
1032 .filter(|content| !content.is_empty());
1033 let mut zed_settings_changed = false;
1034 match (path.clone(), kind, content) {
1035 (LocalSettingsPath::InWorktree(directory_path), LocalSettingsKind::Tasks, _) => {
1036 return Err(InvalidSettingsError::Tasks {
1037 message: "Attempted to submit tasks into the settings store".to_string(),
1038 path: directory_path
1039 .join(RelPath::unix(task_file_name()).unwrap())
1040 .as_std_path()
1041 .to_path_buf(),
1042 });
1043 }
1044 (LocalSettingsPath::InWorktree(directory_path), LocalSettingsKind::Debug, _) => {
1045 return Err(InvalidSettingsError::Debug {
1046 message: "Attempted to submit debugger config into the settings store"
1047 .to_string(),
1048 path: directory_path
1049 .join(RelPath::unix(task_file_name()).unwrap())
1050 .as_std_path()
1051 .to_path_buf(),
1052 });
1053 }
1054 (LocalSettingsPath::InWorktree(directory_path), LocalSettingsKind::Settings, None) => {
1055 zed_settings_changed = self
1056 .local_settings
1057 .remove(&(root_id, directory_path.clone()))
1058 .is_some();
1059 self.file_errors
1060 .remove(&SettingsFile::Project((root_id, directory_path)));
1061 }
1062 (
1063 LocalSettingsPath::InWorktree(directory_path),
1064 LocalSettingsKind::Settings,
1065 Some(settings_contents),
1066 ) => {
1067 let (new_settings, parse_result) = self
1068 .parse_and_migrate_zed_settings::<ProjectSettingsContent>(
1069 settings_contents,
1070 SettingsFile::Project((root_id, directory_path.clone())),
1071 );
1072 match parse_result.parse_status {
1073 ParseStatus::Success => Ok(()),
1074 ParseStatus::Unchanged => Ok(()),
1075 ParseStatus::Failed { error } => Err(InvalidSettingsError::LocalSettings {
1076 path: directory_path.join(local_settings_file_relative_path()),
1077 message: error,
1078 }),
1079 }?;
1080 if let Some(new_settings) = new_settings {
1081 match self.local_settings.entry((root_id, directory_path)) {
1082 btree_map::Entry::Vacant(v) => {
1083 v.insert(SettingsContent {
1084 project: new_settings,
1085 ..Default::default()
1086 });
1087 zed_settings_changed = true;
1088 }
1089 btree_map::Entry::Occupied(mut o) => {
1090 if &o.get().project != &new_settings {
1091 o.insert(SettingsContent {
1092 project: new_settings,
1093 ..Default::default()
1094 });
1095 zed_settings_changed = true;
1096 }
1097 }
1098 }
1099 }
1100 }
1101 (directory_path, LocalSettingsKind::Editorconfig, editorconfig_contents) => {
1102 self.editorconfig_store.update(cx, |store, _| {
1103 store.set_configs(root_id, directory_path, editorconfig_contents)
1104 })?;
1105 }
1106 (LocalSettingsPath::OutsideWorktree(path), kind, _) => {
1107 log::error!(
1108 "OutsideWorktree path {:?} with kind {:?} is only supported by editorconfig",
1109 path,
1110 kind
1111 );
1112 return Ok(());
1113 }
1114 }
1115 if let LocalSettingsPath::InWorktree(directory_path) = &path {
1116 if zed_settings_changed {
1117 self.recompute_values(Some((root_id, &directory_path)), cx);
1118 }
1119 }
1120 Ok(())
1121 }
1122
1123 pub fn set_extension_settings(
1124 &mut self,
1125 content: ExtensionsSettingsContent,
1126 cx: &mut App,
1127 ) -> Result<()> {
1128 self.extension_settings = Some(Box::new(SettingsContent {
1129 project: ProjectSettingsContent {
1130 all_languages: content.all_languages,
1131 ..Default::default()
1132 },
1133 ..Default::default()
1134 }));
1135 self.recompute_values(None, cx);
1136 Ok(())
1137 }
1138
1139 /// Add or remove a set of local settings via a JSON string.
1140 pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut App) -> Result<()> {
1141 self.local_settings
1142 .retain(|(worktree_id, _), _| worktree_id != &root_id);
1143
1144 self.editorconfig_store
1145 .update(cx, |store, _cx| store.remove_for_worktree(root_id));
1146
1147 for setting_value in self.setting_values.values_mut() {
1148 setting_value.clear_local_values(root_id);
1149 }
1150 self.recompute_values(Some((root_id, RelPath::empty())), cx);
1151 Ok(())
1152 }
1153
1154 pub fn local_settings(
1155 &self,
1156 root_id: WorktreeId,
1157 ) -> impl '_ + Iterator<Item = (Arc<RelPath>, &ProjectSettingsContent)> {
1158 self.local_settings
1159 .range(
1160 (root_id, RelPath::empty().into())
1161 ..(
1162 WorktreeId::from_usize(root_id.to_usize() + 1),
1163 RelPath::empty().into(),
1164 ),
1165 )
1166 .map(|((_, path), content)| (path.clone(), &content.project))
1167 }
1168
1169 /// Configures common schema replacements shared between user and project
1170 /// settings schemas.
1171 ///
1172 /// This sets up language-specific settings and LSP adapter settings that
1173 /// are valid in both user and project settings.
1174 fn configure_schema_generator(
1175 generator: &mut schemars::SchemaGenerator,
1176 params: &SettingsJsonSchemaParams,
1177 ) {
1178 let language_settings_content_ref = generator
1179 .subschema_for::<LanguageSettingsContent>()
1180 .to_value();
1181
1182 if !params.language_names.is_empty() {
1183 replace_subschema::<LanguageToSettingsMap>(generator, || {
1184 json_schema!({
1185 "type": "object",
1186 "errorMessage": "No language with this name is installed.",
1187 "properties": params.language_names.iter().map(|name| (name.clone(), language_settings_content_ref.clone())).collect::<serde_json::Map<_, _>>()
1188 })
1189 });
1190 }
1191
1192 generator.subschema_for::<LspSettings>();
1193
1194 let lsp_settings_definition = generator
1195 .definitions()
1196 .get("LspSettings")
1197 .expect("LspSettings should be defined")
1198 .clone();
1199
1200 if !params.lsp_adapter_names.is_empty() {
1201 replace_subschema::<LspSettingsMap>(generator, || {
1202 let mut lsp_properties = serde_json::Map::new();
1203
1204 for adapter_name in params.lsp_adapter_names {
1205 let mut base_lsp_settings = lsp_settings_definition
1206 .as_object()
1207 .expect("LspSettings should be an object")
1208 .clone();
1209
1210 if let Some(properties) = base_lsp_settings.get_mut("properties") {
1211 if let Some(properties_object) = properties.as_object_mut() {
1212 properties_object.insert(
1213 "initialization_options".to_string(),
1214 serde_json::json!({
1215 "$ref": format!("{LSP_SETTINGS_SCHEMA_URL_PREFIX}{adapter_name}/initialization_options")
1216 }),
1217 );
1218 properties_object.insert(
1219 "settings".to_string(),
1220 serde_json::json!({
1221 "$ref": format!("{LSP_SETTINGS_SCHEMA_URL_PREFIX}{adapter_name}/settings")
1222 }),
1223 );
1224 }
1225 }
1226
1227 lsp_properties.insert(
1228 adapter_name.clone(),
1229 serde_json::Value::Object(base_lsp_settings),
1230 );
1231 }
1232
1233 json_schema!({
1234 "type": "object",
1235 "properties": lsp_properties
1236 })
1237 });
1238 }
1239 }
1240
1241 pub fn json_schema(params: &SettingsJsonSchemaParams) -> Value {
1242 let mut generator = schemars::generate::SchemaSettings::draft2019_09()
1243 .with_transform(DefaultDenyUnknownFields)
1244 .with_transform(AllowTrailingCommas)
1245 .into_generator();
1246
1247 UserSettingsContent::json_schema(&mut generator);
1248 Self::configure_schema_generator(&mut generator, params);
1249
1250 if !params.font_names.is_empty() {
1251 replace_subschema::<FontFamilyName>(&mut generator, || {
1252 json_schema!({
1253 "type": "string",
1254 "enum": params.font_names,
1255 })
1256 });
1257 }
1258
1259 if !params.theme_names.is_empty() {
1260 replace_subschema::<ThemeName>(&mut generator, || {
1261 json_schema!({
1262 "type": "string",
1263 "enum": params.theme_names,
1264 })
1265 });
1266 }
1267
1268 if !params.icon_theme_names.is_empty() {
1269 replace_subschema::<IconThemeName>(&mut generator, || {
1270 json_schema!({
1271 "type": "string",
1272 "enum": params.icon_theme_names,
1273 })
1274 });
1275 }
1276
1277 if !params.action_names.is_empty() {
1278 replace_subschema::<ActionName>(&mut generator, || {
1279 ActionName::build_schema(
1280 params.action_names.iter().copied(),
1281 params.action_documentation,
1282 params.deprecations,
1283 params.deprecation_messages,
1284 )
1285 });
1286 }
1287
1288 generator
1289 .root_schema_for::<UserSettingsContent>()
1290 .to_value()
1291 }
1292
1293 /// Generate JSON schema for project settings, including only settings valid
1294 /// for project-level configurations.
1295 pub fn project_json_schema(params: &SettingsJsonSchemaParams) -> Value {
1296 let mut generator = schemars::generate::SchemaSettings::draft2019_09()
1297 .with_transform(DefaultDenyUnknownFields)
1298 .with_transform(AllowTrailingCommas)
1299 .into_generator();
1300
1301 ProjectSettingsContent::json_schema(&mut generator);
1302 Self::configure_schema_generator(&mut generator, params);
1303
1304 generator
1305 .root_schema_for::<ProjectSettingsContent>()
1306 .to_value()
1307 }
1308
1309 fn recompute_values(
1310 &mut self,
1311 changed_local_path: Option<(WorktreeId, &RelPath)>,
1312 cx: &mut App,
1313 ) {
1314 // Reload the global and local values for every setting.
1315 let mut project_settings_stack = Vec::<SettingsContent>::new();
1316 let mut paths_stack = Vec::<Option<(WorktreeId, &RelPath)>>::new();
1317
1318 if changed_local_path.is_none() {
1319 let mut merged = self.default_settings.as_ref().clone();
1320 merged.merge_from_option(self.extension_settings.as_deref());
1321 merged.merge_from_option(self.global_settings.as_deref());
1322 if let Some(user_settings) = self.user_settings.as_ref() {
1323 let active_profile = user_settings.for_profile(cx);
1324 let should_merge_user_settings =
1325 active_profile.is_none_or(|profile| profile.base == ProfileBase::User);
1326
1327 if should_merge_user_settings {
1328 merged.merge_from(&user_settings.content);
1329 merged.merge_from_option(user_settings.for_release_channel());
1330 merged.merge_from_option(user_settings.for_os());
1331 }
1332
1333 if let Some(profile) = active_profile {
1334 merged.merge_from(&profile.settings);
1335 }
1336 }
1337 merged.merge_from_option(self.server_settings.as_deref());
1338
1339 // Merge `disable_ai` from all project/local settings into the global value.
1340 // Since `SaturatingBool` uses OR logic, if any project has `disable_ai: true`,
1341 // the global value will be true. This allows project-level `disable_ai` to
1342 // affect the global setting used by UI elements without file context.
1343 for local_settings in self.local_settings.values() {
1344 merged
1345 .project
1346 .disable_ai
1347 .merge_from(&local_settings.project.disable_ai);
1348 }
1349
1350 self.merged_settings = Rc::new(merged);
1351
1352 for setting_value in self.setting_values.values_mut() {
1353 let value = setting_value.from_settings(&self.merged_settings);
1354 setting_value.set_global_value(value);
1355 }
1356 } else {
1357 // When only a local path changed, we still need to recompute the global
1358 // `disable_ai` value since it depends on all local settings.
1359 let mut merged = (*self.merged_settings).clone();
1360 // Reset disable_ai to compute fresh from base settings
1361 merged.project.disable_ai = self.default_settings.project.disable_ai;
1362 if let Some(global) = &self.global_settings {
1363 merged
1364 .project
1365 .disable_ai
1366 .merge_from(&global.project.disable_ai);
1367 }
1368 if let Some(user) = &self.user_settings {
1369 merged
1370 .project
1371 .disable_ai
1372 .merge_from(&user.content.project.disable_ai);
1373 }
1374 if let Some(server) = &self.server_settings {
1375 merged
1376 .project
1377 .disable_ai
1378 .merge_from(&server.project.disable_ai);
1379 }
1380 for local_settings in self.local_settings.values() {
1381 merged
1382 .project
1383 .disable_ai
1384 .merge_from(&local_settings.project.disable_ai);
1385 }
1386 self.merged_settings = Rc::new(merged);
1387
1388 for setting_value in self.setting_values.values_mut() {
1389 let value = setting_value.from_settings(&self.merged_settings);
1390 setting_value.set_global_value(value);
1391 }
1392 }
1393
1394 for ((root_id, directory_path), local_settings) in &self.local_settings {
1395 // Build a stack of all of the local values for that setting.
1396 while let Some(prev_entry) = paths_stack.last() {
1397 if let Some((prev_root_id, prev_path)) = prev_entry
1398 && (root_id != prev_root_id || !directory_path.starts_with(prev_path))
1399 {
1400 paths_stack.pop();
1401 project_settings_stack.pop();
1402 continue;
1403 }
1404 break;
1405 }
1406
1407 paths_stack.push(Some((*root_id, directory_path.as_ref())));
1408 let mut merged_local_settings = if let Some(deepest) = project_settings_stack.last() {
1409 (*deepest).clone()
1410 } else {
1411 self.merged_settings.as_ref().clone()
1412 };
1413 merged_local_settings.merge_from(local_settings);
1414
1415 project_settings_stack.push(merged_local_settings);
1416
1417 // If a local settings file changed, then avoid recomputing local
1418 // settings for any path outside of that directory.
1419 if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| {
1420 *root_id != changed_root_id || !directory_path.starts_with(changed_local_path)
1421 }) {
1422 continue;
1423 }
1424
1425 for setting_value in self.setting_values.values_mut() {
1426 let value = setting_value.from_settings(&project_settings_stack.last().unwrap());
1427 setting_value.set_local_value(*root_id, directory_path.clone(), value);
1428 }
1429 }
1430 }
1431}
1432
1433/// The result of parsing settings, including any migration attempts
1434#[derive(Debug, Clone, PartialEq, Eq)]
1435pub struct SettingsParseResult {
1436 /// The result of parsing the settings file (possibly after migration)
1437 pub parse_status: ParseStatus,
1438 /// The result of attempting to migrate the settings file
1439 pub migration_status: MigrationStatus,
1440}
1441
1442#[derive(Debug, Clone, PartialEq, Eq)]
1443pub enum MigrationStatus {
1444 /// No migration was needed - settings are up to date
1445 NotNeeded,
1446 /// Settings were automatically migrated in memory, but the file needs to be updated
1447 Succeeded,
1448 /// Migration was attempted but failed. Original settings were parsed instead.
1449 Failed { error: String },
1450}
1451
1452impl Default for SettingsParseResult {
1453 fn default() -> Self {
1454 Self {
1455 parse_status: ParseStatus::Success,
1456 migration_status: MigrationStatus::NotNeeded,
1457 }
1458 }
1459}
1460
1461impl SettingsParseResult {
1462 pub fn unwrap(self) -> bool {
1463 self.result().unwrap()
1464 }
1465
1466 pub fn expect(self, message: &str) -> bool {
1467 self.result().expect(message)
1468 }
1469
1470 /// Formats the ParseResult as a Result type. This is a lossy conversion
1471 pub fn result(self) -> Result<bool> {
1472 let migration_result = match self.migration_status {
1473 MigrationStatus::NotNeeded => Ok(false),
1474 MigrationStatus::Succeeded => Ok(true),
1475 MigrationStatus::Failed { error } => {
1476 Err(anyhow::format_err!(error)).context("Failed to migrate settings")
1477 }
1478 };
1479
1480 let parse_result = match self.parse_status {
1481 ParseStatus::Success | ParseStatus::Unchanged => Ok(()),
1482 ParseStatus::Failed { error } => {
1483 Err(anyhow::format_err!(error)).context("Failed to parse settings")
1484 }
1485 };
1486
1487 match (migration_result, parse_result) {
1488 (migration_result @ Ok(_), Ok(())) => migration_result,
1489 (Err(migration_err), Ok(())) => Err(migration_err),
1490 (_, Err(parse_err)) => Err(parse_err),
1491 }
1492 }
1493
1494 /// Returns true if there were any errors migrating and parsing the settings content or if migration was required but there were no errors
1495 pub fn requires_user_action(&self) -> bool {
1496 matches!(self.parse_status, ParseStatus::Failed { .. })
1497 || matches!(
1498 self.migration_status,
1499 MigrationStatus::Succeeded | MigrationStatus::Failed { .. }
1500 )
1501 }
1502
1503 pub fn ok(self) -> Option<bool> {
1504 self.result().ok()
1505 }
1506
1507 pub fn parse_error(&self) -> Option<String> {
1508 match &self.parse_status {
1509 ParseStatus::Failed { error } => Some(error.clone()),
1510 ParseStatus::Success | ParseStatus::Unchanged => None,
1511 }
1512 }
1513}
1514
1515#[derive(Debug, Clone, PartialEq)]
1516pub enum InvalidSettingsError {
1517 LocalSettings {
1518 path: Arc<RelPath>,
1519 message: String,
1520 },
1521 UserSettings {
1522 message: String,
1523 },
1524 ServerSettings {
1525 message: String,
1526 },
1527 DefaultSettings {
1528 message: String,
1529 },
1530 Editorconfig {
1531 path: LocalSettingsPath,
1532 message: String,
1533 },
1534 Tasks {
1535 path: PathBuf,
1536 message: String,
1537 },
1538 Debug {
1539 path: PathBuf,
1540 message: String,
1541 },
1542}
1543
1544impl std::fmt::Display for InvalidSettingsError {
1545 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1546 match self {
1547 InvalidSettingsError::LocalSettings { message, .. }
1548 | InvalidSettingsError::UserSettings { message }
1549 | InvalidSettingsError::ServerSettings { message }
1550 | InvalidSettingsError::DefaultSettings { message }
1551 | InvalidSettingsError::Tasks { message, .. }
1552 | InvalidSettingsError::Editorconfig { message, .. }
1553 | InvalidSettingsError::Debug { message, .. } => write!(f, "{message}"),
1554 }
1555 }
1556}
1557impl std::error::Error for InvalidSettingsError {}
1558
1559impl Debug for SettingsStore {
1560 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1561 f.debug_struct("SettingsStore")
1562 .field(
1563 "types",
1564 &self
1565 .setting_values
1566 .values()
1567 .map(|value| value.setting_type_name())
1568 .collect::<Vec<_>>(),
1569 )
1570 .field("default_settings", &self.default_settings)
1571 .field("user_settings", &self.user_settings)
1572 .field("local_settings", &self.local_settings)
1573 .finish_non_exhaustive()
1574 }
1575}
1576
1577impl<T: Settings> AnySettingValue for SettingValue<T> {
1578 fn from_settings(&self, s: &SettingsContent) -> Box<dyn Any> {
1579 Box::new(T::from_settings(s)) as _
1580 }
1581
1582 fn setting_type_name(&self) -> &'static str {
1583 type_name::<T>()
1584 }
1585
1586 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<RelPath>, &dyn Any)> {
1587 self.local_values
1588 .iter()
1589 .map(|(id, path, value)| (*id, path.clone(), value as _))
1590 .collect()
1591 }
1592
1593 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
1594 if let Some(SettingsLocation { worktree_id, path }) = path {
1595 for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
1596 if worktree_id == *settings_root_id && path.starts_with(settings_path) {
1597 return value;
1598 }
1599 }
1600 }
1601
1602 self.global_value
1603 .as_ref()
1604 .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
1605 }
1606
1607 fn set_global_value(&mut self, value: Box<dyn Any>) {
1608 self.global_value = Some(*value.downcast().unwrap());
1609 }
1610
1611 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<RelPath>, value: Box<dyn Any>) {
1612 let value = *value.downcast().unwrap();
1613 match self
1614 .local_values
1615 .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
1616 {
1617 Ok(ix) => self.local_values[ix].2 = value,
1618 Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
1619 }
1620 }
1621
1622 fn clear_local_values(&mut self, root_id: WorktreeId) {
1623 self.local_values
1624 .retain(|(worktree_id, _, _)| *worktree_id != root_id);
1625 }
1626}
1627
1628#[cfg(test)]
1629mod tests {
1630 use std::{cell::RefCell, num::NonZeroU32};
1631
1632 use crate::{
1633 ClosePosition, ItemSettingsContent, VsCodeSettingsSource, default_settings,
1634 settings_content::LanguageSettingsContent, test_settings,
1635 };
1636
1637 use super::*;
1638 use fs::FakeFs;
1639 use unindent::Unindent;
1640 use util::rel_path::rel_path;
1641
1642 #[derive(Debug, PartialEq)]
1643 struct AutoUpdateSetting {
1644 auto_update: bool,
1645 }
1646
1647 impl Settings for AutoUpdateSetting {
1648 fn from_settings(content: &SettingsContent) -> Self {
1649 AutoUpdateSetting {
1650 auto_update: content.auto_update.unwrap(),
1651 }
1652 }
1653 }
1654
1655 #[derive(Debug, PartialEq)]
1656 struct ItemSettings {
1657 close_position: ClosePosition,
1658 git_status: bool,
1659 }
1660
1661 impl Settings for ItemSettings {
1662 fn from_settings(content: &SettingsContent) -> Self {
1663 let content = content.tabs.clone().unwrap();
1664 ItemSettings {
1665 close_position: content.close_position.unwrap(),
1666 git_status: content.git_status.unwrap(),
1667 }
1668 }
1669 }
1670
1671 #[derive(Debug, PartialEq)]
1672 struct DefaultLanguageSettings {
1673 tab_size: NonZeroU32,
1674 preferred_line_length: u32,
1675 }
1676
1677 impl Settings for DefaultLanguageSettings {
1678 fn from_settings(content: &SettingsContent) -> Self {
1679 let content = &content.project.all_languages.defaults;
1680 DefaultLanguageSettings {
1681 tab_size: content.tab_size.unwrap(),
1682 preferred_line_length: content.preferred_line_length.unwrap(),
1683 }
1684 }
1685 }
1686
1687 #[derive(Debug, PartialEq)]
1688 struct ThemeSettings {
1689 buffer_font_family: FontFamilyName,
1690 buffer_font_fallbacks: Vec<FontFamilyName>,
1691 }
1692
1693 impl Settings for ThemeSettings {
1694 fn from_settings(content: &SettingsContent) -> Self {
1695 let content = content.theme.clone();
1696 ThemeSettings {
1697 buffer_font_family: content.buffer_font_family.unwrap(),
1698 buffer_font_fallbacks: content.buffer_font_fallbacks.unwrap(),
1699 }
1700 }
1701 }
1702
1703 #[gpui::test]
1704 async fn test_update_settings_file_updates_store_before_watcher(cx: &mut gpui::TestAppContext) {
1705 let fs = FakeFs::new(cx.background_executor.clone());
1706 fs.create_dir(paths::settings_file().parent().unwrap())
1707 .await
1708 .unwrap();
1709 fs.insert_file(
1710 paths::settings_file(),
1711 r#"{ "tabs": { "close_position": "right" } }"#.as_bytes().to_vec(),
1712 )
1713 .await;
1714 fs.pause_events();
1715 cx.run_until_parked();
1716
1717 let success = SettingsParseResult {
1718 parse_status: ParseStatus::Success,
1719 migration_status: MigrationStatus::NotNeeded,
1720 };
1721 let parse_results = Rc::new(RefCell::new(Vec::new()));
1722
1723 cx.update(|cx| {
1724 let mut store = SettingsStore::new(cx, &default_settings());
1725 store.register_setting::<ItemSettings>();
1726 store.watch_settings_files(fs.clone(), cx, {
1727 let parse_results = parse_results.clone();
1728 move |_, result, _| {
1729 parse_results.borrow_mut().push(result);
1730 }
1731 });
1732 cx.set_global(store);
1733 });
1734
1735 // Calling watch_settings_files loads user and global settings.
1736 assert_eq!(
1737 parse_results.borrow().as_slice(),
1738 &[success.clone(), success.clone()]
1739 );
1740 cx.update(|cx| {
1741 assert_eq!(
1742 cx.global::<SettingsStore>()
1743 .get::<ItemSettings>(None)
1744 .close_position,
1745 ClosePosition::Right
1746 );
1747 });
1748
1749 // Updating the settings file returns a channel that resolves once the settings are loaded.
1750 let rx = cx.update(|cx| {
1751 cx.global::<SettingsStore>()
1752 .update_settings_file_with_completion(fs.clone(), move |settings, _| {
1753 settings.tabs.get_or_insert_default().close_position =
1754 Some(ClosePosition::Left);
1755 })
1756 });
1757 assert!(rx.await.unwrap().is_ok());
1758 assert_eq!(
1759 parse_results.borrow().as_slice(),
1760 &[success.clone(), success.clone()]
1761 );
1762 cx.update(|cx| {
1763 assert_eq!(
1764 cx.global::<SettingsStore>()
1765 .get::<ItemSettings>(None)
1766 .close_position,
1767 ClosePosition::Left
1768 );
1769 });
1770
1771 // When the FS event occurs, the settings are recognized as unchanged.
1772 fs.flush_events(100);
1773 cx.run_until_parked();
1774 assert_eq!(
1775 parse_results.borrow().as_slice(),
1776 &[
1777 success.clone(),
1778 success.clone(),
1779 SettingsParseResult {
1780 parse_status: ParseStatus::Unchanged,
1781 migration_status: MigrationStatus::NotNeeded
1782 }
1783 ]
1784 );
1785 }
1786
1787 #[gpui::test]
1788 fn test_settings_store_basic(cx: &mut App) {
1789 let mut store = SettingsStore::new(cx, &default_settings());
1790 store.register_setting::<AutoUpdateSetting>();
1791 store.register_setting::<ItemSettings>();
1792 store.register_setting::<DefaultLanguageSettings>();
1793
1794 assert_eq!(
1795 store.get::<AutoUpdateSetting>(None),
1796 &AutoUpdateSetting { auto_update: true }
1797 );
1798 assert_eq!(
1799 store.get::<ItemSettings>(None).close_position,
1800 ClosePosition::Right
1801 );
1802
1803 store
1804 .set_user_settings(
1805 r#"{
1806 "auto_update": false,
1807 "tabs": {
1808 "close_position": "left"
1809 }
1810 }"#,
1811 cx,
1812 )
1813 .unwrap();
1814
1815 assert_eq!(
1816 store.get::<AutoUpdateSetting>(None),
1817 &AutoUpdateSetting { auto_update: false }
1818 );
1819 assert_eq!(
1820 store.get::<ItemSettings>(None).close_position,
1821 ClosePosition::Left
1822 );
1823
1824 store
1825 .set_local_settings(
1826 WorktreeId::from_usize(1),
1827 LocalSettingsPath::InWorktree(rel_path("root1").into()),
1828 LocalSettingsKind::Settings,
1829 Some(r#"{ "tab_size": 5 }"#),
1830 cx,
1831 )
1832 .unwrap();
1833 store
1834 .set_local_settings(
1835 WorktreeId::from_usize(1),
1836 LocalSettingsPath::InWorktree(rel_path("root1/subdir").into()),
1837 LocalSettingsKind::Settings,
1838 Some(r#"{ "preferred_line_length": 50 }"#),
1839 cx,
1840 )
1841 .unwrap();
1842
1843 store
1844 .set_local_settings(
1845 WorktreeId::from_usize(1),
1846 LocalSettingsPath::InWorktree(rel_path("root2").into()),
1847 LocalSettingsKind::Settings,
1848 Some(r#"{ "tab_size": 9, "auto_update": true}"#),
1849 cx,
1850 )
1851 .unwrap();
1852
1853 assert_eq!(
1854 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1855 worktree_id: WorktreeId::from_usize(1),
1856 path: rel_path("root1/something"),
1857 })),
1858 &DefaultLanguageSettings {
1859 preferred_line_length: 80,
1860 tab_size: 5.try_into().unwrap(),
1861 }
1862 );
1863 assert_eq!(
1864 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1865 worktree_id: WorktreeId::from_usize(1),
1866 path: rel_path("root1/subdir/something"),
1867 })),
1868 &DefaultLanguageSettings {
1869 preferred_line_length: 50,
1870 tab_size: 5.try_into().unwrap(),
1871 }
1872 );
1873 assert_eq!(
1874 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1875 worktree_id: WorktreeId::from_usize(1),
1876 path: rel_path("root2/something"),
1877 })),
1878 &DefaultLanguageSettings {
1879 preferred_line_length: 80,
1880 tab_size: 9.try_into().unwrap(),
1881 }
1882 );
1883 assert_eq!(
1884 store.get::<AutoUpdateSetting>(Some(SettingsLocation {
1885 worktree_id: WorktreeId::from_usize(1),
1886 path: rel_path("root2/something")
1887 })),
1888 &AutoUpdateSetting { auto_update: false }
1889 );
1890 }
1891
1892 #[gpui::test]
1893 fn test_setting_store_assign_json_before_register(cx: &mut App) {
1894 let mut store = SettingsStore::new(cx, &test_settings());
1895 store
1896 .set_user_settings(r#"{ "auto_update": false }"#, cx)
1897 .unwrap();
1898 store.register_setting::<AutoUpdateSetting>();
1899
1900 assert_eq!(
1901 store.get::<AutoUpdateSetting>(None),
1902 &AutoUpdateSetting { auto_update: false }
1903 );
1904 }
1905
1906 #[track_caller]
1907 fn check_settings_update(
1908 store: &mut SettingsStore,
1909 old_json: String,
1910 update: fn(&mut SettingsContent),
1911 expected_new_json: String,
1912 cx: &mut App,
1913 ) {
1914 store.set_user_settings(&old_json, cx).ok();
1915 let edits = store.edits_for_update(&old_json, update).unwrap();
1916 let mut new_json = old_json;
1917 for (range, replacement) in edits.into_iter() {
1918 new_json.replace_range(range, &replacement);
1919 }
1920 pretty_assertions::assert_eq!(new_json, expected_new_json);
1921 }
1922
1923 #[gpui::test]
1924 fn test_setting_store_update(cx: &mut App) {
1925 let mut store = SettingsStore::new(cx, &test_settings());
1926
1927 // entries added and updated
1928 check_settings_update(
1929 &mut store,
1930 r#"{
1931 "languages": {
1932 "JSON": {
1933 "auto_indent": "syntax_aware"
1934 }
1935 }
1936 }"#
1937 .unindent(),
1938 |settings| {
1939 settings
1940 .languages_mut()
1941 .get_mut("JSON")
1942 .unwrap()
1943 .auto_indent = Some(crate::AutoIndentMode::None);
1944
1945 settings.languages_mut().insert(
1946 "Rust".into(),
1947 LanguageSettingsContent {
1948 auto_indent: Some(crate::AutoIndentMode::SyntaxAware),
1949 ..Default::default()
1950 },
1951 );
1952 },
1953 r#"{
1954 "languages": {
1955 "Rust": {
1956 "auto_indent": "syntax_aware"
1957 },
1958 "JSON": {
1959 "auto_indent": "none"
1960 }
1961 }
1962 }"#
1963 .unindent(),
1964 cx,
1965 );
1966
1967 // entries removed
1968 check_settings_update(
1969 &mut store,
1970 r#"{
1971 "languages": {
1972 "Rust": {
1973 "language_setting_2": true
1974 },
1975 "JSON": {
1976 "language_setting_1": false
1977 }
1978 }
1979 }"#
1980 .unindent(),
1981 |settings| {
1982 settings.languages_mut().remove("JSON").unwrap();
1983 },
1984 r#"{
1985 "languages": {
1986 "Rust": {
1987 "language_setting_2": true
1988 }
1989 }
1990 }"#
1991 .unindent(),
1992 cx,
1993 );
1994
1995 check_settings_update(
1996 &mut store,
1997 r#"{
1998 "languages": {
1999 "Rust": {
2000 "language_setting_2": true
2001 },
2002 "JSON": {
2003 "language_setting_1": false
2004 }
2005 }
2006 }"#
2007 .unindent(),
2008 |settings| {
2009 settings.languages_mut().remove("Rust").unwrap();
2010 },
2011 r#"{
2012 "languages": {
2013 "JSON": {
2014 "language_setting_1": false
2015 }
2016 }
2017 }"#
2018 .unindent(),
2019 cx,
2020 );
2021
2022 // weird formatting
2023 check_settings_update(
2024 &mut store,
2025 r#"{
2026 "tabs": { "close_position": "left", "name": "Max" }
2027 }"#
2028 .unindent(),
2029 |settings| {
2030 settings.tabs.as_mut().unwrap().close_position = Some(ClosePosition::Left);
2031 },
2032 r#"{
2033 "tabs": { "close_position": "left", "name": "Max" }
2034 }"#
2035 .unindent(),
2036 cx,
2037 );
2038
2039 // single-line formatting, other keys
2040 check_settings_update(
2041 &mut store,
2042 r#"{ "one": 1, "two": 2 }"#.to_owned(),
2043 |settings| settings.auto_update = Some(true),
2044 r#"{ "auto_update": true, "one": 1, "two": 2 }"#.to_owned(),
2045 cx,
2046 );
2047
2048 // empty object
2049 check_settings_update(
2050 &mut store,
2051 r#"{
2052 "tabs": {}
2053 }"#
2054 .unindent(),
2055 |settings| settings.tabs.as_mut().unwrap().close_position = Some(ClosePosition::Left),
2056 r#"{
2057 "tabs": {
2058 "close_position": "left"
2059 }
2060 }"#
2061 .unindent(),
2062 cx,
2063 );
2064
2065 // no content
2066 check_settings_update(
2067 &mut store,
2068 r#""#.unindent(),
2069 |settings| {
2070 settings.tabs = Some(ItemSettingsContent {
2071 git_status: Some(true),
2072 ..Default::default()
2073 })
2074 },
2075 r#"{
2076 "tabs": {
2077 "git_status": true
2078 }
2079 }
2080 "#
2081 .unindent(),
2082 cx,
2083 );
2084
2085 check_settings_update(
2086 &mut store,
2087 r#"{
2088 }
2089 "#
2090 .unindent(),
2091 |settings| settings.title_bar.get_or_insert_default().show_branch_name = Some(true),
2092 r#"{
2093 "title_bar": {
2094 "show_branch_name": true
2095 }
2096 }
2097 "#
2098 .unindent(),
2099 cx,
2100 );
2101 }
2102
2103 #[gpui::test]
2104 fn test_edits_for_update_preserves_unknown_keys(cx: &mut App) {
2105 let mut store = SettingsStore::new(cx, &test_settings());
2106 store.register_setting::<AutoUpdateSetting>();
2107
2108 let old_json = r#"{
2109 "some_unknown_key": "should_be_preserved",
2110 "auto_update": false
2111 }"#
2112 .unindent();
2113
2114 check_settings_update(
2115 &mut store,
2116 old_json,
2117 |settings| settings.auto_update = Some(true),
2118 r#"{
2119 "some_unknown_key": "should_be_preserved",
2120 "auto_update": true
2121 }"#
2122 .unindent(),
2123 cx,
2124 );
2125 }
2126
2127 #[gpui::test]
2128 fn test_edits_for_update_returns_error_on_invalid_json(cx: &mut App) {
2129 let store = SettingsStore::new(cx, &test_settings());
2130
2131 let invalid_json = r#"{ this is not valid json at all !!!"#;
2132 let result = store.edits_for_update(invalid_json, |_| {});
2133 assert!(result.is_err());
2134 }
2135
2136 #[gpui::test]
2137 fn test_vscode_import(cx: &mut App) {
2138 let mut store = SettingsStore::new(cx, &test_settings());
2139 store.register_setting::<DefaultLanguageSettings>();
2140 store.register_setting::<ItemSettings>();
2141 store.register_setting::<AutoUpdateSetting>();
2142 store.register_setting::<ThemeSettings>();
2143
2144 // create settings that werent present
2145 check_vscode_import(
2146 &mut store,
2147 r#"{
2148 }
2149 "#
2150 .unindent(),
2151 r#" { "editor.tabSize": 37 } "#.to_owned(),
2152 r#"{
2153 "base_keymap": "VSCode",
2154 "tab_size": 37
2155 }
2156 "#
2157 .unindent(),
2158 cx,
2159 );
2160
2161 // persist settings that were present
2162 check_vscode_import(
2163 &mut store,
2164 r#"{
2165 "preferred_line_length": 99,
2166 }
2167 "#
2168 .unindent(),
2169 r#"{ "editor.tabSize": 42 }"#.to_owned(),
2170 r#"{
2171 "base_keymap": "VSCode",
2172 "tab_size": 42,
2173 "preferred_line_length": 99,
2174 }
2175 "#
2176 .unindent(),
2177 cx,
2178 );
2179
2180 // don't clobber settings that aren't present in vscode
2181 check_vscode_import(
2182 &mut store,
2183 r#"{
2184 "preferred_line_length": 99,
2185 "tab_size": 42
2186 }
2187 "#
2188 .unindent(),
2189 r#"{}"#.to_owned(),
2190 r#"{
2191 "base_keymap": "VSCode",
2192 "preferred_line_length": 99,
2193 "tab_size": 42
2194 }
2195 "#
2196 .unindent(),
2197 cx,
2198 );
2199
2200 // custom enum
2201 check_vscode_import(
2202 &mut store,
2203 r#"{
2204 }
2205 "#
2206 .unindent(),
2207 r#"{ "git.decorations.enabled": true }"#.to_owned(),
2208 r#"{
2209 "project_panel": {
2210 "git_status": true
2211 },
2212 "outline_panel": {
2213 "git_status": true
2214 },
2215 "base_keymap": "VSCode",
2216 "tabs": {
2217 "git_status": true
2218 }
2219 }
2220 "#
2221 .unindent(),
2222 cx,
2223 );
2224
2225 // explorer sort settings
2226 check_vscode_import(
2227 &mut store,
2228 r#"{
2229 }
2230 "#
2231 .unindent(),
2232 r#"{
2233 "explorer.sortOrder": "mixed",
2234 "explorer.sortOrderLexicographicOptions": "lower"
2235 }"#
2236 .unindent(),
2237 r#"{
2238 "project_panel": {
2239 "sort_mode": "mixed",
2240 "sort_order": "lower"
2241 },
2242 "base_keymap": "VSCode"
2243 }
2244 "#
2245 .unindent(),
2246 cx,
2247 );
2248
2249 // font-family
2250 check_vscode_import(
2251 &mut store,
2252 r#"{
2253 }
2254 "#
2255 .unindent(),
2256 r#"{ "editor.fontFamily": "Cascadia Code, 'Consolas', Courier New" }"#.to_owned(),
2257 r#"{
2258 "base_keymap": "VSCode",
2259 "buffer_font_fallbacks": [
2260 "Consolas",
2261 "Courier New"
2262 ],
2263 "buffer_font_family": "Cascadia Code"
2264 }
2265 "#
2266 .unindent(),
2267 cx,
2268 );
2269
2270 // terminal bell settings - newer accessibility setting
2271 check_vscode_import(
2272 &mut store,
2273 r#"{
2274 }
2275 "#
2276 .unindent(),
2277 r#"{ "accessibility.signals.terminalBell": { "sound": "on" } }"#.to_owned(),
2278 r#"{
2279 "terminal": {
2280 "bell": "system"
2281 },
2282 "base_keymap": "VSCode"
2283 }
2284 "#
2285 .unindent(),
2286 cx,
2287 );
2288
2289 // terminal bell settings - newer accessibility setting disabled
2290 check_vscode_import(
2291 &mut store,
2292 r#"{
2293 }
2294 "#
2295 .unindent(),
2296 r#"{ "accessibility.signals.terminalBell": { "sound": "off" } }"#.to_owned(),
2297 r#"{
2298 "terminal": {
2299 "bell": "off"
2300 },
2301 "base_keymap": "VSCode"
2302 }
2303 "#
2304 .unindent(),
2305 cx,
2306 );
2307
2308 // terminal bell settings - older enableBell setting (true)
2309 check_vscode_import(
2310 &mut store,
2311 r#"{
2312 }
2313 "#
2314 .unindent(),
2315 r#"{ "terminal.integrated.enableBell": true }"#.to_owned(),
2316 r#"{
2317 "terminal": {
2318 "bell": "system"
2319 },
2320 "base_keymap": "VSCode"
2321 }
2322 "#
2323 .unindent(),
2324 cx,
2325 );
2326
2327 // terminal bell settings - older enableBell setting (false)
2328 check_vscode_import(
2329 &mut store,
2330 r#"{
2331 }
2332 "#
2333 .unindent(),
2334 r#"{ "terminal.integrated.enableBell": false }"#.to_owned(),
2335 r#"{
2336 "terminal": {
2337 "bell": "off"
2338 },
2339 "base_keymap": "VSCode"
2340 }
2341 "#
2342 .unindent(),
2343 cx,
2344 );
2345
2346 // newer accessibility setting takes precedence over older enableBell
2347 check_vscode_import(
2348 &mut store,
2349 r#"{
2350 }
2351 "#
2352 .unindent(),
2353 r#"{
2354 "accessibility.signals.terminalBell": { "sound": "off" },
2355 "terminal.integrated.enableBell": true
2356 }"#
2357 .to_owned(),
2358 r#"{
2359 "terminal": {
2360 "bell": "off"
2361 },
2362 "base_keymap": "VSCode"
2363 }
2364 "#
2365 .unindent(),
2366 cx,
2367 );
2368
2369 // hover sticky settings
2370 check_vscode_import(
2371 &mut store,
2372 r#"{
2373 }
2374 "#
2375 .unindent(),
2376 r#"{
2377 "editor.hover.sticky": false,
2378 "editor.hover.hidingDelay": 500
2379 }"#
2380 .to_owned(),
2381 r#"{
2382 "base_keymap": "VSCode",
2383 "hover_popover_hiding_delay": 500,
2384 "hover_popover_sticky": false
2385 }
2386 "#
2387 .unindent(),
2388 cx,
2389 );
2390 }
2391
2392 #[track_caller]
2393 fn check_vscode_import(
2394 store: &mut SettingsStore,
2395 old: String,
2396 vscode: String,
2397 expected: String,
2398 cx: &mut App,
2399 ) {
2400 store.set_user_settings(&old, cx).ok();
2401 let new = store
2402 .get_vscode_edits(
2403 old,
2404 &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
2405 )
2406 .unwrap();
2407 pretty_assertions::assert_eq!(new, expected);
2408 }
2409
2410 #[gpui::test]
2411 fn test_update_git_settings(cx: &mut App) {
2412 let store = SettingsStore::new(cx, &test_settings());
2413
2414 let actual = store
2415 .new_text_for_update("{}".to_string(), |current| {
2416 current
2417 .git
2418 .get_or_insert_default()
2419 .inline_blame
2420 .get_or_insert_default()
2421 .enabled = Some(true);
2422 })
2423 .unwrap();
2424 pretty_assertions::assert_str_eq!(
2425 actual,
2426 r#"{
2427 "git": {
2428 "inline_blame": {
2429 "enabled": true
2430 }
2431 }
2432 }
2433 "#
2434 .unindent()
2435 );
2436 }
2437
2438 #[gpui::test]
2439 fn test_global_settings(cx: &mut App) {
2440 let mut store = SettingsStore::new(cx, &test_settings());
2441 store.register_setting::<ItemSettings>();
2442
2443 // Set global settings - these should override defaults but not user settings
2444 store
2445 .set_global_settings(
2446 r#"{
2447 "tabs": {
2448 "close_position": "right",
2449 "git_status": true,
2450 }
2451 }"#,
2452 cx,
2453 )
2454 .unwrap();
2455
2456 // Before user settings, global settings should apply
2457 assert_eq!(
2458 store.get::<ItemSettings>(None),
2459 &ItemSettings {
2460 close_position: ClosePosition::Right,
2461 git_status: true,
2462 }
2463 );
2464
2465 // Set user settings - these should override both defaults and global
2466 store
2467 .set_user_settings(
2468 r#"{
2469 "tabs": {
2470 "close_position": "left"
2471 }
2472 }"#,
2473 cx,
2474 )
2475 .unwrap();
2476
2477 // User settings should override global settings
2478 assert_eq!(
2479 store.get::<ItemSettings>(None),
2480 &ItemSettings {
2481 close_position: ClosePosition::Left,
2482 git_status: true, // Staff from global settings
2483 }
2484 );
2485 }
2486
2487 #[gpui::test]
2488 fn test_get_value_for_field_basic(cx: &mut App) {
2489 let mut store = SettingsStore::new(cx, &test_settings());
2490 store.register_setting::<DefaultLanguageSettings>();
2491
2492 store
2493 .set_user_settings(r#"{"preferred_line_length": 0}"#, cx)
2494 .unwrap();
2495 let local = (WorktreeId::from_usize(0), RelPath::empty().into_arc());
2496 store
2497 .set_local_settings(
2498 local.0,
2499 LocalSettingsPath::InWorktree(local.1.clone()),
2500 LocalSettingsKind::Settings,
2501 Some(r#"{}"#),
2502 cx,
2503 )
2504 .unwrap();
2505
2506 fn get(content: &SettingsContent) -> Option<&u32> {
2507 content
2508 .project
2509 .all_languages
2510 .defaults
2511 .preferred_line_length
2512 .as_ref()
2513 }
2514
2515 let default_value = *get(&store.default_settings).unwrap();
2516
2517 assert_eq!(
2518 store.get_value_from_file(SettingsFile::Project(local.clone()), get),
2519 (SettingsFile::User, Some(&0))
2520 );
2521 assert_eq!(
2522 store.get_value_from_file(SettingsFile::User, get),
2523 (SettingsFile::User, Some(&0))
2524 );
2525 store.set_user_settings(r#"{}"#, cx).unwrap();
2526 assert_eq!(
2527 store.get_value_from_file(SettingsFile::Project(local.clone()), get),
2528 (SettingsFile::Default, Some(&default_value))
2529 );
2530 store
2531 .set_local_settings(
2532 local.0,
2533 LocalSettingsPath::InWorktree(local.1.clone()),
2534 LocalSettingsKind::Settings,
2535 Some(r#"{"preferred_line_length": 80}"#),
2536 cx,
2537 )
2538 .unwrap();
2539 assert_eq!(
2540 store.get_value_from_file(SettingsFile::Project(local.clone()), get),
2541 (SettingsFile::Project(local), Some(&80))
2542 );
2543 assert_eq!(
2544 store.get_value_from_file(SettingsFile::User, get),
2545 (SettingsFile::Default, Some(&default_value))
2546 );
2547 }
2548
2549 #[gpui::test]
2550 fn test_get_value_for_field_local_worktrees_dont_interfere(cx: &mut App) {
2551 let mut store = SettingsStore::new(cx, &test_settings());
2552 store.register_setting::<DefaultLanguageSettings>();
2553 store.register_setting::<AutoUpdateSetting>();
2554
2555 let local_1 = (WorktreeId::from_usize(0), RelPath::empty().into_arc());
2556
2557 let local_1_child = (
2558 WorktreeId::from_usize(0),
2559 RelPath::new(
2560 std::path::Path::new("child1"),
2561 util::paths::PathStyle::Posix,
2562 )
2563 .unwrap()
2564 .into_arc(),
2565 );
2566
2567 let local_2 = (WorktreeId::from_usize(1), RelPath::empty().into_arc());
2568 let local_2_child = (
2569 WorktreeId::from_usize(1),
2570 RelPath::new(
2571 std::path::Path::new("child2"),
2572 util::paths::PathStyle::Posix,
2573 )
2574 .unwrap()
2575 .into_arc(),
2576 );
2577
2578 fn get(content: &SettingsContent) -> Option<&u32> {
2579 content
2580 .project
2581 .all_languages
2582 .defaults
2583 .preferred_line_length
2584 .as_ref()
2585 }
2586
2587 store
2588 .set_local_settings(
2589 local_1.0,
2590 LocalSettingsPath::InWorktree(local_1.1.clone()),
2591 LocalSettingsKind::Settings,
2592 Some(r#"{"preferred_line_length": 1}"#),
2593 cx,
2594 )
2595 .unwrap();
2596 store
2597 .set_local_settings(
2598 local_1_child.0,
2599 LocalSettingsPath::InWorktree(local_1_child.1.clone()),
2600 LocalSettingsKind::Settings,
2601 Some(r#"{}"#),
2602 cx,
2603 )
2604 .unwrap();
2605 store
2606 .set_local_settings(
2607 local_2.0,
2608 LocalSettingsPath::InWorktree(local_2.1.clone()),
2609 LocalSettingsKind::Settings,
2610 Some(r#"{"preferred_line_length": 2}"#),
2611 cx,
2612 )
2613 .unwrap();
2614 store
2615 .set_local_settings(
2616 local_2_child.0,
2617 LocalSettingsPath::InWorktree(local_2_child.1.clone()),
2618 LocalSettingsKind::Settings,
2619 Some(r#"{}"#),
2620 cx,
2621 )
2622 .unwrap();
2623
2624 // each local child should only inherit from it's parent
2625 assert_eq!(
2626 store.get_value_from_file(SettingsFile::Project(local_2_child), get),
2627 (SettingsFile::Project(local_2), Some(&2))
2628 );
2629 assert_eq!(
2630 store.get_value_from_file(SettingsFile::Project(local_1_child.clone()), get),
2631 (SettingsFile::Project(local_1.clone()), Some(&1))
2632 );
2633
2634 // adjacent children should be treated as siblings not inherit from each other
2635 let local_1_adjacent_child = (local_1.0, rel_path("adjacent_child").into_arc());
2636 store
2637 .set_local_settings(
2638 local_1_adjacent_child.0,
2639 LocalSettingsPath::InWorktree(local_1_adjacent_child.1.clone()),
2640 LocalSettingsKind::Settings,
2641 Some(r#"{}"#),
2642 cx,
2643 )
2644 .unwrap();
2645 store
2646 .set_local_settings(
2647 local_1_child.0,
2648 LocalSettingsPath::InWorktree(local_1_child.1.clone()),
2649 LocalSettingsKind::Settings,
2650 Some(r#"{"preferred_line_length": 3}"#),
2651 cx,
2652 )
2653 .unwrap();
2654
2655 assert_eq!(
2656 store.get_value_from_file(SettingsFile::Project(local_1_adjacent_child.clone()), get),
2657 (SettingsFile::Project(local_1.clone()), Some(&1))
2658 );
2659 store
2660 .set_local_settings(
2661 local_1_adjacent_child.0,
2662 LocalSettingsPath::InWorktree(local_1_adjacent_child.1),
2663 LocalSettingsKind::Settings,
2664 Some(r#"{"preferred_line_length": 3}"#),
2665 cx,
2666 )
2667 .unwrap();
2668 store
2669 .set_local_settings(
2670 local_1_child.0,
2671 LocalSettingsPath::InWorktree(local_1_child.1.clone()),
2672 LocalSettingsKind::Settings,
2673 Some(r#"{}"#),
2674 cx,
2675 )
2676 .unwrap();
2677 assert_eq!(
2678 store.get_value_from_file(SettingsFile::Project(local_1_child), get),
2679 (SettingsFile::Project(local_1), Some(&1))
2680 );
2681 }
2682
2683 #[gpui::test]
2684 fn test_get_overrides_for_field(cx: &mut App) {
2685 let mut store = SettingsStore::new(cx, &test_settings());
2686 store.register_setting::<DefaultLanguageSettings>();
2687
2688 let wt0_root = (WorktreeId::from_usize(0), RelPath::empty().into_arc());
2689 let wt0_child1 = (WorktreeId::from_usize(0), rel_path("child1").into_arc());
2690 let wt0_child2 = (WorktreeId::from_usize(0), rel_path("child2").into_arc());
2691
2692 let wt1_root = (WorktreeId::from_usize(1), RelPath::empty().into_arc());
2693 let wt1_subdir = (WorktreeId::from_usize(1), rel_path("subdir").into_arc());
2694
2695 fn get(content: &SettingsContent) -> &Option<u32> {
2696 &content.project.all_languages.defaults.preferred_line_length
2697 }
2698
2699 store
2700 .set_user_settings(r#"{"preferred_line_length": 100}"#, cx)
2701 .unwrap();
2702
2703 store
2704 .set_local_settings(
2705 wt0_root.0,
2706 LocalSettingsPath::InWorktree(wt0_root.1.clone()),
2707 LocalSettingsKind::Settings,
2708 Some(r#"{"preferred_line_length": 80}"#),
2709 cx,
2710 )
2711 .unwrap();
2712 store
2713 .set_local_settings(
2714 wt0_child1.0,
2715 LocalSettingsPath::InWorktree(wt0_child1.1.clone()),
2716 LocalSettingsKind::Settings,
2717 Some(r#"{"preferred_line_length": 120}"#),
2718 cx,
2719 )
2720 .unwrap();
2721 store
2722 .set_local_settings(
2723 wt0_child2.0,
2724 LocalSettingsPath::InWorktree(wt0_child2.1.clone()),
2725 LocalSettingsKind::Settings,
2726 Some(r#"{}"#),
2727 cx,
2728 )
2729 .unwrap();
2730
2731 store
2732 .set_local_settings(
2733 wt1_root.0,
2734 LocalSettingsPath::InWorktree(wt1_root.1.clone()),
2735 LocalSettingsKind::Settings,
2736 Some(r#"{"preferred_line_length": 90}"#),
2737 cx,
2738 )
2739 .unwrap();
2740 store
2741 .set_local_settings(
2742 wt1_subdir.0,
2743 LocalSettingsPath::InWorktree(wt1_subdir.1.clone()),
2744 LocalSettingsKind::Settings,
2745 Some(r#"{}"#),
2746 cx,
2747 )
2748 .unwrap();
2749
2750 let overrides = store.get_overrides_for_field(SettingsFile::Default, get);
2751 assert_eq!(
2752 overrides,
2753 vec![
2754 SettingsFile::User,
2755 SettingsFile::Project(wt0_root.clone()),
2756 SettingsFile::Project(wt0_child1.clone()),
2757 SettingsFile::Project(wt1_root.clone()),
2758 ]
2759 );
2760
2761 let overrides = store.get_overrides_for_field(SettingsFile::User, get);
2762 assert_eq!(
2763 overrides,
2764 vec![
2765 SettingsFile::Project(wt0_root.clone()),
2766 SettingsFile::Project(wt0_child1.clone()),
2767 SettingsFile::Project(wt1_root.clone()),
2768 ]
2769 );
2770
2771 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_root), get);
2772 assert_eq!(overrides, vec![]);
2773
2774 let overrides =
2775 store.get_overrides_for_field(SettingsFile::Project(wt0_child1.clone()), get);
2776 assert_eq!(overrides, vec![]);
2777
2778 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_child2), get);
2779 assert_eq!(overrides, vec![]);
2780
2781 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt1_root), get);
2782 assert_eq!(overrides, vec![]);
2783
2784 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt1_subdir), get);
2785 assert_eq!(overrides, vec![]);
2786
2787 let wt0_deep_child = (
2788 WorktreeId::from_usize(0),
2789 rel_path("child1/subdir").into_arc(),
2790 );
2791 store
2792 .set_local_settings(
2793 wt0_deep_child.0,
2794 LocalSettingsPath::InWorktree(wt0_deep_child.1.clone()),
2795 LocalSettingsKind::Settings,
2796 Some(r#"{"preferred_line_length": 140}"#),
2797 cx,
2798 )
2799 .unwrap();
2800
2801 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_deep_child), get);
2802 assert_eq!(overrides, vec![]);
2803
2804 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_child1), get);
2805 assert_eq!(overrides, vec![]);
2806 }
2807
2808 #[test]
2809 fn test_file_ord() {
2810 let wt0_root =
2811 SettingsFile::Project((WorktreeId::from_usize(0), RelPath::empty().into_arc()));
2812 let wt0_child1 =
2813 SettingsFile::Project((WorktreeId::from_usize(0), rel_path("child1").into_arc()));
2814 let wt0_child2 =
2815 SettingsFile::Project((WorktreeId::from_usize(0), rel_path("child2").into_arc()));
2816
2817 let wt1_root =
2818 SettingsFile::Project((WorktreeId::from_usize(1), RelPath::empty().into_arc()));
2819 let wt1_subdir =
2820 SettingsFile::Project((WorktreeId::from_usize(1), rel_path("subdir").into_arc()));
2821
2822 let mut files = vec![
2823 &wt1_root,
2824 &SettingsFile::Default,
2825 &wt0_root,
2826 &wt1_subdir,
2827 &wt0_child2,
2828 &SettingsFile::Server,
2829 &wt0_child1,
2830 &SettingsFile::User,
2831 ];
2832
2833 files.sort();
2834 pretty_assertions::assert_eq!(
2835 files,
2836 vec![
2837 &wt0_child2,
2838 &wt0_child1,
2839 &wt0_root,
2840 &wt1_subdir,
2841 &wt1_root,
2842 &SettingsFile::Server,
2843 &SettingsFile::User,
2844 &SettingsFile::Default,
2845 ]
2846 )
2847 }
2848
2849 #[gpui::test]
2850 fn test_lsp_settings_schema_generation(cx: &mut App) {
2851 SettingsStore::test(cx);
2852
2853 let schema = SettingsStore::json_schema(&SettingsJsonSchemaParams {
2854 language_names: &["Rust".to_string(), "TypeScript".to_string()],
2855 font_names: &["Zed Mono".to_string()],
2856 theme_names: &["One Dark".into()],
2857 icon_theme_names: &["Zed Icons".into()],
2858 lsp_adapter_names: &[
2859 "rust-analyzer".to_string(),
2860 "typescript-language-server".to_string(),
2861 ],
2862 action_names: &[],
2863 action_documentation: &HashMap::default(),
2864 deprecations: &HashMap::default(),
2865 deprecation_messages: &HashMap::default(),
2866 });
2867
2868 let properties = schema
2869 .pointer("/$defs/LspSettingsMap/properties")
2870 .expect("LspSettingsMap should have properties")
2871 .as_object()
2872 .unwrap();
2873
2874 assert!(properties.contains_key("rust-analyzer"));
2875 assert!(properties.contains_key("typescript-language-server"));
2876
2877 let init_options_ref = properties
2878 .get("rust-analyzer")
2879 .unwrap()
2880 .pointer("/properties/initialization_options/$ref")
2881 .expect("initialization_options should have a $ref")
2882 .as_str()
2883 .unwrap();
2884
2885 assert_eq!(
2886 init_options_ref,
2887 "zed://schemas/settings/lsp/rust-analyzer/initialization_options"
2888 );
2889
2890 let settings_ref = properties
2891 .get("rust-analyzer")
2892 .unwrap()
2893 .pointer("/properties/settings/$ref")
2894 .expect("settings should have a $ref")
2895 .as_str()
2896 .unwrap();
2897
2898 assert_eq!(
2899 settings_ref,
2900 "zed://schemas/settings/lsp/rust-analyzer/settings"
2901 );
2902 }
2903
2904 #[gpui::test]
2905 fn test_lsp_project_settings_schema_generation(cx: &mut App) {
2906 SettingsStore::test(cx);
2907
2908 let schema = SettingsStore::project_json_schema(&SettingsJsonSchemaParams {
2909 language_names: &["Rust".to_string(), "TypeScript".to_string()],
2910 font_names: &["Zed Mono".to_string()],
2911 theme_names: &["One Dark".into()],
2912 icon_theme_names: &["Zed Icons".into()],
2913 lsp_adapter_names: &[
2914 "rust-analyzer".to_string(),
2915 "typescript-language-server".to_string(),
2916 ],
2917 action_names: &[],
2918 action_documentation: &HashMap::default(),
2919 deprecations: &HashMap::default(),
2920 deprecation_messages: &HashMap::default(),
2921 });
2922
2923 let properties = schema
2924 .pointer("/$defs/LspSettingsMap/properties")
2925 .expect("LspSettingsMap should have properties")
2926 .as_object()
2927 .unwrap();
2928
2929 assert!(properties.contains_key("rust-analyzer"));
2930 assert!(properties.contains_key("typescript-language-server"));
2931
2932 let init_options_ref = properties
2933 .get("rust-analyzer")
2934 .unwrap()
2935 .pointer("/properties/initialization_options/$ref")
2936 .expect("initialization_options should have a $ref")
2937 .as_str()
2938 .unwrap();
2939
2940 assert_eq!(
2941 init_options_ref,
2942 "zed://schemas/settings/lsp/rust-analyzer/initialization_options"
2943 );
2944
2945 let settings_ref = properties
2946 .get("rust-analyzer")
2947 .unwrap()
2948 .pointer("/properties/settings/$ref")
2949 .expect("settings should have a $ref")
2950 .as_str()
2951 .unwrap();
2952
2953 assert_eq!(
2954 settings_ref,
2955 "zed://schemas/settings/lsp/rust-analyzer/settings"
2956 );
2957 }
2958
2959 #[gpui::test]
2960 fn test_project_json_schema_differs_from_user_schema(cx: &mut App) {
2961 SettingsStore::test(cx);
2962
2963 let params = SettingsJsonSchemaParams {
2964 language_names: &["Rust".to_string()],
2965 font_names: &["Zed Mono".to_string()],
2966 theme_names: &["One Dark".into()],
2967 icon_theme_names: &["Zed Icons".into()],
2968 lsp_adapter_names: &["rust-analyzer".to_string()],
2969 action_names: &[],
2970 action_documentation: &HashMap::default(),
2971 deprecations: &HashMap::default(),
2972 deprecation_messages: &HashMap::default(),
2973 };
2974
2975 let user_schema = SettingsStore::json_schema(¶ms);
2976 let project_schema = SettingsStore::project_json_schema(¶ms);
2977
2978 assert_ne!(user_schema, project_schema);
2979
2980 let user_schema_str = serde_json::to_string(&user_schema).unwrap();
2981 let project_schema_str = serde_json::to_string(&project_schema).unwrap();
2982
2983 assert!(user_schema_str.contains("\"auto_update\""));
2984 assert!(!project_schema_str.contains("\"auto_update\""));
2985 }
2986}