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