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