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