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