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