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