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