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