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