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