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