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 pub base_keymap: BaseKeymap,
38}
39
40impl Setting for Settings {
41 const KEY: Option<&'static str> = None;
42
43 type FileContent = SettingsFileContent;
44
45 fn load(
46 defaults: &Self::FileContent,
47 user_values: &[&Self::FileContent],
48 cx: &AppContext,
49 ) -> Result<Self> {
50 let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
51 let themes = cx.global::<Arc<ThemeRegistry>>();
52
53 let mut this = Self {
54 buffer_font_family: cx
55 .font_cache()
56 .load_family(
57 &[defaults.buffer_font_family.as_ref().unwrap()],
58 &buffer_font_features,
59 )
60 .unwrap(),
61 buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(),
62 buffer_font_features,
63 buffer_font_size: defaults.buffer_font_size.unwrap(),
64 theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
65 base_keymap: Default::default(),
66 };
67
68 for value in user_values.into_iter().copied().cloned() {
69 this.set_user_settings(value, themes.as_ref(), cx.font_cache());
70 }
71
72 Ok(this)
73 }
74
75 fn json_schema(
76 generator: &mut SchemaGenerator,
77 params: &SettingsJsonSchemaParams,
78 ) -> schemars::schema::RootSchema {
79 let mut root_schema = generator.root_schema_for::<SettingsFileContent>();
80
81 // Create a schema for a theme name.
82 let theme_name_schema = SchemaObject {
83 instance_type: Some(InstanceType::String.into()),
84 enum_values: Some(
85 params
86 .theme_names
87 .iter()
88 .cloned()
89 .map(Value::String)
90 .collect(),
91 ),
92 ..Default::default()
93 };
94
95 root_schema
96 .definitions
97 .extend([("ThemeName".into(), theme_name_schema.into())]);
98
99 root_schema
100 .schema
101 .object
102 .as_mut()
103 .unwrap()
104 .properties
105 .extend([(
106 "theme".to_owned(),
107 Schema::new_ref("#/definitions/ThemeName".into()),
108 )]);
109
110 root_schema
111 }
112}
113
114#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
115pub enum BaseKeymap {
116 #[default]
117 VSCode,
118 JetBrains,
119 SublimeText,
120 Atom,
121 TextMate,
122}
123
124impl BaseKeymap {
125 pub const OPTIONS: [(&'static str, Self); 5] = [
126 ("VSCode (Default)", Self::VSCode),
127 ("Atom", Self::Atom),
128 ("JetBrains", Self::JetBrains),
129 ("Sublime Text", Self::SublimeText),
130 ("TextMate", Self::TextMate),
131 ];
132
133 pub fn asset_path(&self) -> Option<&'static str> {
134 match self {
135 BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"),
136 BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"),
137 BaseKeymap::Atom => Some("keymaps/atom.json"),
138 BaseKeymap::TextMate => Some("keymaps/textmate.json"),
139 BaseKeymap::VSCode => None,
140 }
141 }
142
143 pub fn names() -> impl Iterator<Item = &'static str> {
144 Self::OPTIONS.iter().map(|(name, _)| *name)
145 }
146
147 pub fn from_names(option: &str) -> BaseKeymap {
148 Self::OPTIONS
149 .iter()
150 .copied()
151 .find_map(|(name, value)| (name == option).then(|| value))
152 .unwrap_or_default()
153 }
154}
155
156#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
157pub struct SettingsFileContent {
158 #[serde(default)]
159 pub buffer_font_family: Option<String>,
160 #[serde(default)]
161 pub buffer_font_size: Option<f32>,
162 #[serde(default)]
163 pub buffer_font_features: Option<fonts::Features>,
164 #[serde(default)]
165 pub theme: Option<String>,
166 #[serde(default)]
167 pub base_keymap: Option<BaseKeymap>,
168}
169
170impl Settings {
171 pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
172 match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
173 Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
174 Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
175 }
176 }
177
178 /// Fill out the settings corresponding to the default.json file, overrides will be set later
179 pub fn defaults(
180 assets: impl AssetSource,
181 font_cache: &FontCache,
182 themes: &ThemeRegistry,
183 ) -> Self {
184 let defaults: SettingsFileContent = settings_store::parse_json_with_comments(
185 str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(),
186 )
187 .unwrap();
188
189 let buffer_font_features = defaults.buffer_font_features.unwrap();
190 Self {
191 buffer_font_family: font_cache
192 .load_family(
193 &[defaults.buffer_font_family.as_ref().unwrap()],
194 &buffer_font_features,
195 )
196 .unwrap(),
197 buffer_font_family_name: defaults.buffer_font_family.unwrap(),
198 buffer_font_features,
199 buffer_font_size: defaults.buffer_font_size.unwrap(),
200 theme: themes.get(&defaults.theme.unwrap()).unwrap(),
201 base_keymap: Default::default(),
202 }
203 }
204
205 // Fill out the overrride and etc. settings from the user's settings.json
206 fn set_user_settings(
207 &mut self,
208 data: SettingsFileContent,
209 theme_registry: &ThemeRegistry,
210 font_cache: &FontCache,
211 ) {
212 let mut family_changed = false;
213 if let Some(value) = data.buffer_font_family {
214 self.buffer_font_family_name = value;
215 family_changed = true;
216 }
217 if let Some(value) = data.buffer_font_features {
218 self.buffer_font_features = value;
219 family_changed = true;
220 }
221 if family_changed {
222 if let Some(id) = font_cache
223 .load_family(&[&self.buffer_font_family_name], &self.buffer_font_features)
224 .log_err()
225 {
226 self.buffer_font_family = id;
227 }
228 }
229
230 if let Some(value) = &data.theme {
231 if let Some(theme) = theme_registry.get(value).log_err() {
232 self.theme = theme;
233 }
234 }
235
236 merge(&mut self.buffer_font_size, data.buffer_font_size);
237 merge(&mut self.base_keymap, data.base_keymap);
238 }
239
240 #[cfg(any(test, feature = "test-support"))]
241 pub fn test(cx: &gpui::AppContext) -> Settings {
242 Settings {
243 buffer_font_family_name: "Monaco".to_string(),
244 buffer_font_features: Default::default(),
245 buffer_font_family: cx
246 .font_cache()
247 .load_family(&["Monaco"], &Default::default())
248 .unwrap(),
249 buffer_font_size: 14.,
250 theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
251 base_keymap: Default::default(),
252 }
253 }
254
255 #[cfg(any(test, feature = "test-support"))]
256 pub fn test_async(cx: &mut gpui::TestAppContext) {
257 cx.update(|cx| {
258 let settings = Self::test(cx);
259 cx.set_global(settings);
260 });
261 }
262}
263
264fn merge<T: Copy>(target: &mut T, value: Option<T>) {
265 if let Some(value) = value {
266 *target = value;
267 }
268}