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