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