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 /// Represents project settings in ssh projects as well as local projects
166 Project((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::Project),
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 pub 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::Project(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::Project((wt_id, ref path)) = file
519 && let SettingsFile::Project((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 /// Checks the given file, and files that the passed file overrides for the given field.
538 /// Returns the first file found that contains the value.
539 /// The value will only be None if no file contains the value.
540 /// I.e. if no file contains the value, returns `(File::Default, None)`
541 pub fn get_value_from_file<T>(
542 &self,
543 target_file: SettingsFile,
544 pick: fn(&SettingsContent) -> &Option<T>,
545 ) -> (SettingsFile, Option<&T>) {
546 self.get_value_from_file_inner(target_file, pick, true)
547 }
548
549 /// Same as `Self::get_value_from_file` except that it does not include the current file.
550 /// Therefore it returns the value that was potentially overloaded by the target file.
551 pub fn get_value_up_to_file<T>(
552 &self,
553 target_file: SettingsFile,
554 pick: fn(&SettingsContent) -> &Option<T>,
555 ) -> (SettingsFile, Option<&T>) {
556 self.get_value_from_file_inner(target_file, pick, false)
557 }
558
559 fn get_value_from_file_inner<T>(
560 &self,
561 target_file: SettingsFile,
562 pick: fn(&SettingsContent) -> &Option<T>,
563 include_target_file: bool,
564 ) -> (SettingsFile, Option<&T>) {
565 // todo(settings_ui): Add a metadata field for overriding the "overrides" tag, for contextually different settings
566 // e.g. disable AI isn't overridden, or a vec that gets extended instead or some such
567
568 // todo(settings_ui) cache all files
569 let all_files = self.get_all_files();
570 let mut found_file = false;
571
572 for file in all_files.into_iter() {
573 if !found_file && file != SettingsFile::Default {
574 if file != target_file {
575 continue;
576 }
577 found_file = true;
578 if !include_target_file {
579 continue;
580 }
581 }
582
583 if let SettingsFile::Project((worktree_id, ref path)) = file
584 && let SettingsFile::Project((target_worktree_id, ref target_path)) = target_file
585 && (worktree_id != target_worktree_id || !target_path.starts_with(&path))
586 {
587 // if requesting value from a local file, don't return values from local files in different worktrees
588 continue;
589 }
590
591 let Some(content) = self.get_content_for_file(file.clone()) else {
592 continue;
593 };
594 if let Some(value) = pick(content).as_ref() {
595 return (file, Some(value));
596 }
597 }
598
599 (SettingsFile::Default, None)
600 }
601}
602
603impl SettingsStore {
604 /// Updates the value of a setting in a JSON file, returning the new text
605 /// for that JSON file.
606 pub fn new_text_for_update(
607 &self,
608 old_text: String,
609 update: impl FnOnce(&mut SettingsContent),
610 ) -> String {
611 let edits = self.edits_for_update(&old_text, update);
612 let mut new_text = old_text;
613 for (range, replacement) in edits.into_iter() {
614 new_text.replace_range(range, &replacement);
615 }
616 new_text
617 }
618
619 pub fn get_vscode_edits(&self, old_text: String, vscode: &VsCodeSettings) -> String {
620 self.new_text_for_update(old_text, |settings_content| {
621 for v in self.setting_values.values() {
622 v.import_from_vscode(vscode, settings_content)
623 }
624 })
625 }
626
627 /// Updates the value of a setting in a JSON file, returning a list
628 /// of edits to apply to the JSON file.
629 pub fn edits_for_update(
630 &self,
631 text: &str,
632 update: impl FnOnce(&mut SettingsContent),
633 ) -> Vec<(Range<usize>, String)> {
634 let old_content: UserSettingsContent =
635 parse_json_with_comments(text).log_err().unwrap_or_default();
636 let mut new_content = old_content.clone();
637 update(&mut new_content.content);
638
639 let old_value = serde_json::to_value(&old_content).unwrap();
640 let new_value = serde_json::to_value(new_content).unwrap();
641
642 let mut key_path = Vec::new();
643 let mut edits = Vec::new();
644 let tab_size = self.json_tab_size();
645 let mut text = text.to_string();
646 update_value_in_json_text(
647 &mut text,
648 &mut key_path,
649 tab_size,
650 &old_value,
651 &new_value,
652 &mut edits,
653 );
654 edits
655 }
656
657 pub fn json_tab_size(&self) -> usize {
658 2
659 }
660
661 /// Sets the default settings via a JSON string.
662 ///
663 /// The string should contain a JSON object with a default value for every setting.
664 pub fn set_default_settings(
665 &mut self,
666 default_settings_content: &str,
667 cx: &mut App,
668 ) -> Result<()> {
669 self.default_settings = parse_json_with_comments(default_settings_content)?;
670 self.recompute_values(None, cx)?;
671 Ok(())
672 }
673
674 /// Sets the user settings via a JSON string.
675 pub fn set_user_settings(&mut self, user_settings_content: &str, cx: &mut App) -> Result<()> {
676 let settings: UserSettingsContent = if user_settings_content.is_empty() {
677 parse_json_with_comments("{}")?
678 } else {
679 parse_json_with_comments(user_settings_content)?
680 };
681
682 self.user_settings = Some(settings);
683 self.recompute_values(None, cx)?;
684 Ok(())
685 }
686
687 /// Sets the global settings via a JSON string.
688 pub fn set_global_settings(
689 &mut self,
690 global_settings_content: &str,
691 cx: &mut App,
692 ) -> Result<()> {
693 let settings: SettingsContent = if global_settings_content.is_empty() {
694 parse_json_with_comments("{}")?
695 } else {
696 parse_json_with_comments(global_settings_content)?
697 };
698
699 self.global_settings = Some(Box::new(settings));
700 self.recompute_values(None, cx)?;
701 Ok(())
702 }
703
704 pub fn set_server_settings(
705 &mut self,
706 server_settings_content: &str,
707 cx: &mut App,
708 ) -> Result<()> {
709 let settings: Option<SettingsContent> = if server_settings_content.is_empty() {
710 None
711 } else {
712 parse_json_with_comments(server_settings_content)?
713 };
714
715 // Rewrite the server settings into a content type
716 self.server_settings = settings.map(|settings| Box::new(settings));
717
718 self.recompute_values(None, cx)?;
719 Ok(())
720 }
721
722 /// Add or remove a set of local settings via a JSON string.
723 pub fn set_local_settings(
724 &mut self,
725 root_id: WorktreeId,
726 directory_path: Arc<RelPath>,
727 kind: LocalSettingsKind,
728 settings_content: Option<&str>,
729 cx: &mut App,
730 ) -> std::result::Result<(), InvalidSettingsError> {
731 let mut zed_settings_changed = false;
732 match (
733 kind,
734 settings_content
735 .map(|content| content.trim())
736 .filter(|content| !content.is_empty()),
737 ) {
738 (LocalSettingsKind::Tasks, _) => {
739 return Err(InvalidSettingsError::Tasks {
740 message: "Attempted to submit tasks into the settings store".to_string(),
741 path: directory_path
742 .join(RelPath::unix(task_file_name()).unwrap())
743 .as_std_path()
744 .to_path_buf(),
745 });
746 }
747 (LocalSettingsKind::Debug, _) => {
748 return Err(InvalidSettingsError::Debug {
749 message: "Attempted to submit debugger config into the settings store"
750 .to_string(),
751 path: directory_path
752 .join(RelPath::unix(task_file_name()).unwrap())
753 .as_std_path()
754 .to_path_buf(),
755 });
756 }
757 (LocalSettingsKind::Settings, None) => {
758 zed_settings_changed = self
759 .local_settings
760 .remove(&(root_id, directory_path.clone()))
761 .is_some()
762 }
763 (LocalSettingsKind::Editorconfig, None) => {
764 self.raw_editorconfig_settings
765 .remove(&(root_id, directory_path.clone()));
766 }
767 (LocalSettingsKind::Settings, Some(settings_contents)) => {
768 let new_settings = parse_json_with_comments::<ProjectSettingsContent>(
769 settings_contents,
770 )
771 .map_err(|e| InvalidSettingsError::LocalSettings {
772 path: directory_path.join(local_settings_file_relative_path()),
773 message: e.to_string(),
774 })?;
775 match self.local_settings.entry((root_id, directory_path.clone())) {
776 btree_map::Entry::Vacant(v) => {
777 v.insert(SettingsContent {
778 project: new_settings,
779 ..Default::default()
780 });
781 zed_settings_changed = true;
782 }
783 btree_map::Entry::Occupied(mut o) => {
784 if &o.get().project != &new_settings {
785 o.insert(SettingsContent {
786 project: new_settings,
787 ..Default::default()
788 });
789 zed_settings_changed = true;
790 }
791 }
792 }
793 }
794 (LocalSettingsKind::Editorconfig, Some(editorconfig_contents)) => {
795 match self
796 .raw_editorconfig_settings
797 .entry((root_id, directory_path.clone()))
798 {
799 btree_map::Entry::Vacant(v) => match editorconfig_contents.parse() {
800 Ok(new_contents) => {
801 v.insert((editorconfig_contents.to_owned(), Some(new_contents)));
802 }
803 Err(e) => {
804 v.insert((editorconfig_contents.to_owned(), None));
805 return Err(InvalidSettingsError::Editorconfig {
806 message: e.to_string(),
807 path: directory_path
808 .join(RelPath::unix(EDITORCONFIG_NAME).unwrap()),
809 });
810 }
811 },
812 btree_map::Entry::Occupied(mut o) => {
813 if o.get().0 != editorconfig_contents {
814 match editorconfig_contents.parse() {
815 Ok(new_contents) => {
816 o.insert((
817 editorconfig_contents.to_owned(),
818 Some(new_contents),
819 ));
820 }
821 Err(e) => {
822 o.insert((editorconfig_contents.to_owned(), None));
823 return Err(InvalidSettingsError::Editorconfig {
824 message: e.to_string(),
825 path: directory_path
826 .join(RelPath::unix(EDITORCONFIG_NAME).unwrap()),
827 });
828 }
829 }
830 }
831 }
832 }
833 }
834 };
835
836 if zed_settings_changed {
837 self.recompute_values(Some((root_id, &directory_path)), cx)?;
838 }
839 Ok(())
840 }
841
842 pub fn set_extension_settings(
843 &mut self,
844 content: ExtensionsSettingsContent,
845 cx: &mut App,
846 ) -> Result<()> {
847 self.extension_settings = Some(Box::new(SettingsContent {
848 project: ProjectSettingsContent {
849 all_languages: content.all_languages,
850 ..Default::default()
851 },
852 ..Default::default()
853 }));
854 self.recompute_values(None, cx)?;
855 Ok(())
856 }
857
858 /// Add or remove a set of local settings via a JSON string.
859 pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut App) -> Result<()> {
860 self.local_settings
861 .retain(|(worktree_id, _), _| worktree_id != &root_id);
862 self.recompute_values(Some((root_id, RelPath::empty())), cx)?;
863 Ok(())
864 }
865
866 pub fn local_settings(
867 &self,
868 root_id: WorktreeId,
869 ) -> impl '_ + Iterator<Item = (Arc<RelPath>, &ProjectSettingsContent)> {
870 self.local_settings
871 .range(
872 (root_id, RelPath::empty().into())
873 ..(
874 WorktreeId::from_usize(root_id.to_usize() + 1),
875 RelPath::empty().into(),
876 ),
877 )
878 .map(|((_, path), content)| (path.clone(), &content.project))
879 }
880
881 pub fn local_editorconfig_settings(
882 &self,
883 root_id: WorktreeId,
884 ) -> impl '_ + Iterator<Item = (Arc<RelPath>, String, Option<Editorconfig>)> {
885 self.raw_editorconfig_settings
886 .range(
887 (root_id, RelPath::empty().into())
888 ..(
889 WorktreeId::from_usize(root_id.to_usize() + 1),
890 RelPath::empty().into(),
891 ),
892 )
893 .map(|((_, path), (content, parsed_content))| {
894 (path.clone(), content.clone(), parsed_content.clone())
895 })
896 }
897
898 pub fn json_schema(&self, params: &SettingsJsonSchemaParams) -> Value {
899 let mut generator = schemars::generate::SchemaSettings::draft2019_09()
900 .with_transform(DefaultDenyUnknownFields)
901 .into_generator();
902
903 UserSettingsContent::json_schema(&mut generator);
904
905 let language_settings_content_ref = generator
906 .subschema_for::<LanguageSettingsContent>()
907 .to_value();
908
909 replace_subschema::<LanguageToSettingsMap>(&mut generator, || {
910 json_schema!({
911 "type": "object",
912 "properties": params
913 .language_names
914 .iter()
915 .map(|name| {
916 (
917 name.clone(),
918 language_settings_content_ref.clone(),
919 )
920 })
921 .collect::<serde_json::Map<_, _>>(),
922 "errorMessage": "No language with this name is installed."
923 })
924 });
925
926 replace_subschema::<FontFamilyName>(&mut generator, || {
927 json_schema!({
928 "type": "string",
929 "enum": params.font_names,
930 })
931 });
932
933 replace_subschema::<ThemeName>(&mut generator, || {
934 json_schema!({
935 "type": "string",
936 "enum": params.theme_names,
937 })
938 });
939
940 replace_subschema::<IconThemeName>(&mut generator, || {
941 json_schema!({
942 "type": "string",
943 "enum": params.icon_theme_names,
944 })
945 });
946
947 generator
948 .root_schema_for::<UserSettingsContent>()
949 .to_value()
950 }
951
952 fn recompute_values(
953 &mut self,
954 changed_local_path: Option<(WorktreeId, &RelPath)>,
955 cx: &mut App,
956 ) -> std::result::Result<(), InvalidSettingsError> {
957 // Reload the global and local values for every setting.
958 let mut project_settings_stack = Vec::<SettingsContent>::new();
959 let mut paths_stack = Vec::<Option<(WorktreeId, &RelPath)>>::new();
960
961 if changed_local_path.is_none() {
962 let mut merged = self.default_settings.as_ref().clone();
963 merged.merge_from_option(self.extension_settings.as_deref());
964 merged.merge_from_option(self.global_settings.as_deref());
965 if let Some(user_settings) = self.user_settings.as_ref() {
966 merged.merge_from(&user_settings.content);
967 merged.merge_from_option(user_settings.for_release_channel());
968 merged.merge_from_option(user_settings.for_os());
969 merged.merge_from_option(user_settings.for_profile(cx));
970 }
971 merged.merge_from_option(self.server_settings.as_deref());
972 self.merged_settings = Rc::new(merged);
973
974 for setting_value in self.setting_values.values_mut() {
975 let value = setting_value.from_settings(&self.merged_settings, cx);
976 setting_value.set_global_value(value);
977 }
978 }
979
980 for ((root_id, directory_path), local_settings) in &self.local_settings {
981 // Build a stack of all of the local values for that setting.
982 while let Some(prev_entry) = paths_stack.last() {
983 if let Some((prev_root_id, prev_path)) = prev_entry
984 && (root_id != prev_root_id || !directory_path.starts_with(prev_path))
985 {
986 paths_stack.pop();
987 project_settings_stack.pop();
988 continue;
989 }
990 break;
991 }
992
993 paths_stack.push(Some((*root_id, directory_path.as_ref())));
994 let mut merged_local_settings = if let Some(deepest) = project_settings_stack.last() {
995 (*deepest).clone()
996 } else {
997 self.merged_settings.as_ref().clone()
998 };
999 merged_local_settings.merge_from(local_settings);
1000
1001 project_settings_stack.push(merged_local_settings);
1002
1003 // If a local settings file changed, then avoid recomputing local
1004 // settings for any path outside of that directory.
1005 if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| {
1006 *root_id != changed_root_id || !directory_path.starts_with(changed_local_path)
1007 }) {
1008 continue;
1009 }
1010
1011 for setting_value in self.setting_values.values_mut() {
1012 let value =
1013 setting_value.from_settings(&project_settings_stack.last().unwrap(), cx);
1014 setting_value.set_local_value(*root_id, directory_path.clone(), value);
1015 }
1016 }
1017 Ok(())
1018 }
1019
1020 pub fn editorconfig_properties(
1021 &self,
1022 for_worktree: WorktreeId,
1023 for_path: &RelPath,
1024 ) -> Option<EditorconfigProperties> {
1025 let mut properties = EditorconfigProperties::new();
1026
1027 for (directory_with_config, _, parsed_editorconfig) in
1028 self.local_editorconfig_settings(for_worktree)
1029 {
1030 if !for_path.starts_with(&directory_with_config) {
1031 properties.use_fallbacks();
1032 return Some(properties);
1033 }
1034 let parsed_editorconfig = parsed_editorconfig?;
1035 if parsed_editorconfig.is_root {
1036 properties = EditorconfigProperties::new();
1037 }
1038 for section in parsed_editorconfig.sections {
1039 section
1040 .apply_to(&mut properties, for_path.as_std_path())
1041 .log_err()?;
1042 }
1043 }
1044
1045 properties.use_fallbacks();
1046 Some(properties)
1047 }
1048}
1049
1050#[derive(Debug, Clone, PartialEq)]
1051pub enum InvalidSettingsError {
1052 LocalSettings { path: Arc<RelPath>, message: String },
1053 UserSettings { message: String },
1054 ServerSettings { message: String },
1055 DefaultSettings { message: String },
1056 Editorconfig { path: Arc<RelPath>, message: String },
1057 Tasks { path: PathBuf, message: String },
1058 Debug { path: PathBuf, message: String },
1059}
1060
1061impl std::fmt::Display for InvalidSettingsError {
1062 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1063 match self {
1064 InvalidSettingsError::LocalSettings { message, .. }
1065 | InvalidSettingsError::UserSettings { message }
1066 | InvalidSettingsError::ServerSettings { message }
1067 | InvalidSettingsError::DefaultSettings { message }
1068 | InvalidSettingsError::Tasks { message, .. }
1069 | InvalidSettingsError::Editorconfig { message, .. }
1070 | InvalidSettingsError::Debug { message, .. } => {
1071 write!(f, "{message}")
1072 }
1073 }
1074 }
1075}
1076impl std::error::Error for InvalidSettingsError {}
1077
1078impl Debug for SettingsStore {
1079 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1080 f.debug_struct("SettingsStore")
1081 .field(
1082 "types",
1083 &self
1084 .setting_values
1085 .values()
1086 .map(|value| value.setting_type_name())
1087 .collect::<Vec<_>>(),
1088 )
1089 .field("default_settings", &self.default_settings)
1090 .field("user_settings", &self.user_settings)
1091 .field("local_settings", &self.local_settings)
1092 .finish_non_exhaustive()
1093 }
1094}
1095
1096impl<T: Settings> AnySettingValue for SettingValue<T> {
1097 fn from_settings(&self, s: &SettingsContent, cx: &mut App) -> Box<dyn Any> {
1098 Box::new(T::from_settings(s, cx)) as _
1099 }
1100
1101 fn setting_type_name(&self) -> &'static str {
1102 type_name::<T>()
1103 }
1104
1105 fn all_local_values(&self) -> Vec<(WorktreeId, Arc<RelPath>, &dyn Any)> {
1106 self.local_values
1107 .iter()
1108 .map(|(id, path, value)| (*id, path.clone(), value as _))
1109 .collect()
1110 }
1111
1112 fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
1113 if let Some(SettingsLocation { worktree_id, path }) = path {
1114 for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
1115 if worktree_id == *settings_root_id && path.starts_with(settings_path) {
1116 return value;
1117 }
1118 }
1119 }
1120
1121 self.global_value
1122 .as_ref()
1123 .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
1124 }
1125
1126 fn set_global_value(&mut self, value: Box<dyn Any>) {
1127 self.global_value = Some(*value.downcast().unwrap());
1128 }
1129
1130 fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<RelPath>, value: Box<dyn Any>) {
1131 let value = *value.downcast().unwrap();
1132 match self
1133 .local_values
1134 .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
1135 {
1136 Ok(ix) => self.local_values[ix].2 = value,
1137 Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
1138 }
1139 }
1140
1141 fn import_from_vscode(
1142 &self,
1143 vscode_settings: &VsCodeSettings,
1144 settings_content: &mut SettingsContent,
1145 ) {
1146 T::import_from_vscode(vscode_settings, settings_content);
1147 }
1148}
1149
1150#[cfg(test)]
1151mod tests {
1152 use std::num::NonZeroU32;
1153
1154 use crate::{
1155 ClosePosition, ItemSettingsContent, VsCodeSettingsSource, default_settings,
1156 settings_content::LanguageSettingsContent, test_settings,
1157 };
1158
1159 use super::*;
1160 use unindent::Unindent;
1161 use util::rel_path::rel_path;
1162
1163 #[derive(Debug, PartialEq)]
1164 struct AutoUpdateSetting {
1165 auto_update: bool,
1166 }
1167
1168 impl Settings for AutoUpdateSetting {
1169 fn from_settings(content: &SettingsContent, _: &mut App) -> Self {
1170 AutoUpdateSetting {
1171 auto_update: content.auto_update.unwrap(),
1172 }
1173 }
1174 }
1175
1176 #[derive(Debug, PartialEq)]
1177 struct ItemSettings {
1178 close_position: ClosePosition,
1179 git_status: bool,
1180 }
1181
1182 impl Settings for ItemSettings {
1183 fn from_settings(content: &SettingsContent, _: &mut App) -> Self {
1184 let content = content.tabs.clone().unwrap();
1185 ItemSettings {
1186 close_position: content.close_position.unwrap(),
1187 git_status: content.git_status.unwrap(),
1188 }
1189 }
1190
1191 fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
1192 let mut show = None;
1193
1194 vscode.bool_setting("workbench.editor.decorations.colors", &mut show);
1195 if let Some(show) = show {
1196 content
1197 .tabs
1198 .get_or_insert_default()
1199 .git_status
1200 .replace(show);
1201 }
1202 }
1203 }
1204
1205 #[derive(Debug, PartialEq)]
1206 struct DefaultLanguageSettings {
1207 tab_size: NonZeroU32,
1208 preferred_line_length: u32,
1209 }
1210
1211 impl Settings for DefaultLanguageSettings {
1212 fn from_settings(content: &SettingsContent, _: &mut App) -> Self {
1213 let content = &content.project.all_languages.defaults;
1214 DefaultLanguageSettings {
1215 tab_size: content.tab_size.unwrap(),
1216 preferred_line_length: content.preferred_line_length.unwrap(),
1217 }
1218 }
1219
1220 fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
1221 let content = &mut content.project.all_languages.defaults;
1222
1223 if let Some(size) = vscode
1224 .read_value("editor.tabSize")
1225 .and_then(|v| v.as_u64())
1226 .and_then(|n| NonZeroU32::new(n as u32))
1227 {
1228 content.tab_size = Some(size);
1229 }
1230 }
1231 }
1232
1233 #[gpui::test]
1234 fn test_settings_store_basic(cx: &mut App) {
1235 let mut store = SettingsStore::new(cx, &default_settings());
1236 store.register_setting::<AutoUpdateSetting>(cx);
1237 store.register_setting::<ItemSettings>(cx);
1238 store.register_setting::<DefaultLanguageSettings>(cx);
1239
1240 assert_eq!(
1241 store.get::<AutoUpdateSetting>(None),
1242 &AutoUpdateSetting { auto_update: true }
1243 );
1244 assert_eq!(
1245 store.get::<ItemSettings>(None).close_position,
1246 ClosePosition::Right
1247 );
1248
1249 store
1250 .set_user_settings(
1251 r#"{
1252 "auto_update": false,
1253 "tabs": {
1254 "close_position": "left"
1255 }
1256 }"#,
1257 cx,
1258 )
1259 .unwrap();
1260
1261 assert_eq!(
1262 store.get::<AutoUpdateSetting>(None),
1263 &AutoUpdateSetting { auto_update: false }
1264 );
1265 assert_eq!(
1266 store.get::<ItemSettings>(None).close_position,
1267 ClosePosition::Left
1268 );
1269
1270 store
1271 .set_local_settings(
1272 WorktreeId::from_usize(1),
1273 rel_path("root1").into(),
1274 LocalSettingsKind::Settings,
1275 Some(r#"{ "tab_size": 5 }"#),
1276 cx,
1277 )
1278 .unwrap();
1279 store
1280 .set_local_settings(
1281 WorktreeId::from_usize(1),
1282 rel_path("root1/subdir").into(),
1283 LocalSettingsKind::Settings,
1284 Some(r#"{ "preferred_line_length": 50 }"#),
1285 cx,
1286 )
1287 .unwrap();
1288
1289 store
1290 .set_local_settings(
1291 WorktreeId::from_usize(1),
1292 rel_path("root2").into(),
1293 LocalSettingsKind::Settings,
1294 Some(r#"{ "tab_size": 9, "auto_update": true}"#),
1295 cx,
1296 )
1297 .unwrap();
1298
1299 assert_eq!(
1300 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1301 worktree_id: WorktreeId::from_usize(1),
1302 path: rel_path("root1/something"),
1303 })),
1304 &DefaultLanguageSettings {
1305 preferred_line_length: 80,
1306 tab_size: 5.try_into().unwrap(),
1307 }
1308 );
1309 assert_eq!(
1310 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1311 worktree_id: WorktreeId::from_usize(1),
1312 path: rel_path("root1/subdir/something"),
1313 })),
1314 &DefaultLanguageSettings {
1315 preferred_line_length: 50,
1316 tab_size: 5.try_into().unwrap(),
1317 }
1318 );
1319 assert_eq!(
1320 store.get::<DefaultLanguageSettings>(Some(SettingsLocation {
1321 worktree_id: WorktreeId::from_usize(1),
1322 path: rel_path("root2/something"),
1323 })),
1324 &DefaultLanguageSettings {
1325 preferred_line_length: 80,
1326 tab_size: 9.try_into().unwrap(),
1327 }
1328 );
1329 assert_eq!(
1330 store.get::<AutoUpdateSetting>(Some(SettingsLocation {
1331 worktree_id: WorktreeId::from_usize(1),
1332 path: rel_path("root2/something")
1333 })),
1334 &AutoUpdateSetting { auto_update: false }
1335 );
1336 }
1337
1338 #[gpui::test]
1339 fn test_setting_store_assign_json_before_register(cx: &mut App) {
1340 let mut store = SettingsStore::new(cx, &test_settings());
1341 store
1342 .set_user_settings(r#"{ "auto_update": false }"#, cx)
1343 .unwrap();
1344 store.register_setting::<AutoUpdateSetting>(cx);
1345
1346 assert_eq!(
1347 store.get::<AutoUpdateSetting>(None),
1348 &AutoUpdateSetting { auto_update: false }
1349 );
1350 }
1351
1352 #[track_caller]
1353 fn check_settings_update(
1354 store: &mut SettingsStore,
1355 old_json: String,
1356 update: fn(&mut SettingsContent),
1357 expected_new_json: String,
1358 cx: &mut App,
1359 ) {
1360 store.set_user_settings(&old_json, cx).ok();
1361 let edits = store.edits_for_update(&old_json, update);
1362 let mut new_json = old_json;
1363 for (range, replacement) in edits.into_iter() {
1364 new_json.replace_range(range, &replacement);
1365 }
1366 pretty_assertions::assert_eq!(new_json, expected_new_json);
1367 }
1368
1369 #[gpui::test]
1370 fn test_setting_store_update(cx: &mut App) {
1371 let mut store = SettingsStore::new(cx, &test_settings());
1372
1373 // entries added and updated
1374 check_settings_update(
1375 &mut store,
1376 r#"{
1377 "languages": {
1378 "JSON": {
1379 "auto_indent": true
1380 }
1381 }
1382 }"#
1383 .unindent(),
1384 |settings| {
1385 settings
1386 .languages_mut()
1387 .get_mut("JSON")
1388 .unwrap()
1389 .auto_indent = Some(false);
1390
1391 settings.languages_mut().insert(
1392 "Rust".into(),
1393 LanguageSettingsContent {
1394 auto_indent: Some(true),
1395 ..Default::default()
1396 },
1397 );
1398 },
1399 r#"{
1400 "languages": {
1401 "Rust": {
1402 "auto_indent": true
1403 },
1404 "JSON": {
1405 "auto_indent": false
1406 }
1407 }
1408 }"#
1409 .unindent(),
1410 cx,
1411 );
1412
1413 // entries removed
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("JSON").unwrap();
1429 },
1430 r#"{
1431 "languages": {
1432 "Rust": {
1433 "language_setting_2": true
1434 }
1435 }
1436 }"#
1437 .unindent(),
1438 cx,
1439 );
1440
1441 check_settings_update(
1442 &mut store,
1443 r#"{
1444 "languages": {
1445 "Rust": {
1446 "language_setting_2": true
1447 },
1448 "JSON": {
1449 "language_setting_1": false
1450 }
1451 }
1452 }"#
1453 .unindent(),
1454 |settings| {
1455 settings.languages_mut().remove("Rust").unwrap();
1456 },
1457 r#"{
1458 "languages": {
1459 "JSON": {
1460 "language_setting_1": false
1461 }
1462 }
1463 }"#
1464 .unindent(),
1465 cx,
1466 );
1467
1468 // weird formatting
1469 check_settings_update(
1470 &mut store,
1471 r#"{
1472 "tabs": { "close_position": "left", "name": "Max" }
1473 }"#
1474 .unindent(),
1475 |settings| {
1476 settings.tabs.as_mut().unwrap().close_position = Some(ClosePosition::Left);
1477 },
1478 r#"{
1479 "tabs": { "close_position": "left", "name": "Max" }
1480 }"#
1481 .unindent(),
1482 cx,
1483 );
1484
1485 // single-line formatting, other keys
1486 check_settings_update(
1487 &mut store,
1488 r#"{ "one": 1, "two": 2 }"#.to_owned(),
1489 |settings| settings.auto_update = Some(true),
1490 r#"{ "auto_update": true, "one": 1, "two": 2 }"#.to_owned(),
1491 cx,
1492 );
1493
1494 // empty object
1495 check_settings_update(
1496 &mut store,
1497 r#"{
1498 "tabs": {}
1499 }"#
1500 .unindent(),
1501 |settings| settings.tabs.as_mut().unwrap().close_position = Some(ClosePosition::Left),
1502 r#"{
1503 "tabs": {
1504 "close_position": "left"
1505 }
1506 }"#
1507 .unindent(),
1508 cx,
1509 );
1510
1511 // no content
1512 check_settings_update(
1513 &mut store,
1514 r#""#.unindent(),
1515 |settings| {
1516 settings.tabs = Some(ItemSettingsContent {
1517 git_status: Some(true),
1518 ..Default::default()
1519 })
1520 },
1521 r#"{
1522 "tabs": {
1523 "git_status": true
1524 }
1525 }
1526 "#
1527 .unindent(),
1528 cx,
1529 );
1530
1531 check_settings_update(
1532 &mut store,
1533 r#"{
1534 }
1535 "#
1536 .unindent(),
1537 |settings| settings.title_bar.get_or_insert_default().show_branch_name = Some(true),
1538 r#"{
1539 "title_bar": {
1540 "show_branch_name": true
1541 }
1542 }
1543 "#
1544 .unindent(),
1545 cx,
1546 );
1547 }
1548
1549 #[gpui::test]
1550 fn test_vscode_import(cx: &mut App) {
1551 let mut store = SettingsStore::new(cx, &test_settings());
1552 store.register_setting::<DefaultLanguageSettings>(cx);
1553 store.register_setting::<ItemSettings>(cx);
1554 store.register_setting::<AutoUpdateSetting>(cx);
1555
1556 // create settings that werent present
1557 check_vscode_import(
1558 &mut store,
1559 r#"{
1560 }
1561 "#
1562 .unindent(),
1563 r#" { "editor.tabSize": 37 } "#.to_owned(),
1564 r#"{
1565 "tab_size": 37
1566 }
1567 "#
1568 .unindent(),
1569 cx,
1570 );
1571
1572 // persist settings that were present
1573 check_vscode_import(
1574 &mut store,
1575 r#"{
1576 "preferred_line_length": 99,
1577 }
1578 "#
1579 .unindent(),
1580 r#"{ "editor.tabSize": 42 }"#.to_owned(),
1581 r#"{
1582 "tab_size": 42,
1583 "preferred_line_length": 99,
1584 }
1585 "#
1586 .unindent(),
1587 cx,
1588 );
1589
1590 // don't clobber settings that aren't present in vscode
1591 check_vscode_import(
1592 &mut store,
1593 r#"{
1594 "preferred_line_length": 99,
1595 "tab_size": 42
1596 }
1597 "#
1598 .unindent(),
1599 r#"{}"#.to_owned(),
1600 r#"{
1601 "preferred_line_length": 99,
1602 "tab_size": 42
1603 }
1604 "#
1605 .unindent(),
1606 cx,
1607 );
1608
1609 // custom enum
1610 check_vscode_import(
1611 &mut store,
1612 r#"{
1613 }
1614 "#
1615 .unindent(),
1616 r#"{ "workbench.editor.decorations.colors": true }"#.to_owned(),
1617 r#"{
1618 "tabs": {
1619 "git_status": true
1620 }
1621 }
1622 "#
1623 .unindent(),
1624 cx,
1625 );
1626 }
1627
1628 #[track_caller]
1629 fn check_vscode_import(
1630 store: &mut SettingsStore,
1631 old: String,
1632 vscode: String,
1633 expected: String,
1634 cx: &mut App,
1635 ) {
1636 store.set_user_settings(&old, cx).ok();
1637 let new = store.get_vscode_edits(
1638 old,
1639 &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
1640 );
1641 pretty_assertions::assert_eq!(new, expected);
1642 }
1643
1644 #[gpui::test]
1645 fn test_update_git_settings(cx: &mut App) {
1646 let store = SettingsStore::new(cx, &test_settings());
1647
1648 let actual = store.new_text_for_update("{}".to_string(), |current| {
1649 current
1650 .git
1651 .get_or_insert_default()
1652 .inline_blame
1653 .get_or_insert_default()
1654 .enabled = Some(true);
1655 });
1656 assert_eq!(
1657 actual,
1658 r#"{
1659 "git": {
1660 "inline_blame": {
1661 "enabled": true
1662 }
1663 }
1664 }
1665 "#
1666 .unindent()
1667 );
1668 }
1669
1670 #[gpui::test]
1671 fn test_global_settings(cx: &mut App) {
1672 let mut store = SettingsStore::new(cx, &test_settings());
1673 store.register_setting::<ItemSettings>(cx);
1674
1675 // Set global settings - these should override defaults but not user settings
1676 store
1677 .set_global_settings(
1678 r#"{
1679 "tabs": {
1680 "close_position": "right",
1681 "git_status": true,
1682 }
1683 }"#,
1684 cx,
1685 )
1686 .unwrap();
1687
1688 // Before user settings, global settings should apply
1689 assert_eq!(
1690 store.get::<ItemSettings>(None),
1691 &ItemSettings {
1692 close_position: ClosePosition::Right,
1693 git_status: true,
1694 }
1695 );
1696
1697 // Set user settings - these should override both defaults and global
1698 store
1699 .set_user_settings(
1700 r#"{
1701 "tabs": {
1702 "close_position": "left"
1703 }
1704 }"#,
1705 cx,
1706 )
1707 .unwrap();
1708
1709 // User settings should override global settings
1710 assert_eq!(
1711 store.get::<ItemSettings>(None),
1712 &ItemSettings {
1713 close_position: ClosePosition::Left,
1714 git_status: true, // Staff from global settings
1715 }
1716 );
1717 }
1718
1719 #[gpui::test]
1720 fn test_get_value_for_field_basic(cx: &mut App) {
1721 let mut store = SettingsStore::new(cx, &test_settings());
1722 store.register_setting::<DefaultLanguageSettings>(cx);
1723
1724 store
1725 .set_user_settings(r#"{"preferred_line_length": 0}"#, cx)
1726 .unwrap();
1727 let local = (WorktreeId::from_usize(0), RelPath::empty().into_arc());
1728 store
1729 .set_local_settings(
1730 local.0,
1731 local.1.clone(),
1732 LocalSettingsKind::Settings,
1733 Some(r#"{}"#),
1734 cx,
1735 )
1736 .unwrap();
1737
1738 fn get(content: &SettingsContent) -> &Option<u32> {
1739 &content.project.all_languages.defaults.preferred_line_length
1740 }
1741
1742 let default_value = get(&store.default_settings).unwrap();
1743
1744 assert_eq!(
1745 store.get_value_from_file(SettingsFile::Project(local.clone()), get),
1746 (SettingsFile::User, Some(&0))
1747 );
1748 assert_eq!(
1749 store.get_value_from_file(SettingsFile::User, get),
1750 (SettingsFile::User, Some(&0))
1751 );
1752 store.set_user_settings(r#"{}"#, cx).unwrap();
1753 assert_eq!(
1754 store.get_value_from_file(SettingsFile::Project(local.clone()), get),
1755 (SettingsFile::Default, Some(&default_value))
1756 );
1757 store
1758 .set_local_settings(
1759 local.0,
1760 local.1.clone(),
1761 LocalSettingsKind::Settings,
1762 Some(r#"{"preferred_line_length": 80}"#),
1763 cx,
1764 )
1765 .unwrap();
1766 assert_eq!(
1767 store.get_value_from_file(SettingsFile::Project(local.clone()), get),
1768 (SettingsFile::Project(local), Some(&80))
1769 );
1770 assert_eq!(
1771 store.get_value_from_file(SettingsFile::User, get),
1772 (SettingsFile::Default, Some(&default_value))
1773 );
1774 }
1775
1776 #[gpui::test]
1777 fn test_get_value_for_field_local_worktrees_dont_interfere(cx: &mut App) {
1778 let mut store = SettingsStore::new(cx, &test_settings());
1779 store.register_setting::<DefaultLanguageSettings>(cx);
1780 store.register_setting::<AutoUpdateSetting>(cx);
1781
1782 let local_1 = (WorktreeId::from_usize(0), RelPath::empty().into_arc());
1783
1784 let local_1_child = (
1785 WorktreeId::from_usize(0),
1786 RelPath::new(
1787 std::path::Path::new("child1"),
1788 util::paths::PathStyle::Posix,
1789 )
1790 .unwrap()
1791 .into_arc(),
1792 );
1793
1794 let local_2 = (WorktreeId::from_usize(1), RelPath::empty().into_arc());
1795 let local_2_child = (
1796 WorktreeId::from_usize(1),
1797 RelPath::new(
1798 std::path::Path::new("child2"),
1799 util::paths::PathStyle::Posix,
1800 )
1801 .unwrap()
1802 .into_arc(),
1803 );
1804
1805 fn get(content: &SettingsContent) -> &Option<u32> {
1806 &content.project.all_languages.defaults.preferred_line_length
1807 }
1808
1809 store
1810 .set_local_settings(
1811 local_1.0,
1812 local_1.1.clone(),
1813 LocalSettingsKind::Settings,
1814 Some(r#"{"preferred_line_length": 1}"#),
1815 cx,
1816 )
1817 .unwrap();
1818 store
1819 .set_local_settings(
1820 local_1_child.0,
1821 local_1_child.1.clone(),
1822 LocalSettingsKind::Settings,
1823 Some(r#"{}"#),
1824 cx,
1825 )
1826 .unwrap();
1827 store
1828 .set_local_settings(
1829 local_2.0,
1830 local_2.1.clone(),
1831 LocalSettingsKind::Settings,
1832 Some(r#"{"preferred_line_length": 2}"#),
1833 cx,
1834 )
1835 .unwrap();
1836 store
1837 .set_local_settings(
1838 local_2_child.0,
1839 local_2_child.1.clone(),
1840 LocalSettingsKind::Settings,
1841 Some(r#"{}"#),
1842 cx,
1843 )
1844 .unwrap();
1845
1846 // each local child should only inherit from it's parent
1847 assert_eq!(
1848 store.get_value_from_file(SettingsFile::Project(local_2_child), get),
1849 (SettingsFile::Project(local_2), Some(&2))
1850 );
1851 assert_eq!(
1852 store.get_value_from_file(SettingsFile::Project(local_1_child.clone()), get),
1853 (SettingsFile::Project(local_1.clone()), Some(&1))
1854 );
1855
1856 // adjacent children should be treated as siblings not inherit from each other
1857 let local_1_adjacent_child = (local_1.0, rel_path("adjacent_child").into_arc());
1858 store
1859 .set_local_settings(
1860 local_1_adjacent_child.0,
1861 local_1_adjacent_child.1.clone(),
1862 LocalSettingsKind::Settings,
1863 Some(r#"{}"#),
1864 cx,
1865 )
1866 .unwrap();
1867 store
1868 .set_local_settings(
1869 local_1_child.0,
1870 local_1_child.1.clone(),
1871 LocalSettingsKind::Settings,
1872 Some(r#"{"preferred_line_length": 3}"#),
1873 cx,
1874 )
1875 .unwrap();
1876
1877 assert_eq!(
1878 store.get_value_from_file(SettingsFile::Project(local_1_adjacent_child.clone()), get),
1879 (SettingsFile::Project(local_1.clone()), Some(&1))
1880 );
1881 store
1882 .set_local_settings(
1883 local_1_adjacent_child.0,
1884 local_1_adjacent_child.1,
1885 LocalSettingsKind::Settings,
1886 Some(r#"{"preferred_line_length": 3}"#),
1887 cx,
1888 )
1889 .unwrap();
1890 store
1891 .set_local_settings(
1892 local_1_child.0,
1893 local_1_child.1.clone(),
1894 LocalSettingsKind::Settings,
1895 Some(r#"{}"#),
1896 cx,
1897 )
1898 .unwrap();
1899 assert_eq!(
1900 store.get_value_from_file(SettingsFile::Project(local_1_child), get),
1901 (SettingsFile::Project(local_1), Some(&1))
1902 );
1903 }
1904
1905 #[gpui::test]
1906 fn test_get_overrides_for_field(cx: &mut App) {
1907 let mut store = SettingsStore::new(cx, &test_settings());
1908 store.register_setting::<DefaultLanguageSettings>(cx);
1909
1910 let wt0_root = (WorktreeId::from_usize(0), RelPath::empty().into_arc());
1911 let wt0_child1 = (WorktreeId::from_usize(0), rel_path("child1").into_arc());
1912 let wt0_child2 = (WorktreeId::from_usize(0), rel_path("child2").into_arc());
1913
1914 let wt1_root = (WorktreeId::from_usize(1), RelPath::empty().into_arc());
1915 let wt1_subdir = (WorktreeId::from_usize(1), rel_path("subdir").into_arc());
1916
1917 fn get(content: &SettingsContent) -> &Option<u32> {
1918 &content.project.all_languages.defaults.preferred_line_length
1919 }
1920
1921 store
1922 .set_user_settings(r#"{"preferred_line_length": 100}"#, cx)
1923 .unwrap();
1924
1925 store
1926 .set_local_settings(
1927 wt0_root.0,
1928 wt0_root.1.clone(),
1929 LocalSettingsKind::Settings,
1930 Some(r#"{"preferred_line_length": 80}"#),
1931 cx,
1932 )
1933 .unwrap();
1934 store
1935 .set_local_settings(
1936 wt0_child1.0,
1937 wt0_child1.1.clone(),
1938 LocalSettingsKind::Settings,
1939 Some(r#"{"preferred_line_length": 120}"#),
1940 cx,
1941 )
1942 .unwrap();
1943 store
1944 .set_local_settings(
1945 wt0_child2.0,
1946 wt0_child2.1.clone(),
1947 LocalSettingsKind::Settings,
1948 Some(r#"{}"#),
1949 cx,
1950 )
1951 .unwrap();
1952
1953 store
1954 .set_local_settings(
1955 wt1_root.0,
1956 wt1_root.1.clone(),
1957 LocalSettingsKind::Settings,
1958 Some(r#"{"preferred_line_length": 90}"#),
1959 cx,
1960 )
1961 .unwrap();
1962 store
1963 .set_local_settings(
1964 wt1_subdir.0,
1965 wt1_subdir.1.clone(),
1966 LocalSettingsKind::Settings,
1967 Some(r#"{}"#),
1968 cx,
1969 )
1970 .unwrap();
1971
1972 let overrides = store.get_overrides_for_field(SettingsFile::Default, get);
1973 assert_eq!(
1974 overrides,
1975 vec![
1976 SettingsFile::User,
1977 SettingsFile::Project(wt0_root.clone()),
1978 SettingsFile::Project(wt0_child1.clone()),
1979 SettingsFile::Project(wt1_root.clone()),
1980 ]
1981 );
1982
1983 let overrides = store.get_overrides_for_field(SettingsFile::User, get);
1984 assert_eq!(
1985 overrides,
1986 vec![
1987 SettingsFile::Project(wt0_root.clone()),
1988 SettingsFile::Project(wt0_child1.clone()),
1989 SettingsFile::Project(wt1_root.clone()),
1990 ]
1991 );
1992
1993 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_root), get);
1994 assert_eq!(overrides, vec![]);
1995
1996 let overrides =
1997 store.get_overrides_for_field(SettingsFile::Project(wt0_child1.clone()), get);
1998 assert_eq!(overrides, vec![]);
1999
2000 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_child2), get);
2001 assert_eq!(overrides, vec![]);
2002
2003 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt1_root), get);
2004 assert_eq!(overrides, vec![]);
2005
2006 let overrides = store.get_overrides_for_field(SettingsFile::Project(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::Project(wt0_deep_child), get);
2024 assert_eq!(overrides, vec![]);
2025
2026 let overrides = store.get_overrides_for_field(SettingsFile::Project(wt0_child1), get);
2027 assert_eq!(overrides, vec![]);
2028 }
2029}