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