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