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