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