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