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