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, ProfileBase, ProjectSettingsContent, RootUserSettings,
40 SettingsContent, UserSettingsContent, merge_from::MergeFrom,
41 },
42};
43
44use settings_json::{infer_json_indent_size, update_value_in_json_text};
45
46pub const LSP_SETTINGS_SCHEMA_URL_PREFIX: &str = "zed://schemas/settings/lsp/";
47
48pub trait SettingsKey: 'static + Send + Sync {
49 /// The name of a key within the JSON file from which this setting should
50 /// be deserialized. If this is `None`, then the setting will be deserialized
51 /// from the root object.
52 const KEY: Option<&'static str>;
53
54 const FALLBACK_KEY: Option<&'static str> = None;
55}
56
57/// A value that can be defined as a user setting.
58///
59/// Settings can be loaded from a combination of multiple JSON files.
60pub trait Settings: 'static + Send + Sync + Sized {
61 /// The name of the keys in the [`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 let active_profile = user_settings.for_profile(cx);
1214 let should_merge_user_settings =
1215 active_profile.is_none_or(|profile| profile.base == ProfileBase::User);
1216
1217 if should_merge_user_settings {
1218 merged.merge_from(&user_settings.content);
1219 merged.merge_from_option(user_settings.for_release_channel());
1220 merged.merge_from_option(user_settings.for_os());
1221 }
1222
1223 if let Some(profile) = active_profile {
1224 merged.merge_from(&profile.settings);
1225 }
1226 }
1227 merged.merge_from_option(self.server_settings.as_deref());
1228
1229 // Merge `disable_ai` from all project/local settings into the global value.
1230 // Since `SaturatingBool` uses OR logic, if any project has `disable_ai: true`,
1231 // the global value will be true. This allows project-level `disable_ai` to
1232 // affect the global setting used by UI elements without file context.
1233 for local_settings in self.local_settings.values() {
1234 merged
1235 .project
1236 .disable_ai
1237 .merge_from(&local_settings.project.disable_ai);
1238 }
1239
1240 self.merged_settings = Rc::new(merged);
1241
1242 for setting_value in self.setting_values.values_mut() {
1243 let value = setting_value.from_settings(&self.merged_settings);
1244 setting_value.set_global_value(value);
1245 }
1246 } else {
1247 // When only a local path changed, we still need to recompute the global
1248 // `disable_ai` value since it depends on all local settings.
1249 let mut merged = (*self.merged_settings).clone();
1250 // Reset disable_ai to compute fresh from base settings
1251 merged.project.disable_ai = self.default_settings.project.disable_ai;
1252 if let Some(global) = &self.global_settings {
1253 merged
1254 .project
1255 .disable_ai
1256 .merge_from(&global.project.disable_ai);
1257 }
1258 if let Some(user) = &self.user_settings {
1259 merged
1260 .project
1261 .disable_ai
1262 .merge_from(&user.content.project.disable_ai);
1263 }
1264 if let Some(server) = &self.server_settings {
1265 merged
1266 .project
1267 .disable_ai
1268 .merge_from(&server.project.disable_ai);
1269 }
1270 for local_settings in self.local_settings.values() {
1271 merged
1272 .project
1273 .disable_ai
1274 .merge_from(&local_settings.project.disable_ai);
1275 }
1276 self.merged_settings = Rc::new(merged);
1277
1278 for setting_value in self.setting_values.values_mut() {
1279 let value = setting_value.from_settings(&self.merged_settings);
1280 setting_value.set_global_value(value);
1281 }
1282 }
1283
1284 for ((root_id, directory_path), local_settings) in &self.local_settings {
1285 // Build a stack of all of the local values for that setting.
1286 while let Some(prev_entry) = paths_stack.last() {
1287 if let Some((prev_root_id, prev_path)) = prev_entry
1288 && (root_id != prev_root_id || !directory_path.starts_with(prev_path))
1289 {
1290 paths_stack.pop();
1291 project_settings_stack.pop();
1292 continue;
1293 }
1294 break;
1295 }
1296
1297 paths_stack.push(Some((*root_id, directory_path.as_ref())));
1298 let mut merged_local_settings = if let Some(deepest) = project_settings_stack.last() {
1299 (*deepest).clone()
1300 } else {
1301 self.merged_settings.as_ref().clone()
1302 };
1303 merged_local_settings.merge_from(local_settings);
1304
1305 project_settings_stack.push(merged_local_settings);
1306
1307 // If a local settings file changed, then avoid recomputing local
1308 // settings for any path outside of that directory.
1309 if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| {
1310 *root_id != changed_root_id || !directory_path.starts_with(changed_local_path)
1311 }) {
1312 continue;
1313 }
1314
1315 for setting_value in self.setting_values.values_mut() {
1316 let value = setting_value.from_settings(&project_settings_stack.last().unwrap());
1317 setting_value.set_local_value(*root_id, directory_path.clone(), value);
1318 }
1319 }
1320 }
1321}
1322
1323/// The result of parsing settings, including any migration attempts
1324#[derive(Debug, Clone, PartialEq, Eq)]
1325pub struct SettingsParseResult {
1326 /// The result of parsing the settings file (possibly after migration)
1327 pub parse_status: ParseStatus,
1328 /// The result of attempting to migrate the settings file
1329 pub migration_status: MigrationStatus,
1330}
1331
1332#[derive(Debug, Clone, PartialEq, Eq)]
1333pub enum MigrationStatus {
1334 /// No migration was needed - settings are up to date
1335 NotNeeded,
1336 /// Settings were automatically migrated in memory, but the file needs to be updated
1337 Succeeded,
1338 /// Migration was attempted but failed. Original settings were parsed instead.
1339 Failed { error: String },
1340}
1341
1342impl Default for SettingsParseResult {
1343 fn default() -> Self {
1344 Self {
1345 parse_status: ParseStatus::Success,
1346 migration_status: MigrationStatus::NotNeeded,
1347 }
1348 }
1349}
1350
1351impl SettingsParseResult {
1352 pub fn unwrap(self) -> bool {
1353 self.result().unwrap()
1354 }
1355
1356 pub fn expect(self, message: &str) -> bool {
1357 self.result().expect(message)
1358 }
1359
1360 /// Formats the ParseResult as a Result type. This is a lossy conversion
1361 pub fn result(self) -> Result<bool> {
1362 let migration_result = match self.migration_status {
1363 MigrationStatus::NotNeeded => Ok(false),
1364 MigrationStatus::Succeeded => Ok(true),
1365 MigrationStatus::Failed { error } => {
1366 Err(anyhow::format_err!(error)).context("Failed to migrate settings")
1367 }
1368 };
1369
1370 let parse_result = match self.parse_status {
1371 ParseStatus::Success => Ok(()),
1372 ParseStatus::Failed { error } => {
1373 Err(anyhow::format_err!(error)).context("Failed to parse settings")
1374 }
1375 };
1376
1377 match (migration_result, parse_result) {
1378 (migration_result @ Ok(_), Ok(())) => migration_result,
1379 (Err(migration_err), Ok(())) => Err(migration_err),
1380 (_, Err(parse_err)) => Err(parse_err),
1381 }
1382 }
1383
1384 /// Returns true if there were any errors migrating and parsing the settings content or if migration was required but there were no errors
1385 pub fn requires_user_action(&self) -> bool {
1386 matches!(self.parse_status, ParseStatus::Failed { .. })
1387 || matches!(
1388 self.migration_status,
1389 MigrationStatus::Succeeded | MigrationStatus::Failed { .. }
1390 )
1391 }
1392
1393 pub fn ok(self) -> Option<bool> {
1394 self.result().ok()
1395 }
1396
1397 pub fn parse_error(&self) -> Option<String> {
1398 match &self.parse_status {
1399 ParseStatus::Failed { error } => Some(error.clone()),
1400 ParseStatus::Success => None,
1401 }
1402 }
1403}
1404
1405#[derive(Debug, Clone, PartialEq)]
1406pub enum InvalidSettingsError {
1407 LocalSettings {
1408 path: Arc<RelPath>,
1409 message: String,
1410 },
1411 UserSettings {
1412 message: String,
1413 },
1414 ServerSettings {
1415 message: String,
1416 },
1417 DefaultSettings {
1418 message: String,
1419 },
1420 Editorconfig {
1421 path: LocalSettingsPath,
1422 message: String,
1423 },
1424 Tasks {
1425 path: PathBuf,
1426 message: String,
1427 },
1428 Debug {
1429 path: PathBuf,
1430 message: String,
1431 },
1432}
1433
1434impl std::fmt::Display for InvalidSettingsError {
1435 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1436 match self {
1437 InvalidSettingsError::LocalSettings { message, .. }
1438 | InvalidSettingsError::UserSettings { message }
1439 | InvalidSettingsError::ServerSettings { message }
1440 | InvalidSettingsError::DefaultSettings { message }
1441 | InvalidSettingsError::Tasks { message, .. }
1442 | InvalidSettingsError::Editorconfig { message, .. }
1443 | InvalidSettingsError::Debug { message, .. } => write!(f, "{message}"),
1444 }
1445 }
1446}
1447impl std::error::Error for InvalidSettingsError {}
1448
1449impl Debug for SettingsStore {
1450 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1451 f.debug_struct("SettingsStore")
1452 .field(
1453 "types",
1454 &self
1455 .setting_values
1456 .values()
1457 .map(|value| value.setting_type_name())
1458 .collect::<Vec<_>>(),
1459 )
1460 .field("default_settings", &self.default_settings)
1461 .field("user_settings", &self.user_settings)
1462 .field("local_settings", &self.local_settings)
1463 .finish_non_exhaustive()
1464 }
1465}
1466
1467impl<T: Settings> AnySettingValue for SettingValue<T> {
1468 fn from_settings(&self, s: &SettingsContent) -> Box<dyn Any> {
1469 Box::new(T::from_settings(s)) as _
1470 }
1471
1472 fn setting_type_name(&self) -> &'static str {
1473 type_name::<T>()
1474 }
1475
1476 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<RelPath>, &dyn Any)> {
1477 self.local_values
1478 .iter()
1479 .map(|(id, path, value)| (*id, path.clone(), value as _))
1480 .collect()
1481 }
1482
1483 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
1484 if let Some(SettingsLocation { worktree_id, path }) = path {
1485 for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
1486 if worktree_id == *settings_root_id && path.starts_with(settings_path) {
1487 return value;
1488 }
1489 }
1490 }
1491
1492 self.global_value
1493 .as_ref()
1494 .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
1495 }
1496
1497 fn set_global_value(&mut self, value: Box<dyn Any>) {
1498 self.global_value = Some(*value.downcast().unwrap());
1499 }
1500
1501 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<RelPath>, value: Box<dyn Any>) {
1502 let value = *value.downcast().unwrap();
1503 match self
1504 .local_values
1505 .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
1506 {
1507 Ok(ix) => self.local_values[ix].2 = value,
1508 Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
1509 }
1510 }
1511
1512 fn clear_local_values(&mut self, root_id: WorktreeId) {
1513 self.local_values
1514 .retain(|(worktree_id, _, _)| *worktree_id != root_id);
1515 }
1516}
1517
1518#[cfg(test)]
1519mod tests {
1520 use std::num::NonZeroU32;
1521
1522 use crate::{
1523 ClosePosition, ItemSettingsContent, VsCodeSettingsSource, default_settings,
1524 settings_content::LanguageSettingsContent, test_settings,
1525 };
1526
1527 use super::*;
1528 use unindent::Unindent;
1529 use util::rel_path::rel_path;
1530
1531 #[derive(Debug, PartialEq)]
1532 struct AutoUpdateSetting {
1533 auto_update: bool,
1534 }
1535
1536 impl Settings for AutoUpdateSetting {
1537 fn from_settings(content: &SettingsContent) -> Self {
1538 AutoUpdateSetting {
1539 auto_update: content.auto_update.unwrap(),
1540 }
1541 }
1542 }
1543
1544 #[derive(Debug, PartialEq)]
1545 struct ItemSettings {
1546 close_position: ClosePosition,
1547 git_status: bool,
1548 }
1549
1550 impl Settings for ItemSettings {
1551 fn from_settings(content: &SettingsContent) -> Self {
1552 let content = content.tabs.clone().unwrap();
1553 ItemSettings {
1554 close_position: content.close_position.unwrap(),
1555 git_status: content.git_status.unwrap(),
1556 }
1557 }
1558 }
1559
1560 #[derive(Debug, PartialEq)]
1561 struct DefaultLanguageSettings {
1562 tab_size: NonZeroU32,
1563 preferred_line_length: u32,
1564 }
1565
1566 impl Settings for DefaultLanguageSettings {
1567 fn from_settings(content: &SettingsContent) -> Self {
1568 let content = &content.project.all_languages.defaults;
1569 DefaultLanguageSettings {
1570 tab_size: content.tab_size.unwrap(),
1571 preferred_line_length: content.preferred_line_length.unwrap(),
1572 }
1573 }
1574 }
1575
1576 #[derive(Debug, PartialEq)]
1577 struct ThemeSettings {
1578 buffer_font_family: FontFamilyName,
1579 buffer_font_fallbacks: Vec<FontFamilyName>,
1580 }
1581
1582 impl Settings for ThemeSettings {
1583 fn from_settings(content: &SettingsContent) -> Self {
1584 let content = content.theme.clone();
1585 ThemeSettings {
1586 buffer_font_family: content.buffer_font_family.unwrap(),
1587 buffer_font_fallbacks: content.buffer_font_fallbacks.unwrap(),
1588 }
1589 }
1590 }
1591
1592 #[gpui::test]
1593 fn test_settings_store_basic(cx: &mut App) {
1594 let mut store = SettingsStore::new(cx, &default_settings());
1595 store.register_setting::<AutoUpdateSetting>();
1596 store.register_setting::<ItemSettings>();
1597 store.register_setting::<DefaultLanguageSettings>();
1598
1599 assert_eq!(
1600 store.get::<AutoUpdateSetting>(None),
1601 &AutoUpdateSetting { auto_update: true }
1602 );
1603 assert_eq!(
1604 store.get::<ItemSettings>(None).close_position,
1605 ClosePosition::Right
1606 );
1607
1608 store
1609 .set_user_settings(
1610 r#"{
1611 "auto_update": false,
1612 "tabs": {
1613 "close_position": "left"
1614 }
1615 }"#,
1616 cx,
1617 )
1618 .unwrap();
1619
1620 assert_eq!(
1621 store.get::<AutoUpdateSetting>(None),
1622 &AutoUpdateSetting { auto_update: false }
1623 );
1624 assert_eq!(
1625 store.get::<ItemSettings>(None).close_position,
1626 ClosePosition::Left
1627 );
1628
1629 store
1630 .set_local_settings(
1631 WorktreeId::from_usize(1),
1632 LocalSettingsPath::InWorktree(rel_path("root1").into()),
1633 LocalSettingsKind::Settings,
1634 Some(r#"{ "tab_size": 5 }"#),
1635 cx,
1636 )
1637 .unwrap();
1638 store
1639 .set_local_settings(
1640 WorktreeId::from_usize(1),
1641 LocalSettingsPath::InWorktree(rel_path("root1/subdir").into()),
1642 LocalSettingsKind::Settings,
1643 Some(r#"{ "preferred_line_length": 50 }"#),
1644 cx,
1645 )
1646 .unwrap();
1647
1648 store
1649 .set_local_settings(
1650 WorktreeId::from_usize(1),
1651 LocalSettingsPath::InWorktree(rel_path("root2").into()),
1652 LocalSettingsKind::Settings,
1653 Some(r#"{ "tab_size": 9, "auto_update": true}"#),
1654 cx,
1655 )
1656 .unwrap();
1657
1658 assert_eq!(
1659 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1660 worktree_id: WorktreeId::from_usize(1),
1661 path: rel_path("root1/something"),
1662 })),
1663 &DefaultLanguageSettings {
1664 preferred_line_length: 80,
1665 tab_size: 5.try_into().unwrap(),
1666 }
1667 );
1668 assert_eq!(
1669 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1670 worktree_id: WorktreeId::from_usize(1),
1671 path: rel_path("root1/subdir/something"),
1672 })),
1673 &DefaultLanguageSettings {
1674 preferred_line_length: 50,
1675 tab_size: 5.try_into().unwrap(),
1676 }
1677 );
1678 assert_eq!(
1679 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1680 worktree_id: WorktreeId::from_usize(1),
1681 path: rel_path("root2/something"),
1682 })),
1683 &DefaultLanguageSettings {
1684 preferred_line_length: 80,
1685 tab_size: 9.try_into().unwrap(),
1686 }
1687 );
1688 assert_eq!(
1689 store.get::<AutoUpdateSetting>(Some(SettingsLocation {
1690 worktree_id: WorktreeId::from_usize(1),
1691 path: rel_path("root2/something")
1692 })),
1693 &AutoUpdateSetting { auto_update: false }
1694 );
1695 }
1696
1697 #[gpui::test]
1698 fn test_setting_store_assign_json_before_register(cx: &mut App) {
1699 let mut store = SettingsStore::new(cx, &test_settings());
1700 store
1701 .set_user_settings(r#"{ "auto_update": false }"#, cx)
1702 .unwrap();
1703 store.register_setting::<AutoUpdateSetting>();
1704
1705 assert_eq!(
1706 store.get::<AutoUpdateSetting>(None),
1707 &AutoUpdateSetting { auto_update: false }
1708 );
1709 }
1710
1711 #[track_caller]
1712 fn check_settings_update(
1713 store: &mut SettingsStore,
1714 old_json: String,
1715 update: fn(&mut SettingsContent),
1716 expected_new_json: String,
1717 cx: &mut App,
1718 ) {
1719 store.set_user_settings(&old_json, cx).ok();
1720 let edits = store.edits_for_update(&old_json, update).unwrap();
1721 let mut new_json = old_json;
1722 for (range, replacement) in edits.into_iter() {
1723 new_json.replace_range(range, &replacement);
1724 }
1725 pretty_assertions::assert_eq!(new_json, expected_new_json);
1726 }
1727
1728 #[gpui::test]
1729 fn test_setting_store_update(cx: &mut App) {
1730 let mut store = SettingsStore::new(cx, &test_settings());
1731
1732 // entries added and updated
1733 check_settings_update(
1734 &mut store,
1735 r#"{
1736 "languages": {
1737 "JSON": {
1738 "auto_indent": "syntax_aware"
1739 }
1740 }
1741 }"#
1742 .unindent(),
1743 |settings| {
1744 settings
1745 .languages_mut()
1746 .get_mut("JSON")
1747 .unwrap()
1748 .auto_indent = Some(crate::AutoIndentMode::None);
1749
1750 settings.languages_mut().insert(
1751 "Rust".into(),
1752 LanguageSettingsContent {
1753 auto_indent: Some(crate::AutoIndentMode::SyntaxAware),
1754 ..Default::default()
1755 },
1756 );
1757 },
1758 r#"{
1759 "languages": {
1760 "Rust": {
1761 "auto_indent": "syntax_aware"
1762 },
1763 "JSON": {
1764 "auto_indent": "none"
1765 }
1766 }
1767 }"#
1768 .unindent(),
1769 cx,
1770 );
1771
1772 // entries removed
1773 check_settings_update(
1774 &mut store,
1775 r#"{
1776 "languages": {
1777 "Rust": {
1778 "language_setting_2": true
1779 },
1780 "JSON": {
1781 "language_setting_1": false
1782 }
1783 }
1784 }"#
1785 .unindent(),
1786 |settings| {
1787 settings.languages_mut().remove("JSON").unwrap();
1788 },
1789 r#"{
1790 "languages": {
1791 "Rust": {
1792 "language_setting_2": true
1793 }
1794 }
1795 }"#
1796 .unindent(),
1797 cx,
1798 );
1799
1800 check_settings_update(
1801 &mut store,
1802 r#"{
1803 "languages": {
1804 "Rust": {
1805 "language_setting_2": true
1806 },
1807 "JSON": {
1808 "language_setting_1": false
1809 }
1810 }
1811 }"#
1812 .unindent(),
1813 |settings| {
1814 settings.languages_mut().remove("Rust").unwrap();
1815 },
1816 r#"{
1817 "languages": {
1818 "JSON": {
1819 "language_setting_1": false
1820 }
1821 }
1822 }"#
1823 .unindent(),
1824 cx,
1825 );
1826
1827 // weird formatting
1828 check_settings_update(
1829 &mut store,
1830 r#"{
1831 "tabs": { "close_position": "left", "name": "Max" }
1832 }"#
1833 .unindent(),
1834 |settings| {
1835 settings.tabs.as_mut().unwrap().close_position = Some(ClosePosition::Left);
1836 },
1837 r#"{
1838 "tabs": { "close_position": "left", "name": "Max" }
1839 }"#
1840 .unindent(),
1841 cx,
1842 );
1843
1844 // single-line formatting, other keys
1845 check_settings_update(
1846 &mut store,
1847 r#"{ "one": 1, "two": 2 }"#.to_owned(),
1848 |settings| settings.auto_update = Some(true),
1849 r#"{ "auto_update": true, "one": 1, "two": 2 }"#.to_owned(),
1850 cx,
1851 );
1852
1853 // empty object
1854 check_settings_update(
1855 &mut store,
1856 r#"{
1857 "tabs": {}
1858 }"#
1859 .unindent(),
1860 |settings| settings.tabs.as_mut().unwrap().close_position = Some(ClosePosition::Left),
1861 r#"{
1862 "tabs": {
1863 "close_position": "left"
1864 }
1865 }"#
1866 .unindent(),
1867 cx,
1868 );
1869
1870 // no content
1871 check_settings_update(
1872 &mut store,
1873 r#""#.unindent(),
1874 |settings| {
1875 settings.tabs = Some(ItemSettingsContent {
1876 git_status: Some(true),
1877 ..Default::default()
1878 })
1879 },
1880 r#"{
1881 "tabs": {
1882 "git_status": true
1883 }
1884 }
1885 "#
1886 .unindent(),
1887 cx,
1888 );
1889
1890 check_settings_update(
1891 &mut store,
1892 r#"{
1893 }
1894 "#
1895 .unindent(),
1896 |settings| settings.title_bar.get_or_insert_default().show_branch_name = Some(true),
1897 r#"{
1898 "title_bar": {
1899 "show_branch_name": true
1900 }
1901 }
1902 "#
1903 .unindent(),
1904 cx,
1905 );
1906 }
1907
1908 #[gpui::test]
1909 fn test_edits_for_update_preserves_unknown_keys(cx: &mut App) {
1910 let mut store = SettingsStore::new(cx, &test_settings());
1911 store.register_setting::<AutoUpdateSetting>();
1912
1913 let old_json = r#"{
1914 "some_unknown_key": "should_be_preserved",
1915 "auto_update": false
1916 }"#
1917 .unindent();
1918
1919 check_settings_update(
1920 &mut store,
1921 old_json,
1922 |settings| settings.auto_update = Some(true),
1923 r#"{
1924 "some_unknown_key": "should_be_preserved",
1925 "auto_update": true
1926 }"#
1927 .unindent(),
1928 cx,
1929 );
1930 }
1931
1932 #[gpui::test]
1933 fn test_edits_for_update_returns_error_on_invalid_json(cx: &mut App) {
1934 let store = SettingsStore::new(cx, &test_settings());
1935
1936 let invalid_json = r#"{ this is not valid json at all !!!"#;
1937 let result = store.edits_for_update(invalid_json, |_| {});
1938 assert!(result.is_err());
1939 }
1940
1941 #[gpui::test]
1942 fn test_vscode_import(cx: &mut App) {
1943 let mut store = SettingsStore::new(cx, &test_settings());
1944 store.register_setting::<DefaultLanguageSettings>();
1945 store.register_setting::<ItemSettings>();
1946 store.register_setting::<AutoUpdateSetting>();
1947 store.register_setting::<ThemeSettings>();
1948
1949 // create settings that werent present
1950 check_vscode_import(
1951 &mut store,
1952 r#"{
1953 }
1954 "#
1955 .unindent(),
1956 r#" { "editor.tabSize": 37 } "#.to_owned(),
1957 r#"{
1958 "base_keymap": "VSCode",
1959 "tab_size": 37
1960 }
1961 "#
1962 .unindent(),
1963 cx,
1964 );
1965
1966 // persist settings that were present
1967 check_vscode_import(
1968 &mut store,
1969 r#"{
1970 "preferred_line_length": 99,
1971 }
1972 "#
1973 .unindent(),
1974 r#"{ "editor.tabSize": 42 }"#.to_owned(),
1975 r#"{
1976 "base_keymap": "VSCode",
1977 "tab_size": 42,
1978 "preferred_line_length": 99,
1979 }
1980 "#
1981 .unindent(),
1982 cx,
1983 );
1984
1985 // don't clobber settings that aren't present in vscode
1986 check_vscode_import(
1987 &mut store,
1988 r#"{
1989 "preferred_line_length": 99,
1990 "tab_size": 42
1991 }
1992 "#
1993 .unindent(),
1994 r#"{}"#.to_owned(),
1995 r#"{
1996 "base_keymap": "VSCode",
1997 "preferred_line_length": 99,
1998 "tab_size": 42
1999 }
2000 "#
2001 .unindent(),
2002 cx,
2003 );
2004
2005 // custom enum
2006 check_vscode_import(
2007 &mut store,
2008 r#"{
2009 }
2010 "#
2011 .unindent(),
2012 r#"{ "git.decorations.enabled": true }"#.to_owned(),
2013 r#"{
2014 "project_panel": {
2015 "git_status": true
2016 },
2017 "outline_panel": {
2018 "git_status": true
2019 },
2020 "base_keymap": "VSCode",
2021 "tabs": {
2022 "git_status": true
2023 }
2024 }
2025 "#
2026 .unindent(),
2027 cx,
2028 );
2029
2030 // explorer sort settings
2031 check_vscode_import(
2032 &mut store,
2033 r#"{
2034 }
2035 "#
2036 .unindent(),
2037 r#"{
2038 "explorer.sortOrder": "mixed",
2039 "explorer.sortOrderLexicographicOptions": "lower"
2040 }"#
2041 .unindent(),
2042 r#"{
2043 "project_panel": {
2044 "sort_mode": "mixed",
2045 "sort_order": "lower"
2046 },
2047 "base_keymap": "VSCode"
2048 }
2049 "#
2050 .unindent(),
2051 cx,
2052 );
2053
2054 // font-family
2055 check_vscode_import(
2056 &mut store,
2057 r#"{
2058 }
2059 "#
2060 .unindent(),
2061 r#"{ "editor.fontFamily": "Cascadia Code, 'Consolas', Courier New" }"#.to_owned(),
2062 r#"{
2063 "base_keymap": "VSCode",
2064 "buffer_font_fallbacks": [
2065 "Consolas",
2066 "Courier New"
2067 ],
2068 "buffer_font_family": "Cascadia Code"
2069 }
2070 "#
2071 .unindent(),
2072 cx,
2073 );
2074 }
2075
2076 #[track_caller]
2077 fn check_vscode_import(
2078 store: &mut SettingsStore,
2079 old: String,
2080 vscode: String,
2081 expected: String,
2082 cx: &mut App,
2083 ) {
2084 store.set_user_settings(&old, cx).ok();
2085 let new = store
2086 .get_vscode_edits(
2087 old,
2088 &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
2089 )
2090 .unwrap();
2091 pretty_assertions::assert_eq!(new, expected);
2092 }
2093
2094 #[gpui::test]
2095 fn test_update_git_settings(cx: &mut App) {
2096 let store = SettingsStore::new(cx, &test_settings());
2097
2098 let actual = store
2099 .new_text_for_update("{}".to_string(), |current| {
2100 current
2101 .git
2102 .get_or_insert_default()
2103 .inline_blame
2104 .get_or_insert_default()
2105 .enabled = Some(true);
2106 })
2107 .unwrap();
2108 pretty_assertions::assert_str_eq!(
2109 actual,
2110 r#"{
2111 "git": {
2112 "inline_blame": {
2113 "enabled": true
2114 }
2115 }
2116 }
2117 "#
2118 .unindent()
2119 );
2120 }
2121
2122 #[gpui::test]
2123 fn test_global_settings(cx: &mut App) {
2124 let mut store = SettingsStore::new(cx, &test_settings());
2125 store.register_setting::<ItemSettings>();
2126
2127 // Set global settings - these should override defaults but not user settings
2128 store
2129 .set_global_settings(
2130 r#"{
2131 "tabs": {
2132 "close_position": "right",
2133 "git_status": true,
2134 }
2135 }"#,
2136 cx,
2137 )
2138 .unwrap();
2139
2140 // Before user settings, global settings should apply
2141 assert_eq!(
2142 store.get::<ItemSettings>(None),
2143 &ItemSettings {
2144 close_position: ClosePosition::Right,
2145 git_status: true,
2146 }
2147 );
2148
2149 // Set user settings - these should override both defaults and global
2150 store
2151 .set_user_settings(
2152 r#"{
2153 "tabs": {
2154 "close_position": "left"
2155 }
2156 }"#,
2157 cx,
2158 )
2159 .unwrap();
2160
2161 // User settings should override global settings
2162 assert_eq!(
2163 store.get::<ItemSettings>(None),
2164 &ItemSettings {
2165 close_position: ClosePosition::Left,
2166 git_status: true, // Staff from global settings
2167 }
2168 );
2169 }
2170
2171 #[gpui::test]
2172 fn test_get_value_for_field_basic(cx: &mut App) {
2173 let mut store = SettingsStore::new(cx, &test_settings());
2174 store.register_setting::<DefaultLanguageSettings>();
2175
2176 store
2177 .set_user_settings(r#"{"preferred_line_length": 0}"#, cx)
2178 .unwrap();
2179 let local = (WorktreeId::from_usize(0), RelPath::empty().into_arc());
2180 store
2181 .set_local_settings(
2182 local.0,
2183 LocalSettingsPath::InWorktree(local.1.clone()),
2184 LocalSettingsKind::Settings,
2185 Some(r#"{}"#),
2186 cx,
2187 )
2188 .unwrap();
2189
2190 fn get(content: &SettingsContent) -> Option<&u32> {
2191 content
2192 .project
2193 .all_languages
2194 .defaults
2195 .preferred_line_length
2196 .as_ref()
2197 }
2198
2199 let default_value = *get(&store.default_settings).unwrap();
2200
2201 assert_eq!(
2202 store.get_value_from_file(SettingsFile::Project(local.clone()), get),
2203 (SettingsFile::User, Some(&0))
2204 );
2205 assert_eq!(
2206 store.get_value_from_file(SettingsFile::User, get),
2207 (SettingsFile::User, Some(&0))
2208 );
2209 store.set_user_settings(r#"{}"#, cx).unwrap();
2210 assert_eq!(
2211 store.get_value_from_file(SettingsFile::Project(local.clone()), get),
2212 (SettingsFile::Default, Some(&default_value))
2213 );
2214 store
2215 .set_local_settings(
2216 local.0,
2217 LocalSettingsPath::InWorktree(local.1.clone()),
2218 LocalSettingsKind::Settings,
2219 Some(r#"{"preferred_line_length": 80}"#),
2220 cx,
2221 )
2222 .unwrap();
2223 assert_eq!(
2224 store.get_value_from_file(SettingsFile::Project(local.clone()), get),
2225 (SettingsFile::Project(local), Some(&80))
2226 );
2227 assert_eq!(
2228 store.get_value_from_file(SettingsFile::User, get),
2229 (SettingsFile::Default, Some(&default_value))
2230 );
2231 }
2232
2233 #[gpui::test]
2234 fn test_get_value_for_field_local_worktrees_dont_interfere(cx: &mut App) {
2235 let mut store = SettingsStore::new(cx, &test_settings());
2236 store.register_setting::<DefaultLanguageSettings>();
2237 store.register_setting::<AutoUpdateSetting>();
2238
2239 let local_1 = (WorktreeId::from_usize(0), RelPath::empty().into_arc());
2240
2241 let local_1_child = (
2242 WorktreeId::from_usize(0),
2243 RelPath::new(
2244 std::path::Path::new("child1"),
2245 util::paths::PathStyle::Posix,
2246 )
2247 .unwrap()
2248 .into_arc(),
2249 );
2250
2251 let local_2 = (WorktreeId::from_usize(1), RelPath::empty().into_arc());
2252 let local_2_child = (
2253 WorktreeId::from_usize(1),
2254 RelPath::new(
2255 std::path::Path::new("child2"),
2256 util::paths::PathStyle::Posix,
2257 )
2258 .unwrap()
2259 .into_arc(),
2260 );
2261
2262 fn get(content: &SettingsContent) -> Option<&u32> {
2263 content
2264 .project
2265 .all_languages
2266 .defaults
2267 .preferred_line_length
2268 .as_ref()
2269 }
2270
2271 store
2272 .set_local_settings(
2273 local_1.0,
2274 LocalSettingsPath::InWorktree(local_1.1.clone()),
2275 LocalSettingsKind::Settings,
2276 Some(r#"{"preferred_line_length": 1}"#),
2277 cx,
2278 )
2279 .unwrap();
2280 store
2281 .set_local_settings(
2282 local_1_child.0,
2283 LocalSettingsPath::InWorktree(local_1_child.1.clone()),
2284 LocalSettingsKind::Settings,
2285 Some(r#"{}"#),
2286 cx,
2287 )
2288 .unwrap();
2289 store
2290 .set_local_settings(
2291 local_2.0,
2292 LocalSettingsPath::InWorktree(local_2.1.clone()),
2293 LocalSettingsKind::Settings,
2294 Some(r#"{"preferred_line_length": 2}"#),
2295 cx,
2296 )
2297 .unwrap();
2298 store
2299 .set_local_settings(
2300 local_2_child.0,
2301 LocalSettingsPath::InWorktree(local_2_child.1.clone()),
2302 LocalSettingsKind::Settings,
2303 Some(r#"{}"#),
2304 cx,
2305 )
2306 .unwrap();
2307
2308 // each local child should only inherit from it's parent
2309 assert_eq!(
2310 store.get_value_from_file(SettingsFile::Project(local_2_child), get),
2311 (SettingsFile::Project(local_2), Some(&2))
2312 );
2313 assert_eq!(
2314 store.get_value_from_file(SettingsFile::Project(local_1_child.clone()), get),
2315 (SettingsFile::Project(local_1.clone()), Some(&1))
2316 );
2317
2318 // adjacent children should be treated as siblings not inherit from each other
2319 let local_1_adjacent_child = (local_1.0, rel_path("adjacent_child").into_arc());
2320 store
2321 .set_local_settings(
2322 local_1_adjacent_child.0,
2323 LocalSettingsPath::InWorktree(local_1_adjacent_child.1.clone()),
2324 LocalSettingsKind::Settings,
2325 Some(r#"{}"#),
2326 cx,
2327 )
2328 .unwrap();
2329 store
2330 .set_local_settings(
2331 local_1_child.0,
2332 LocalSettingsPath::InWorktree(local_1_child.1.clone()),
2333 LocalSettingsKind::Settings,
2334 Some(r#"{"preferred_line_length": 3}"#),
2335 cx,
2336 )
2337 .unwrap();
2338
2339 assert_eq!(
2340 store.get_value_from_file(SettingsFile::Project(local_1_adjacent_child.clone()), get),
2341 (SettingsFile::Project(local_1.clone()), Some(&1))
2342 );
2343 store
2344 .set_local_settings(
2345 local_1_adjacent_child.0,
2346 LocalSettingsPath::InWorktree(local_1_adjacent_child.1),
2347 LocalSettingsKind::Settings,
2348 Some(r#"{"preferred_line_length": 3}"#),
2349 cx,
2350 )
2351 .unwrap();
2352 store
2353 .set_local_settings(
2354 local_1_child.0,
2355 LocalSettingsPath::InWorktree(local_1_child.1.clone()),
2356 LocalSettingsKind::Settings,
2357 Some(r#"{}"#),
2358 cx,
2359 )
2360 .unwrap();
2361 assert_eq!(
2362 store.get_value_from_file(SettingsFile::Project(local_1_child), get),
2363 (SettingsFile::Project(local_1), Some(&1))
2364 );
2365 }
2366
2367 #[gpui::test]
2368 fn test_get_overrides_for_field(cx: &mut App) {
2369 let mut store = SettingsStore::new(cx, &test_settings());
2370 store.register_setting::<DefaultLanguageSettings>();
2371
2372 let wt0_root = (WorktreeId::from_usize(0), RelPath::empty().into_arc());
2373 let wt0_child1 = (WorktreeId::from_usize(0), rel_path("child1").into_arc());
2374 let wt0_child2 = (WorktreeId::from_usize(0), rel_path("child2").into_arc());
2375
2376 let wt1_root = (WorktreeId::from_usize(1), RelPath::empty().into_arc());
2377 let wt1_subdir = (WorktreeId::from_usize(1), rel_path("subdir").into_arc());
2378
2379 fn get(content: &SettingsContent) -> &Option<u32> {
2380 &content.project.all_languages.defaults.preferred_line_length
2381 }
2382
2383 store
2384 .set_user_settings(r#"{"preferred_line_length": 100}"#, cx)
2385 .unwrap();
2386
2387 store
2388 .set_local_settings(
2389 wt0_root.0,
2390 LocalSettingsPath::InWorktree(wt0_root.1.clone()),
2391 LocalSettingsKind::Settings,
2392 Some(r#"{"preferred_line_length": 80}"#),
2393 cx,
2394 )
2395 .unwrap();
2396 store
2397 .set_local_settings(
2398 wt0_child1.0,
2399 LocalSettingsPath::InWorktree(wt0_child1.1.clone()),
2400 LocalSettingsKind::Settings,
2401 Some(r#"{"preferred_line_length": 120}"#),
2402 cx,
2403 )
2404 .unwrap();
2405 store
2406 .set_local_settings(
2407 wt0_child2.0,
2408 LocalSettingsPath::InWorktree(wt0_child2.1.clone()),
2409 LocalSettingsKind::Settings,
2410 Some(r#"{}"#),
2411 cx,
2412 )
2413 .unwrap();
2414
2415 store
2416 .set_local_settings(
2417 wt1_root.0,
2418 LocalSettingsPath::InWorktree(wt1_root.1.clone()),
2419 LocalSettingsKind::Settings,
2420 Some(r#"{"preferred_line_length": 90}"#),
2421 cx,
2422 )
2423 .unwrap();
2424 store
2425 .set_local_settings(
2426 wt1_subdir.0,
2427 LocalSettingsPath::InWorktree(wt1_subdir.1.clone()),
2428 LocalSettingsKind::Settings,
2429 Some(r#"{}"#),
2430 cx,
2431 )
2432 .unwrap();
2433
2434 let overrides = store.get_overrides_for_field(SettingsFile::Default, get);
2435 assert_eq!(
2436 overrides,
2437 vec![
2438 SettingsFile::User,
2439 SettingsFile::Project(wt0_root.clone()),
2440 SettingsFile::Project(wt0_child1.clone()),
2441 SettingsFile::Project(wt1_root.clone()),
2442 ]
2443 );
2444
2445 let overrides = store.get_overrides_for_field(SettingsFile::User, get);
2446 assert_eq!(
2447 overrides,
2448 vec![
2449 SettingsFile::Project(wt0_root.clone()),
2450 SettingsFile::Project(wt0_child1.clone()),
2451 SettingsFile::Project(wt1_root.clone()),
2452 ]
2453 );
2454
2455 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_root), get);
2456 assert_eq!(overrides, vec![]);
2457
2458 let overrides =
2459 store.get_overrides_for_field(SettingsFile::Project(wt0_child1.clone()), get);
2460 assert_eq!(overrides, vec![]);
2461
2462 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_child2), get);
2463 assert_eq!(overrides, vec![]);
2464
2465 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt1_root), get);
2466 assert_eq!(overrides, vec![]);
2467
2468 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt1_subdir), get);
2469 assert_eq!(overrides, vec![]);
2470
2471 let wt0_deep_child = (
2472 WorktreeId::from_usize(0),
2473 rel_path("child1/subdir").into_arc(),
2474 );
2475 store
2476 .set_local_settings(
2477 wt0_deep_child.0,
2478 LocalSettingsPath::InWorktree(wt0_deep_child.1.clone()),
2479 LocalSettingsKind::Settings,
2480 Some(r#"{"preferred_line_length": 140}"#),
2481 cx,
2482 )
2483 .unwrap();
2484
2485 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_deep_child), get);
2486 assert_eq!(overrides, vec![]);
2487
2488 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_child1), get);
2489 assert_eq!(overrides, vec![]);
2490 }
2491
2492 #[test]
2493 fn test_file_ord() {
2494 let wt0_root =
2495 SettingsFile::Project((WorktreeId::from_usize(0), RelPath::empty().into_arc()));
2496 let wt0_child1 =
2497 SettingsFile::Project((WorktreeId::from_usize(0), rel_path("child1").into_arc()));
2498 let wt0_child2 =
2499 SettingsFile::Project((WorktreeId::from_usize(0), rel_path("child2").into_arc()));
2500
2501 let wt1_root =
2502 SettingsFile::Project((WorktreeId::from_usize(1), RelPath::empty().into_arc()));
2503 let wt1_subdir =
2504 SettingsFile::Project((WorktreeId::from_usize(1), rel_path("subdir").into_arc()));
2505
2506 let mut files = vec![
2507 &wt1_root,
2508 &SettingsFile::Default,
2509 &wt0_root,
2510 &wt1_subdir,
2511 &wt0_child2,
2512 &SettingsFile::Server,
2513 &wt0_child1,
2514 &SettingsFile::User,
2515 ];
2516
2517 files.sort();
2518 pretty_assertions::assert_eq!(
2519 files,
2520 vec![
2521 &wt0_child2,
2522 &wt0_child1,
2523 &wt0_root,
2524 &wt1_subdir,
2525 &wt1_root,
2526 &SettingsFile::Server,
2527 &SettingsFile::User,
2528 &SettingsFile::Default,
2529 ]
2530 )
2531 }
2532
2533 #[gpui::test]
2534 fn test_lsp_settings_schema_generation(cx: &mut App) {
2535 SettingsStore::test(cx);
2536
2537 let schema = SettingsStore::json_schema(&SettingsJsonSchemaParams {
2538 language_names: &["Rust".to_string(), "TypeScript".to_string()],
2539 font_names: &["Zed Mono".to_string()],
2540 theme_names: &["One Dark".into()],
2541 icon_theme_names: &["Zed Icons".into()],
2542 lsp_adapter_names: &[
2543 "rust-analyzer".to_string(),
2544 "typescript-language-server".to_string(),
2545 ],
2546 });
2547
2548 let properties = schema
2549 .pointer("/$defs/LspSettingsMap/properties")
2550 .expect("LspSettingsMap should have properties")
2551 .as_object()
2552 .unwrap();
2553
2554 assert!(properties.contains_key("rust-analyzer"));
2555 assert!(properties.contains_key("typescript-language-server"));
2556
2557 let init_options_ref = properties
2558 .get("rust-analyzer")
2559 .unwrap()
2560 .pointer("/properties/initialization_options/$ref")
2561 .expect("initialization_options should have a $ref")
2562 .as_str()
2563 .unwrap();
2564
2565 assert_eq!(
2566 init_options_ref,
2567 "zed://schemas/settings/lsp/rust-analyzer/initialization_options"
2568 );
2569
2570 let settings_ref = properties
2571 .get("rust-analyzer")
2572 .unwrap()
2573 .pointer("/properties/settings/$ref")
2574 .expect("settings should have a $ref")
2575 .as_str()
2576 .unwrap();
2577
2578 assert_eq!(
2579 settings_ref,
2580 "zed://schemas/settings/lsp/rust-analyzer/settings"
2581 );
2582 }
2583
2584 #[gpui::test]
2585 fn test_lsp_project_settings_schema_generation(cx: &mut App) {
2586 SettingsStore::test(cx);
2587
2588 let schema = SettingsStore::project_json_schema(&SettingsJsonSchemaParams {
2589 language_names: &["Rust".to_string(), "TypeScript".to_string()],
2590 font_names: &["Zed Mono".to_string()],
2591 theme_names: &["One Dark".into()],
2592 icon_theme_names: &["Zed Icons".into()],
2593 lsp_adapter_names: &[
2594 "rust-analyzer".to_string(),
2595 "typescript-language-server".to_string(),
2596 ],
2597 });
2598
2599 let properties = schema
2600 .pointer("/$defs/LspSettingsMap/properties")
2601 .expect("LspSettingsMap should have properties")
2602 .as_object()
2603 .unwrap();
2604
2605 assert!(properties.contains_key("rust-analyzer"));
2606 assert!(properties.contains_key("typescript-language-server"));
2607
2608 let init_options_ref = properties
2609 .get("rust-analyzer")
2610 .unwrap()
2611 .pointer("/properties/initialization_options/$ref")
2612 .expect("initialization_options should have a $ref")
2613 .as_str()
2614 .unwrap();
2615
2616 assert_eq!(
2617 init_options_ref,
2618 "zed://schemas/settings/lsp/rust-analyzer/initialization_options"
2619 );
2620
2621 let settings_ref = properties
2622 .get("rust-analyzer")
2623 .unwrap()
2624 .pointer("/properties/settings/$ref")
2625 .expect("settings should have a $ref")
2626 .as_str()
2627 .unwrap();
2628
2629 assert_eq!(
2630 settings_ref,
2631 "zed://schemas/settings/lsp/rust-analyzer/settings"
2632 );
2633 }
2634
2635 #[gpui::test]
2636 fn test_project_json_schema_differs_from_user_schema(cx: &mut App) {
2637 SettingsStore::test(cx);
2638
2639 let params = SettingsJsonSchemaParams {
2640 language_names: &["Rust".to_string()],
2641 font_names: &["Zed Mono".to_string()],
2642 theme_names: &["One Dark".into()],
2643 icon_theme_names: &["Zed Icons".into()],
2644 lsp_adapter_names: &["rust-analyzer".to_string()],
2645 };
2646
2647 let user_schema = SettingsStore::json_schema(¶ms);
2648 let project_schema = SettingsStore::project_json_schema(¶ms);
2649
2650 assert_ne!(user_schema, project_schema);
2651
2652 let user_schema_str = serde_json::to_string(&user_schema).unwrap();
2653 let project_schema_str = serde_json::to_string(&project_schema).unwrap();
2654
2655 assert!(user_schema_str.contains("\"auto_update\""));
2656 assert!(!project_schema_str.contains("\"auto_update\""));
2657 }
2658}