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