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