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