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