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