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