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