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