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