1mod keymap_file;
2
3use anyhow::Result;
4use gpui::font_cache::{FamilyId, FontCache};
5use schemars::{
6 gen::{SchemaGenerator, SchemaSettings},
7 schema::{
8 InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec, SubschemaValidation,
9 },
10 JsonSchema,
11};
12use serde::{de::DeserializeOwned, Deserialize};
13use serde_json::Value;
14use std::{collections::HashMap, num::NonZeroU32, sync::Arc};
15use theme::{Theme, ThemeRegistry};
16use util::ResultExt as _;
17
18pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
19
20#[derive(Clone)]
21pub struct Settings {
22 pub projects_online_by_default: bool,
23 pub buffer_font_family: FamilyId,
24 pub buffer_font_size: f32,
25 pub default_buffer_font_size: f32,
26 pub hover_popover_enabled: bool,
27 pub vim_mode: bool,
28 pub autosave: Autosave,
29 pub language_settings: LanguageSettings,
30 pub language_defaults: HashMap<Arc<str>, LanguageSettings>,
31 pub language_overrides: HashMap<Arc<str>, LanguageSettings>,
32 pub theme: Arc<Theme>,
33}
34
35#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
36pub struct LanguageSettings {
37 pub tab_size: Option<NonZeroU32>,
38 pub hard_tabs: Option<bool>,
39 pub soft_wrap: Option<SoftWrap>,
40 pub preferred_line_length: Option<u32>,
41 pub format_on_save: Option<bool>,
42 pub enable_language_server: Option<bool>,
43}
44
45#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
46#[serde(rename_all = "snake_case")]
47pub enum SoftWrap {
48 None,
49 EditorWidth,
50 PreferredLineLength,
51}
52
53#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
54#[serde(rename_all = "snake_case")]
55pub enum Autosave {
56 Off,
57 AfterDelay { milliseconds: u64 },
58 OnFocusChange,
59 OnWindowChange,
60}
61
62#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
63pub struct SettingsFileContent {
64 #[serde(default)]
65 pub projects_online_by_default: Option<bool>,
66 #[serde(default)]
67 pub buffer_font_family: Option<String>,
68 #[serde(default)]
69 pub buffer_font_size: Option<f32>,
70 #[serde(default)]
71 pub hover_popover_enabled: Option<bool>,
72 #[serde(default)]
73 pub vim_mode: Option<bool>,
74 #[serde(default)]
75 pub format_on_save: Option<bool>,
76 #[serde(default)]
77 pub autosave: Option<Autosave>,
78 #[serde(default)]
79 pub enable_language_server: Option<bool>,
80 #[serde(flatten)]
81 pub editor: LanguageSettings,
82 #[serde(default)]
83 pub language_overrides: HashMap<Arc<str>, LanguageSettings>,
84 #[serde(default)]
85 pub theme: Option<String>,
86}
87
88impl Settings {
89 pub fn new(
90 buffer_font_family: &str,
91 font_cache: &FontCache,
92 theme: Arc<Theme>,
93 ) -> Result<Self> {
94 Ok(Self {
95 buffer_font_family: font_cache.load_family(&[buffer_font_family])?,
96 buffer_font_size: 15.,
97 default_buffer_font_size: 15.,
98 hover_popover_enabled: true,
99 vim_mode: false,
100 autosave: Autosave::Off,
101 language_settings: Default::default(),
102 language_defaults: Default::default(),
103 language_overrides: Default::default(),
104 projects_online_by_default: true,
105 theme,
106 })
107 }
108
109 pub fn with_language_defaults(
110 mut self,
111 language_name: impl Into<Arc<str>>,
112 overrides: LanguageSettings,
113 ) -> Self {
114 self.language_defaults
115 .insert(language_name.into(), overrides);
116 self
117 }
118
119 pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 {
120 self.language_setting(language, |settings| settings.tab_size)
121 .unwrap_or(4.try_into().unwrap())
122 }
123
124 pub fn hard_tabs(&self, language: Option<&str>) -> bool {
125 self.language_setting(language, |settings| settings.hard_tabs)
126 .unwrap_or(false)
127 }
128
129 pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
130 self.language_setting(language, |settings| settings.soft_wrap)
131 .unwrap_or(SoftWrap::None)
132 }
133
134 pub fn preferred_line_length(&self, language: Option<&str>) -> u32 {
135 self.language_setting(language, |settings| settings.preferred_line_length)
136 .unwrap_or(80)
137 }
138
139 pub fn format_on_save(&self, language: Option<&str>) -> bool {
140 self.language_setting(language, |settings| settings.format_on_save)
141 .unwrap_or(true)
142 }
143
144 pub fn enable_language_server(&self, language: Option<&str>) -> bool {
145 self.language_setting(language, |settings| settings.enable_language_server)
146 .unwrap_or(true)
147 }
148
149 fn language_setting<F, R>(&self, language: Option<&str>, f: F) -> Option<R>
150 where
151 F: Fn(&LanguageSettings) -> Option<R>,
152 {
153 let mut language_override = None;
154 let mut language_default = None;
155 if let Some(language) = language {
156 language_override = self.language_overrides.get(language).and_then(&f);
157 language_default = self.language_defaults.get(language).and_then(&f);
158 }
159
160 language_override
161 .or_else(|| f(&self.language_settings))
162 .or(language_default)
163 }
164
165 #[cfg(any(test, feature = "test-support"))]
166 pub fn test(cx: &gpui::AppContext) -> Settings {
167 Settings {
168 buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(),
169 buffer_font_size: 14.,
170 default_buffer_font_size: 14.,
171 hover_popover_enabled: true,
172 vim_mode: false,
173 autosave: Autosave::Off,
174 language_settings: Default::default(),
175 language_defaults: Default::default(),
176 language_overrides: Default::default(),
177 projects_online_by_default: true,
178 theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), || Default::default()),
179 }
180 }
181
182 #[cfg(any(test, feature = "test-support"))]
183 pub fn test_async(cx: &mut gpui::TestAppContext) {
184 cx.update(|cx| {
185 let settings = Self::test(cx);
186 cx.set_global(settings.clone());
187 });
188 }
189
190 pub fn merge(
191 &mut self,
192 data: &SettingsFileContent,
193 theme_registry: &ThemeRegistry,
194 font_cache: &FontCache,
195 ) {
196 if let Some(value) = &data.buffer_font_family {
197 if let Some(id) = font_cache.load_family(&[value]).log_err() {
198 self.buffer_font_family = id;
199 }
200 }
201 if let Some(value) = &data.theme {
202 if let Some(theme) = theme_registry.get(&value.to_string()).log_err() {
203 self.theme = theme;
204 }
205 }
206
207 merge(
208 &mut self.projects_online_by_default,
209 data.projects_online_by_default,
210 );
211 merge(&mut self.buffer_font_size, data.buffer_font_size);
212 merge(&mut self.default_buffer_font_size, data.buffer_font_size);
213 merge(&mut self.hover_popover_enabled, data.hover_popover_enabled);
214 merge(&mut self.vim_mode, data.vim_mode);
215 merge(&mut self.autosave, data.autosave);
216 merge_option(
217 &mut self.language_settings.format_on_save,
218 data.format_on_save,
219 );
220 merge_option(
221 &mut self.language_settings.enable_language_server,
222 data.enable_language_server,
223 );
224 merge_option(&mut self.language_settings.soft_wrap, data.editor.soft_wrap);
225 merge_option(&mut self.language_settings.tab_size, data.editor.tab_size);
226 merge_option(
227 &mut self.language_settings.preferred_line_length,
228 data.editor.preferred_line_length,
229 );
230
231 for (language_name, settings) in data.language_overrides.clone().into_iter() {
232 let target = self
233 .language_overrides
234 .entry(language_name.into())
235 .or_default();
236
237 merge_option(&mut target.tab_size, settings.tab_size);
238 merge_option(&mut target.soft_wrap, settings.soft_wrap);
239 merge_option(&mut target.format_on_save, settings.format_on_save);
240 merge_option(
241 &mut target.enable_language_server,
242 settings.enable_language_server,
243 );
244 merge_option(
245 &mut target.preferred_line_length,
246 settings.preferred_line_length,
247 );
248 }
249 }
250}
251
252pub fn settings_file_json_schema(
253 theme_names: Vec<String>,
254 language_names: Vec<String>,
255) -> serde_json::Value {
256 let settings = SchemaSettings::draft07().with(|settings| {
257 settings.option_add_null_type = false;
258 });
259 let generator = SchemaGenerator::new(settings);
260 let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
261
262 // Construct theme names reference type
263 let theme_names = theme_names
264 .into_iter()
265 .map(|name| Value::String(name))
266 .collect();
267 let theme_names_schema = Schema::Object(SchemaObject {
268 instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
269 enum_values: Some(theme_names),
270 ..Default::default()
271 });
272 root_schema
273 .definitions
274 .insert("ThemeName".to_owned(), theme_names_schema);
275
276 // Construct language settings reference type
277 let language_settings_schema_reference = Schema::Object(SchemaObject {
278 reference: Some("#/definitions/LanguageSettings".to_owned()),
279 ..Default::default()
280 });
281 let language_settings_properties = language_names
282 .into_iter()
283 .map(|name| {
284 (
285 name,
286 Schema::Object(SchemaObject {
287 subschemas: Some(Box::new(SubschemaValidation {
288 all_of: Some(vec![language_settings_schema_reference.clone()]),
289 ..Default::default()
290 })),
291 ..Default::default()
292 }),
293 )
294 })
295 .collect();
296 let language_overrides_schema = Schema::Object(SchemaObject {
297 instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
298 object: Some(Box::new(ObjectValidation {
299 properties: language_settings_properties,
300 ..Default::default()
301 })),
302 ..Default::default()
303 });
304 root_schema
305 .definitions
306 .insert("LanguageOverrides".to_owned(), language_overrides_schema);
307
308 // Modify theme property to use new theme reference type
309 let settings_file_schema = root_schema.schema.object.as_mut().unwrap();
310 let language_overrides_schema_reference = Schema::Object(SchemaObject {
311 reference: Some("#/definitions/ThemeName".to_owned()),
312 ..Default::default()
313 });
314 settings_file_schema.properties.insert(
315 "theme".to_owned(),
316 Schema::Object(SchemaObject {
317 subschemas: Some(Box::new(SubschemaValidation {
318 all_of: Some(vec![language_overrides_schema_reference]),
319 ..Default::default()
320 })),
321 ..Default::default()
322 }),
323 );
324
325 // Modify language_overrides property to use LanguageOverrides reference
326 settings_file_schema.properties.insert(
327 "language_overrides".to_owned(),
328 Schema::Object(SchemaObject {
329 reference: Some("#/definitions/LanguageOverrides".to_owned()),
330 ..Default::default()
331 }),
332 );
333 serde_json::to_value(root_schema).unwrap()
334}
335
336fn merge<T: Copy>(target: &mut T, value: Option<T>) {
337 if let Some(value) = value {
338 *target = value;
339 }
340}
341
342fn merge_option<T: Copy>(target: &mut Option<T>, value: Option<T>) {
343 if value.is_some() {
344 *target = value;
345 }
346}
347
348pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T> {
349 Ok(serde_json::from_reader(
350 json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()),
351 )?)
352}