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, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
153#[serde(rename_all = "snake_case")]
154pub enum Formatter {
155 LanguageServer,
156 External {
157 command: Arc<str>,
158 arguments: Arc<[String]>,
159 },
160}
161
162#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
163pub struct InlayHintSettings {
164 #[serde(default)]
165 pub enabled: bool,
166 #[serde(default = "default_true")]
167 pub show_type_hints: bool,
168 #[serde(default = "default_true")]
169 pub show_parameter_hints: bool,
170 #[serde(default = "default_true")]
171 pub show_other_hints: bool,
172}
173
174fn default_true() -> bool {
175 true
176}
177
178impl InlayHintSettings {
179 pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
180 let mut kinds = HashSet::default();
181 if self.show_type_hints {
182 kinds.insert(Some(InlayHintKind::Type));
183 }
184 if self.show_parameter_hints {
185 kinds.insert(Some(InlayHintKind::Parameter));
186 }
187 if self.show_other_hints {
188 kinds.insert(None);
189 }
190 kinds
191 }
192}
193
194impl AllLanguageSettings {
195 pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
196 if let Some(name) = language_name {
197 if let Some(overrides) = self.languages.get(name) {
198 return overrides;
199 }
200 }
201 &self.defaults
202 }
203
204 pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
205 !self
206 .copilot
207 .disabled_globs
208 .iter()
209 .any(|glob| glob.is_match(path))
210 }
211
212 pub fn copilot_enabled(&self, language: Option<&Arc<Language>>, path: Option<&Path>) -> bool {
213 if !self.copilot.feature_enabled {
214 return false;
215 }
216
217 if let Some(path) = path {
218 if !self.copilot_enabled_for_path(path) {
219 return false;
220 }
221 }
222
223 self.language(language.map(|l| l.name()).as_deref())
224 .show_copilot_suggestions
225 }
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
229pub enum InlayHintKind {
230 Type,
231 Parameter,
232}
233
234impl InlayHintKind {
235 pub fn from_name(name: &str) -> Option<Self> {
236 match name {
237 "type" => Some(InlayHintKind::Type),
238 "parameter" => Some(InlayHintKind::Parameter),
239 _ => None,
240 }
241 }
242
243 pub fn name(&self) -> &'static str {
244 match self {
245 InlayHintKind::Type => "type",
246 InlayHintKind::Parameter => "parameter",
247 }
248 }
249}
250
251impl settings::Setting for AllLanguageSettings {
252 const KEY: Option<&'static str> = None;
253
254 type FileContent = AllLanguageSettingsContent;
255
256 fn load(
257 default_value: &Self::FileContent,
258 user_settings: &[&Self::FileContent],
259 _: &AppContext,
260 ) -> Result<Self> {
261 // A default is provided for all settings.
262 let mut defaults: LanguageSettings =
263 serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
264
265 let mut languages = HashMap::default();
266 for (language_name, settings) in &default_value.languages {
267 let mut language_settings = defaults.clone();
268 merge_settings(&mut language_settings, &settings);
269 languages.insert(language_name.clone(), language_settings);
270 }
271
272 let mut copilot_enabled = default_value
273 .features
274 .as_ref()
275 .and_then(|f| f.copilot)
276 .ok_or_else(Self::missing_default)?;
277 let mut copilot_globs = default_value
278 .copilot
279 .as_ref()
280 .and_then(|c| c.disabled_globs.as_ref())
281 .ok_or_else(Self::missing_default)?;
282
283 for user_settings in user_settings {
284 if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
285 copilot_enabled = copilot;
286 }
287 if let Some(globs) = user_settings
288 .copilot
289 .as_ref()
290 .and_then(|f| f.disabled_globs.as_ref())
291 {
292 copilot_globs = globs;
293 }
294
295 // A user's global settings override the default global settings and
296 // all default language-specific settings.
297 merge_settings(&mut defaults, &user_settings.defaults);
298 for language_settings in languages.values_mut() {
299 merge_settings(language_settings, &user_settings.defaults);
300 }
301
302 // A user's language-specific settings override default language-specific settings.
303 for (language_name, user_language_settings) in &user_settings.languages {
304 merge_settings(
305 languages
306 .entry(language_name.clone())
307 .or_insert_with(|| defaults.clone()),
308 &user_language_settings,
309 );
310 }
311 }
312
313 Ok(Self {
314 copilot: CopilotSettings {
315 feature_enabled: copilot_enabled,
316 disabled_globs: copilot_globs
317 .iter()
318 .filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher()))
319 .collect(),
320 },
321 defaults,
322 languages,
323 })
324 }
325
326 fn json_schema(
327 generator: &mut schemars::gen::SchemaGenerator,
328 params: &settings::SettingsJsonSchemaParams,
329 _: &AppContext,
330 ) -> schemars::schema::RootSchema {
331 let mut root_schema = generator.root_schema_for::<Self::FileContent>();
332
333 // Create a schema for a 'languages overrides' object, associating editor
334 // settings with specific languages.
335 assert!(root_schema
336 .definitions
337 .contains_key("LanguageSettingsContent"));
338
339 let languages_object_schema = SchemaObject {
340 instance_type: Some(InstanceType::Object.into()),
341 object: Some(Box::new(ObjectValidation {
342 properties: params
343 .language_names
344 .iter()
345 .map(|name| {
346 (
347 name.clone(),
348 Schema::new_ref("#/definitions/LanguageSettingsContent".into()),
349 )
350 })
351 .collect(),
352 ..Default::default()
353 })),
354 ..Default::default()
355 };
356
357 root_schema
358 .definitions
359 .extend([("Languages".into(), languages_object_schema.into())]);
360
361 root_schema
362 .schema
363 .object
364 .as_mut()
365 .unwrap()
366 .properties
367 .extend([
368 (
369 "languages".to_owned(),
370 Schema::new_ref("#/definitions/Languages".into()),
371 ),
372 // For backward compatibility
373 (
374 "language_overrides".to_owned(),
375 Schema::new_ref("#/definitions/Languages".into()),
376 ),
377 ]);
378
379 root_schema
380 }
381}
382
383fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
384 merge(&mut settings.tab_size, src.tab_size);
385 merge(&mut settings.hard_tabs, src.hard_tabs);
386 merge(&mut settings.soft_wrap, src.soft_wrap);
387 merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
388 merge(&mut settings.wrap_guides, src.wrap_guides.clone());
389
390 merge(
391 &mut settings.preferred_line_length,
392 src.preferred_line_length,
393 );
394 merge(&mut settings.formatter, src.formatter.clone());
395 merge(&mut settings.format_on_save, src.format_on_save.clone());
396 merge(
397 &mut settings.remove_trailing_whitespace_on_save,
398 src.remove_trailing_whitespace_on_save,
399 );
400 merge(
401 &mut settings.ensure_final_newline_on_save,
402 src.ensure_final_newline_on_save,
403 );
404 merge(
405 &mut settings.enable_language_server,
406 src.enable_language_server,
407 );
408 merge(
409 &mut settings.show_copilot_suggestions,
410 src.show_copilot_suggestions,
411 );
412 merge(&mut settings.show_whitespaces, src.show_whitespaces);
413 merge(
414 &mut settings.extend_comment_on_newline,
415 src.extend_comment_on_newline,
416 );
417 merge(&mut settings.inlay_hints, src.inlay_hints);
418 fn merge<T>(target: &mut T, value: Option<T>) {
419 if let Some(value) = value {
420 *target = value;
421 }
422 }
423}