1use anyhow::Result;
2use collections::HashMap;
3use globset::GlobMatcher;
4use gpui::AppContext;
5use schemars::{
6 schema::{InstanceType, ObjectValidation, Schema, SchemaObject},
7 JsonSchema,
8};
9use serde::{Deserialize, Serialize};
10use std::{num::NonZeroU32, path::Path, sync::Arc};
11
12pub fn init(cx: &mut AppContext) {
13 settings::register::<AllLanguageSettings>(cx);
14}
15
16pub fn language_settings<'a>(language: Option<&str>, cx: &'a AppContext) -> &'a LanguageSettings {
17 settings::get::<AllLanguageSettings>(cx).language(language)
18}
19
20pub fn all_language_settings<'a>(cx: &'a AppContext) -> &'a AllLanguageSettings {
21 settings::get::<AllLanguageSettings>(cx)
22}
23
24#[derive(Debug, Clone)]
25pub struct AllLanguageSettings {
26 pub copilot: CopilotSettings,
27 defaults: LanguageSettings,
28 languages: HashMap<Arc<str>, LanguageSettings>,
29}
30
31#[derive(Debug, Clone, Deserialize)]
32pub struct LanguageSettings {
33 pub tab_size: NonZeroU32,
34 pub hard_tabs: bool,
35 pub soft_wrap: SoftWrap,
36 pub preferred_line_length: u32,
37 pub format_on_save: FormatOnSave,
38 pub remove_trailing_whitespace_on_save: bool,
39 pub ensure_final_newline_on_save: bool,
40 pub formatter: Formatter,
41 pub enable_language_server: bool,
42 pub show_copilot_suggestions: bool,
43 pub show_whitespaces: ShowWhitespaceSetting,
44}
45
46#[derive(Clone, Debug, Default)]
47pub struct CopilotSettings {
48 pub feature_enabled: bool,
49 pub disabled_globs: Vec<GlobMatcher>,
50}
51
52#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
53pub struct AllLanguageSettingsContent {
54 #[serde(default)]
55 pub features: Option<FeaturesContent>,
56 #[serde(default)]
57 pub copilot: Option<CopilotSettingsContent>,
58 #[serde(flatten)]
59 pub defaults: LanguageSettingsContent,
60 #[serde(default, alias = "language_overrides")]
61 pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
62}
63
64#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
65pub struct LanguageSettingsContent {
66 #[serde(default)]
67 pub tab_size: Option<NonZeroU32>,
68 #[serde(default)]
69 pub hard_tabs: Option<bool>,
70 #[serde(default)]
71 pub soft_wrap: Option<SoftWrap>,
72 #[serde(default)]
73 pub preferred_line_length: Option<u32>,
74 #[serde(default)]
75 pub format_on_save: Option<FormatOnSave>,
76 #[serde(default)]
77 pub remove_trailing_whitespace_on_save: Option<bool>,
78 #[serde(default)]
79 pub ensure_final_newline_on_save: Option<bool>,
80 #[serde(default)]
81 pub formatter: Option<Formatter>,
82 #[serde(default)]
83 pub enable_language_server: Option<bool>,
84 #[serde(default)]
85 pub show_copilot_suggestions: Option<bool>,
86 #[serde(default)]
87 pub show_whitespaces: Option<ShowWhitespaceSetting>,
88}
89
90#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
91pub struct CopilotSettingsContent {
92 #[serde(default)]
93 pub disabled_globs: Option<Vec<String>>,
94}
95
96#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
97#[serde(rename_all = "snake_case")]
98pub struct FeaturesContent {
99 pub copilot: Option<bool>,
100}
101
102#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
103#[serde(rename_all = "snake_case")]
104pub enum SoftWrap {
105 None,
106 EditorWidth,
107 PreferredLineLength,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
111#[serde(rename_all = "snake_case")]
112pub enum FormatOnSave {
113 On,
114 Off,
115 LanguageServer,
116 External {
117 command: Arc<str>,
118 arguments: Arc<[String]>,
119 },
120}
121
122#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
123#[serde(rename_all = "snake_case")]
124pub enum ShowWhitespaceSetting {
125 Selection,
126 None,
127 All,
128}
129
130#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
131#[serde(rename_all = "snake_case")]
132pub enum Formatter {
133 LanguageServer,
134 External {
135 command: Arc<str>,
136 arguments: Arc<[String]>,
137 },
138}
139
140impl AllLanguageSettings {
141 pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
142 if let Some(name) = language_name {
143 if let Some(overrides) = self.languages.get(name) {
144 return overrides;
145 }
146 }
147 &self.defaults
148 }
149
150 pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
151 !self
152 .copilot
153 .disabled_globs
154 .iter()
155 .any(|glob| glob.is_match(path))
156 }
157
158 pub fn copilot_enabled(&self, language_name: Option<&str>, path: Option<&Path>) -> bool {
159 if !self.copilot.feature_enabled {
160 return false;
161 }
162
163 if let Some(path) = path {
164 if !self.copilot_enabled_for_path(path) {
165 return false;
166 }
167 }
168
169 self.language(language_name).show_copilot_suggestions
170 }
171}
172
173impl settings::Setting for AllLanguageSettings {
174 const KEY: Option<&'static str> = None;
175
176 type FileContent = AllLanguageSettingsContent;
177
178 fn load(
179 default_value: &Self::FileContent,
180 user_settings: &[&Self::FileContent],
181 _: &AppContext,
182 ) -> Result<Self> {
183 // A default is provided for all settings.
184 let mut defaults: LanguageSettings =
185 serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
186
187 let mut languages = HashMap::default();
188 for (language_name, settings) in &default_value.languages {
189 let mut language_settings = defaults.clone();
190 merge_settings(&mut language_settings, &settings);
191 languages.insert(language_name.clone(), language_settings);
192 }
193
194 let mut copilot_enabled = default_value
195 .features
196 .as_ref()
197 .and_then(|f| f.copilot)
198 .ok_or_else(Self::missing_default)?;
199 let mut copilot_globs = default_value
200 .copilot
201 .as_ref()
202 .and_then(|c| c.disabled_globs.as_ref())
203 .ok_or_else(Self::missing_default)?;
204
205 for user_settings in user_settings {
206 if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
207 copilot_enabled = copilot;
208 }
209 if let Some(globs) = user_settings
210 .copilot
211 .as_ref()
212 .and_then(|f| f.disabled_globs.as_ref())
213 {
214 copilot_globs = globs;
215 }
216
217 // A user's global settings override the default global settings and
218 // all default language-specific settings.
219 merge_settings(&mut defaults, &user_settings.defaults);
220 for language_settings in languages.values_mut() {
221 merge_settings(language_settings, &user_settings.defaults);
222 }
223
224 // A user's language-specific settings override default language-specific settings.
225 for (language_name, user_language_settings) in &user_settings.languages {
226 merge_settings(
227 languages
228 .entry(language_name.clone())
229 .or_insert_with(|| defaults.clone()),
230 &user_language_settings,
231 );
232 }
233 }
234
235 Ok(Self {
236 copilot: CopilotSettings {
237 feature_enabled: copilot_enabled,
238 disabled_globs: copilot_globs
239 .iter()
240 .filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher()))
241 .collect(),
242 },
243 defaults,
244 languages,
245 })
246 }
247
248 fn json_schema(
249 generator: &mut schemars::gen::SchemaGenerator,
250 params: &settings::SettingsJsonSchemaParams,
251 _: &AppContext,
252 ) -> schemars::schema::RootSchema {
253 let mut root_schema = generator.root_schema_for::<Self::FileContent>();
254
255 // Create a schema for a 'languages overrides' object, associating editor
256 // settings with specific langauges.
257 assert!(root_schema
258 .definitions
259 .contains_key("LanguageSettingsContent"));
260
261 let languages_object_schema = SchemaObject {
262 instance_type: Some(InstanceType::Object.into()),
263 object: Some(Box::new(ObjectValidation {
264 properties: params
265 .language_names
266 .iter()
267 .map(|name| {
268 (
269 name.clone(),
270 Schema::new_ref("#/definitions/LanguageSettingsContent".into()),
271 )
272 })
273 .collect(),
274 ..Default::default()
275 })),
276 ..Default::default()
277 };
278
279 root_schema
280 .definitions
281 .extend([("Languages".into(), languages_object_schema.into())]);
282
283 root_schema
284 .schema
285 .object
286 .as_mut()
287 .unwrap()
288 .properties
289 .extend([
290 (
291 "languages".to_owned(),
292 Schema::new_ref("#/definitions/Languages".into()),
293 ),
294 // For backward compatibility
295 (
296 "language_overrides".to_owned(),
297 Schema::new_ref("#/definitions/Languages".into()),
298 ),
299 ]);
300
301 root_schema
302 }
303}
304
305fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
306 merge(&mut settings.tab_size, src.tab_size);
307 merge(&mut settings.hard_tabs, src.hard_tabs);
308 merge(&mut settings.soft_wrap, src.soft_wrap);
309 merge(
310 &mut settings.preferred_line_length,
311 src.preferred_line_length,
312 );
313 merge(&mut settings.formatter, src.formatter.clone());
314 merge(&mut settings.format_on_save, src.format_on_save.clone());
315 merge(
316 &mut settings.remove_trailing_whitespace_on_save,
317 src.remove_trailing_whitespace_on_save,
318 );
319 merge(
320 &mut settings.ensure_final_newline_on_save,
321 src.ensure_final_newline_on_save,
322 );
323 merge(
324 &mut settings.enable_language_server,
325 src.enable_language_server,
326 );
327 merge(
328 &mut settings.show_copilot_suggestions,
329 src.show_copilot_suggestions,
330 );
331 merge(&mut settings.show_whitespaces, src.show_whitespaces);
332
333 fn merge<T>(target: &mut T, value: Option<T>) {
334 if let Some(value) = value {
335 *target = value;
336 }
337 }
338}