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