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