1mod font_size;
2mod keymap_file;
3mod settings_file;
4mod settings_store;
5
6use anyhow::Result;
7use gpui::{
8 font_cache::{FamilyId, FontCache},
9 fonts, AppContext, AssetSource,
10};
11use schemars::{
12 gen::SchemaGenerator,
13 schema::{InstanceType, Schema, SchemaObject},
14 JsonSchema,
15};
16use serde::{Deserialize, Serialize};
17use serde_json::Value;
18use std::{borrow::Cow, str, sync::Arc};
19use theme::{Theme, ThemeRegistry};
20use util::ResultExt as _;
21
22pub use font_size::{adjust_font_size_delta, font_size_for_setting};
23pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
24pub use settings_file::*;
25pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore};
26
27pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json";
28pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json";
29
30#[derive(Clone)]
31pub struct Settings {
32 pub buffer_font_family_name: String,
33 pub buffer_font_features: fonts::Features,
34 pub buffer_font_family: FamilyId,
35 pub buffer_font_size: f32,
36 pub theme: Arc<Theme>,
37}
38
39impl Setting for Settings {
40 const KEY: Option<&'static str> = None;
41
42 type FileContent = SettingsFileContent;
43
44 fn load(
45 defaults: &Self::FileContent,
46 user_values: &[&Self::FileContent],
47 cx: &AppContext,
48 ) -> Result<Self> {
49 let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
50 let themes = cx.global::<Arc<ThemeRegistry>>();
51
52 let mut this = Self {
53 buffer_font_family: cx
54 .font_cache()
55 .load_family(
56 &[defaults.buffer_font_family.as_ref().unwrap()],
57 &buffer_font_features,
58 )
59 .unwrap(),
60 buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(),
61 buffer_font_features,
62 buffer_font_size: defaults.buffer_font_size.unwrap(),
63 theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
64 };
65
66 for value in user_values.into_iter().copied().cloned() {
67 this.set_user_settings(value, themes.as_ref(), cx.font_cache());
68 }
69
70 Ok(this)
71 }
72
73 fn json_schema(
74 generator: &mut SchemaGenerator,
75 params: &SettingsJsonSchemaParams,
76 ) -> schemars::schema::RootSchema {
77 let mut root_schema = generator.root_schema_for::<SettingsFileContent>();
78
79 // Create a schema for a theme name.
80 let theme_name_schema = SchemaObject {
81 instance_type: Some(InstanceType::String.into()),
82 enum_values: Some(
83 params
84 .theme_names
85 .iter()
86 .cloned()
87 .map(Value::String)
88 .collect(),
89 ),
90 ..Default::default()
91 };
92
93 root_schema
94 .definitions
95 .extend([("ThemeName".into(), theme_name_schema.into())]);
96
97 root_schema
98 .schema
99 .object
100 .as_mut()
101 .unwrap()
102 .properties
103 .extend([(
104 "theme".to_owned(),
105 Schema::new_ref("#/definitions/ThemeName".into()),
106 )]);
107
108 root_schema
109 }
110}
111
112#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
113pub struct SettingsFileContent {
114 #[serde(default)]
115 pub buffer_font_family: Option<String>,
116 #[serde(default)]
117 pub buffer_font_size: Option<f32>,
118 #[serde(default)]
119 pub buffer_font_features: Option<fonts::Features>,
120 #[serde(default)]
121 pub theme: Option<String>,
122}
123
124impl Settings {
125 pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
126 match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
127 Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
128 Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
129 }
130 }
131
132 /// Fill out the settings corresponding to the default.json file, overrides will be set later
133 pub fn defaults(
134 assets: impl AssetSource,
135 font_cache: &FontCache,
136 themes: &ThemeRegistry,
137 ) -> Self {
138 let defaults: SettingsFileContent = settings_store::parse_json_with_comments(
139 str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(),
140 )
141 .unwrap();
142
143 let buffer_font_features = defaults.buffer_font_features.unwrap();
144 Self {
145 buffer_font_family: font_cache
146 .load_family(
147 &[defaults.buffer_font_family.as_ref().unwrap()],
148 &buffer_font_features,
149 )
150 .unwrap(),
151 buffer_font_family_name: defaults.buffer_font_family.unwrap(),
152 buffer_font_features,
153 buffer_font_size: defaults.buffer_font_size.unwrap(),
154 theme: themes.get(&defaults.theme.unwrap()).unwrap(),
155 }
156 }
157
158 // Fill out the overrride and etc. settings from the user's settings.json
159 fn set_user_settings(
160 &mut self,
161 data: SettingsFileContent,
162 theme_registry: &ThemeRegistry,
163 font_cache: &FontCache,
164 ) {
165 let mut family_changed = false;
166 if let Some(value) = data.buffer_font_family {
167 self.buffer_font_family_name = value;
168 family_changed = true;
169 }
170 if let Some(value) = data.buffer_font_features {
171 self.buffer_font_features = value;
172 family_changed = true;
173 }
174 if family_changed {
175 if let Some(id) = font_cache
176 .load_family(&[&self.buffer_font_family_name], &self.buffer_font_features)
177 .log_err()
178 {
179 self.buffer_font_family = id;
180 }
181 }
182
183 if let Some(value) = &data.theme {
184 if let Some(theme) = theme_registry.get(value).log_err() {
185 self.theme = theme;
186 }
187 }
188
189 merge(&mut self.buffer_font_size, data.buffer_font_size);
190 }
191
192 #[cfg(any(test, feature = "test-support"))]
193 pub fn test(cx: &gpui::AppContext) -> Settings {
194 Settings {
195 buffer_font_family_name: "Monaco".to_string(),
196 buffer_font_features: Default::default(),
197 buffer_font_family: cx
198 .font_cache()
199 .load_family(&["Monaco"], &Default::default())
200 .unwrap(),
201 buffer_font_size: 14.,
202 theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
203 }
204 }
205
206 #[cfg(any(test, feature = "test-support"))]
207 pub fn test_async(cx: &mut gpui::TestAppContext) {
208 cx.update(|cx| {
209 let settings = Self::test(cx);
210 cx.set_global(settings);
211 });
212 }
213}
214
215fn merge<T: Copy>(target: &mut T, value: Option<T>) {
216 if let Some(value) = value {
217 *target = value;
218 }
219}