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