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