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