1//! Provides `language`-related settings.
2
3use crate::{File, Language};
4use anyhow::Result;
5use collections::{HashMap, HashSet};
6use globset::GlobMatcher;
7use gpui::AppContext;
8use schemars::{
9 schema::{InstanceType, ObjectValidation, Schema, SchemaObject},
10 JsonSchema,
11};
12use serde::{Deserialize, Serialize};
13use settings::{Settings, SettingsLocation};
14use std::{num::NonZeroU32, path::Path, sync::Arc};
15
16impl<'a> Into<SettingsLocation<'a>> for &'a dyn File {
17 fn into(self) -> SettingsLocation<'a> {
18 SettingsLocation {
19 worktree_id: self.worktree_id(),
20 path: self.path().as_ref(),
21 }
22 }
23}
24
25/// Initializes the language settings.
26pub fn init(cx: &mut AppContext) {
27 AllLanguageSettings::register(cx);
28}
29
30/// Returns the settings for the specified language from the provided file.
31pub fn language_settings<'a>(
32 language: Option<&Arc<Language>>,
33 file: Option<&Arc<dyn File>>,
34 cx: &'a AppContext,
35) -> &'a LanguageSettings {
36 let language_name = language.map(|l| l.name());
37 all_language_settings(file, cx).language(language_name.as_deref())
38}
39
40/// Returns the settings for all languages from the provided file.
41pub fn all_language_settings<'a>(
42 file: Option<&Arc<dyn File>>,
43 cx: &'a AppContext,
44) -> &'a AllLanguageSettings {
45 let location = file.map(|f| f.as_ref().into());
46 AllLanguageSettings::get(location, cx)
47}
48
49/// The settings for all languages.
50#[derive(Debug, Clone)]
51pub struct AllLanguageSettings {
52 /// The settings for GitHub Copilot.
53 pub copilot: CopilotSettings,
54 defaults: LanguageSettings,
55 languages: HashMap<Arc<str>, LanguageSettings>,
56 pub(crate) file_types: HashMap<Arc<str>, Vec<String>>,
57}
58
59/// The settings for a particular language.
60#[derive(Debug, Clone, Deserialize)]
61pub struct LanguageSettings {
62 /// How many columns a tab should occupy.
63 pub tab_size: NonZeroU32,
64 /// Whether to indent lines using tab characters, as opposed to multiple
65 /// spaces.
66 pub hard_tabs: bool,
67 /// How to soft-wrap long lines of text.
68 pub soft_wrap: SoftWrap,
69 /// The column at which to soft-wrap lines, for buffers where soft-wrap
70 /// is enabled.
71 pub preferred_line_length: u32,
72 /// Whether to show wrap guides in the editor. Setting this to true will
73 /// show a guide at the 'preferred_line_length' value if softwrap is set to
74 /// 'preferred_line_length', and will show any additional guides as specified
75 /// by the 'wrap_guides' setting.
76 pub show_wrap_guides: bool,
77 /// Character counts at which to show wrap guides in the editor.
78 pub wrap_guides: Vec<usize>,
79 /// Whether or not to perform a buffer format before saving.
80 pub format_on_save: FormatOnSave,
81 /// Whether or not to remove any trailing whitespace from lines of a buffer
82 /// before saving it.
83 pub remove_trailing_whitespace_on_save: bool,
84 /// Whether or not to ensure there's a single newline at the end of a buffer
85 /// when saving it.
86 pub ensure_final_newline_on_save: bool,
87 /// How to perform a buffer format.
88 pub formatter: Formatter,
89 /// Zed's Prettier integration settings.
90 /// If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if
91 /// the project has no other Prettier installed.
92 pub prettier: HashMap<String, serde_json::Value>,
93 /// Whether to use language servers to provide code intelligence.
94 pub enable_language_server: bool,
95 /// Controls whether Copilot provides suggestion immediately (true)
96 /// or waits for a `copilot::Toggle` (false).
97 pub show_copilot_suggestions: bool,
98 /// Whether to show tabs and spaces in the editor.
99 pub show_whitespaces: ShowWhitespaceSetting,
100 /// Whether to start a new line with a comment when a previous line is a comment as well.
101 pub extend_comment_on_newline: bool,
102 /// Inlay hint related settings.
103 pub inlay_hints: InlayHintSettings,
104 /// Whether to automatically close brackets.
105 pub use_autoclose: bool,
106 /// Which code actions to run on save
107 pub code_actions_on_format: HashMap<String, bool>,
108}
109
110/// The settings for [GitHub Copilot](https://github.com/features/copilot).
111#[derive(Clone, Debug, Default)]
112pub struct CopilotSettings {
113 /// Whether Copilot is enabled.
114 pub feature_enabled: bool,
115 /// A list of globs representing files that Copilot should be disabled for.
116 pub disabled_globs: Vec<GlobMatcher>,
117}
118
119/// The settings for all languages.
120#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
121pub struct AllLanguageSettingsContent {
122 /// The settings for enabling/disabling features.
123 #[serde(default)]
124 pub features: Option<FeaturesContent>,
125 /// The settings for GitHub Copilot.
126 #[serde(default)]
127 pub copilot: Option<CopilotSettingsContent>,
128 /// The default language settings.
129 #[serde(flatten)]
130 pub defaults: LanguageSettingsContent,
131 /// The settings for individual languages.
132 #[serde(default, alias = "language_overrides")]
133 pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
134 /// Settings for associating file extensions and filenames
135 /// with languages.
136 #[serde(default)]
137 pub file_types: HashMap<Arc<str>, Vec<String>>,
138}
139
140/// The settings for a particular language.
141#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
142pub struct LanguageSettingsContent {
143 /// How many columns a tab should occupy.
144 ///
145 /// Default: 4
146 #[serde(default)]
147 pub tab_size: Option<NonZeroU32>,
148 /// Whether to indent lines using tab characters, as opposed to multiple
149 /// spaces.
150 ///
151 /// Default: false
152 #[serde(default)]
153 pub hard_tabs: Option<bool>,
154 /// How to soft-wrap long lines of text.
155 ///
156 /// Default: none
157 #[serde(default)]
158 pub soft_wrap: Option<SoftWrap>,
159 /// The column at which to soft-wrap lines, for buffers where soft-wrap
160 /// is enabled.
161 ///
162 /// Default: 80
163 #[serde(default)]
164 pub preferred_line_length: Option<u32>,
165 /// Whether to show wrap guides in the editor. Setting this to true will
166 /// show a guide at the 'preferred_line_length' value if softwrap is set to
167 /// 'preferred_line_length', and will show any additional guides as specified
168 /// by the 'wrap_guides' setting.
169 ///
170 /// Default: true
171 #[serde(default)]
172 pub show_wrap_guides: Option<bool>,
173 /// Character counts at which to show wrap guides in the editor.
174 ///
175 /// Default: []
176 #[serde(default)]
177 pub wrap_guides: Option<Vec<usize>>,
178 /// Whether or not to perform a buffer format before saving.
179 ///
180 /// Default: on
181 #[serde(default)]
182 pub format_on_save: Option<FormatOnSave>,
183 /// Whether or not to remove any trailing whitespace from lines of a buffer
184 /// before saving it.
185 ///
186 /// Default: true
187 #[serde(default)]
188 pub remove_trailing_whitespace_on_save: Option<bool>,
189 /// Whether or not to ensure there's a single newline at the end of a buffer
190 /// when saving it.
191 ///
192 /// Default: true
193 #[serde(default)]
194 pub ensure_final_newline_on_save: Option<bool>,
195 /// How to perform a buffer format.
196 ///
197 /// Default: auto
198 #[serde(default)]
199 pub formatter: Option<Formatter>,
200 /// Zed's Prettier integration settings.
201 /// If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if
202 /// the project has no other Prettier installed.
203 ///
204 /// Default: {}
205 #[serde(default)]
206 pub prettier: Option<HashMap<String, serde_json::Value>>,
207 /// Whether to use language servers to provide code intelligence.
208 ///
209 /// Default: true
210 #[serde(default)]
211 pub enable_language_server: Option<bool>,
212 /// Controls whether Copilot provides suggestion immediately (true)
213 /// or waits for a `copilot::Toggle` (false).
214 ///
215 /// Default: true
216 #[serde(default)]
217 pub show_copilot_suggestions: Option<bool>,
218 /// Whether to show tabs and spaces in the editor.
219 #[serde(default)]
220 pub show_whitespaces: Option<ShowWhitespaceSetting>,
221 /// Whether to start a new line with a comment when a previous line is a comment as well.
222 ///
223 /// Default: true
224 #[serde(default)]
225 pub extend_comment_on_newline: Option<bool>,
226 /// Inlay hint related settings.
227 #[serde(default)]
228 pub inlay_hints: Option<InlayHintSettings>,
229 /// Whether to automatically type closing characters for you. For example,
230 /// when you type (, Zed will automatically add a closing ) at the correct position.
231 ///
232 /// Default: true
233 pub use_autoclose: Option<bool>,
234
235 /// Which code actions to run on save
236 ///
237 /// Default: {} (or {"source.organizeImports": true} for Go).
238 pub code_actions_on_format: Option<HashMap<String, bool>>,
239}
240
241/// The contents of the GitHub Copilot settings.
242#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
243pub struct CopilotSettingsContent {
244 /// A list of globs representing files that Copilot should be disabled for.
245 #[serde(default)]
246 pub disabled_globs: Option<Vec<String>>,
247}
248
249/// The settings for enabling/disabling features.
250#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
251#[serde(rename_all = "snake_case")]
252pub struct FeaturesContent {
253 /// Whether the GitHub Copilot feature is enabled.
254 pub copilot: Option<bool>,
255}
256
257/// Controls the soft-wrapping behavior in the editor.
258#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
259#[serde(rename_all = "snake_case")]
260pub enum SoftWrap {
261 /// Do not soft wrap.
262 None,
263 /// Soft wrap lines that overflow the editor
264 EditorWidth,
265 /// Soft wrap lines at the preferred line length
266 PreferredLineLength,
267}
268
269/// Controls the behavior of formatting files when they are saved.
270#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
271#[serde(rename_all = "snake_case")]
272pub enum FormatOnSave {
273 /// Files should be formatted on save.
274 On,
275 /// Files should not be formatted on save.
276 Off,
277 /// Files should be formatted using the current language server.
278 LanguageServer,
279 /// The external program to use to format the files on save.
280 External {
281 /// The external program to run.
282 command: Arc<str>,
283 /// The arguments to pass to the program.
284 arguments: Arc<[String]>,
285 },
286}
287
288/// Controls how whitespace should be displayedin the editor.
289#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
290#[serde(rename_all = "snake_case")]
291pub enum ShowWhitespaceSetting {
292 /// Draw whitespace only for the selected text.
293 Selection,
294 /// Do not draw any tabs or spaces.
295 None,
296 /// Draw all invisible symbols.
297 All,
298}
299
300/// Controls which formatter should be used when formatting code.
301#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
302#[serde(rename_all = "snake_case")]
303pub enum Formatter {
304 /// Format files using Zed's Prettier integration (if applicable),
305 /// or falling back to formatting via language server.
306 #[default]
307 Auto,
308 /// Format code using the current language server.
309 LanguageServer,
310 /// Format code using Zed's Prettier integration.
311 Prettier,
312 /// Format code using an external command.
313 External {
314 /// The external program to run.
315 command: Arc<str>,
316 /// The arguments to pass to the program.
317 arguments: Arc<[String]>,
318 },
319}
320
321/// The settings for inlay hints.
322#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
323pub struct InlayHintSettings {
324 /// Global switch to toggle hints on and off.
325 ///
326 /// Default: false
327 #[serde(default)]
328 pub enabled: bool,
329 /// Whether type hints should be shown.
330 ///
331 /// Default: true
332 #[serde(default = "default_true")]
333 pub show_type_hints: bool,
334 /// Whether parameter hints should be shown.
335 ///
336 /// Default: true
337 #[serde(default = "default_true")]
338 pub show_parameter_hints: bool,
339 /// Whether other hints should be shown.
340 ///
341 /// Default: true
342 #[serde(default = "default_true")]
343 pub show_other_hints: bool,
344 /// Whether or not to debounce inlay hints updates after buffer edits.
345 ///
346 /// Set to 0 to disable debouncing.
347 ///
348 /// Default: 700
349 #[serde(default = "edit_debounce_ms")]
350 pub edit_debounce_ms: u64,
351 /// Whether or not to debounce inlay hints updates after buffer scrolls.
352 ///
353 /// Set to 0 to disable debouncing.
354 ///
355 /// Default: 50
356 #[serde(default = "scroll_debounce_ms")]
357 pub scroll_debounce_ms: u64,
358}
359
360fn default_true() -> bool {
361 true
362}
363
364fn edit_debounce_ms() -> u64 {
365 700
366}
367
368fn scroll_debounce_ms() -> u64 {
369 50
370}
371
372impl InlayHintSettings {
373 /// Returns the kinds of inlay hints that are enabled based on the settings.
374 pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
375 let mut kinds = HashSet::default();
376 if self.show_type_hints {
377 kinds.insert(Some(InlayHintKind::Type));
378 }
379 if self.show_parameter_hints {
380 kinds.insert(Some(InlayHintKind::Parameter));
381 }
382 if self.show_other_hints {
383 kinds.insert(None);
384 }
385 kinds
386 }
387}
388
389impl AllLanguageSettings {
390 /// Returns the [`LanguageSettings`] for the language with the specified name.
391 pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
392 if let Some(name) = language_name {
393 if let Some(overrides) = self.languages.get(name) {
394 return overrides;
395 }
396 }
397 &self.defaults
398 }
399
400 /// Returns whether GitHub Copilot is enabled for the given path.
401 pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
402 !self
403 .copilot
404 .disabled_globs
405 .iter()
406 .any(|glob| glob.is_match(path))
407 }
408
409 /// Returns whether GitHub Copilot is enabled for the given language and path.
410 pub fn copilot_enabled(&self, language: Option<&Arc<Language>>, path: Option<&Path>) -> bool {
411 if !self.copilot.feature_enabled {
412 return false;
413 }
414
415 if let Some(path) = path {
416 if !self.copilot_enabled_for_path(path) {
417 return false;
418 }
419 }
420
421 self.language(language.map(|l| l.name()).as_deref())
422 .show_copilot_suggestions
423 }
424}
425
426/// The kind of an inlay hint.
427#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
428pub enum InlayHintKind {
429 /// An inlay hint for a type.
430 Type,
431 /// An inlay hint for a parameter.
432 Parameter,
433}
434
435impl InlayHintKind {
436 /// Returns the [`InlayHintKind`] from the given name.
437 ///
438 /// Returns `None` if `name` does not match any of the expected
439 /// string representations.
440 pub fn from_name(name: &str) -> Option<Self> {
441 match name {
442 "type" => Some(InlayHintKind::Type),
443 "parameter" => Some(InlayHintKind::Parameter),
444 _ => None,
445 }
446 }
447
448 /// Returns the name of this [`InlayHintKind`].
449 pub fn name(&self) -> &'static str {
450 match self {
451 InlayHintKind::Type => "type",
452 InlayHintKind::Parameter => "parameter",
453 }
454 }
455}
456
457impl settings::Settings for AllLanguageSettings {
458 const KEY: Option<&'static str> = None;
459
460 type FileContent = AllLanguageSettingsContent;
461
462 fn load(
463 default_value: &Self::FileContent,
464 user_settings: &[&Self::FileContent],
465 _: &mut AppContext,
466 ) -> Result<Self> {
467 // A default is provided for all settings.
468 let mut defaults: LanguageSettings =
469 serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
470
471 let mut languages = HashMap::default();
472 for (language_name, settings) in &default_value.languages {
473 let mut language_settings = defaults.clone();
474 merge_settings(&mut language_settings, settings);
475 languages.insert(language_name.clone(), language_settings);
476 }
477
478 let mut copilot_enabled = default_value
479 .features
480 .as_ref()
481 .and_then(|f| f.copilot)
482 .ok_or_else(Self::missing_default)?;
483 let mut copilot_globs = default_value
484 .copilot
485 .as_ref()
486 .and_then(|c| c.disabled_globs.as_ref())
487 .ok_or_else(Self::missing_default)?;
488
489 for user_settings in user_settings {
490 if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
491 copilot_enabled = copilot;
492 }
493 if let Some(globs) = user_settings
494 .copilot
495 .as_ref()
496 .and_then(|f| f.disabled_globs.as_ref())
497 {
498 copilot_globs = globs;
499 }
500
501 // A user's global settings override the default global settings and
502 // all default language-specific settings.
503 merge_settings(&mut defaults, &user_settings.defaults);
504 for language_settings in languages.values_mut() {
505 merge_settings(language_settings, &user_settings.defaults);
506 }
507
508 // A user's language-specific settings override default language-specific settings.
509 for (language_name, user_language_settings) in &user_settings.languages {
510 merge_settings(
511 languages
512 .entry(language_name.clone())
513 .or_insert_with(|| defaults.clone()),
514 user_language_settings,
515 );
516 }
517 }
518
519 let mut file_types: HashMap<Arc<str>, Vec<String>> = HashMap::default();
520 for user_file_types in user_settings.iter().map(|s| &s.file_types) {
521 for (language, suffixes) in user_file_types {
522 file_types
523 .entry(language.clone())
524 .or_default()
525 .extend_from_slice(suffixes);
526 }
527 }
528
529 Ok(Self {
530 copilot: CopilotSettings {
531 feature_enabled: copilot_enabled,
532 disabled_globs: copilot_globs
533 .iter()
534 .filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher()))
535 .collect(),
536 },
537 defaults,
538 languages,
539 file_types,
540 })
541 }
542
543 fn json_schema(
544 generator: &mut schemars::gen::SchemaGenerator,
545 params: &settings::SettingsJsonSchemaParams,
546 _: &AppContext,
547 ) -> schemars::schema::RootSchema {
548 let mut root_schema = generator.root_schema_for::<Self::FileContent>();
549
550 // Create a schema for a 'languages overrides' object, associating editor
551 // settings with specific languages.
552 assert!(root_schema
553 .definitions
554 .contains_key("LanguageSettingsContent"));
555
556 let languages_object_schema = SchemaObject {
557 instance_type: Some(InstanceType::Object.into()),
558 object: Some(Box::new(ObjectValidation {
559 properties: params
560 .language_names
561 .iter()
562 .map(|name| {
563 (
564 name.clone(),
565 Schema::new_ref("#/definitions/LanguageSettingsContent".into()),
566 )
567 })
568 .collect(),
569 ..Default::default()
570 })),
571 ..Default::default()
572 };
573
574 root_schema
575 .definitions
576 .extend([("Languages".into(), languages_object_schema.into())]);
577
578 root_schema
579 .schema
580 .object
581 .as_mut()
582 .unwrap()
583 .properties
584 .extend([
585 (
586 "languages".to_owned(),
587 Schema::new_ref("#/definitions/Languages".into()),
588 ),
589 // For backward compatibility
590 (
591 "language_overrides".to_owned(),
592 Schema::new_ref("#/definitions/Languages".into()),
593 ),
594 ]);
595
596 root_schema
597 }
598}
599
600fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
601 merge(&mut settings.tab_size, src.tab_size);
602 merge(&mut settings.hard_tabs, src.hard_tabs);
603 merge(&mut settings.soft_wrap, src.soft_wrap);
604 merge(&mut settings.use_autoclose, src.use_autoclose);
605 merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
606 merge(&mut settings.wrap_guides, src.wrap_guides.clone());
607 merge(
608 &mut settings.code_actions_on_format,
609 src.code_actions_on_format.clone(),
610 );
611
612 merge(
613 &mut settings.preferred_line_length,
614 src.preferred_line_length,
615 );
616 merge(&mut settings.formatter, src.formatter.clone());
617 merge(&mut settings.prettier, src.prettier.clone());
618 merge(&mut settings.format_on_save, src.format_on_save.clone());
619 merge(
620 &mut settings.remove_trailing_whitespace_on_save,
621 src.remove_trailing_whitespace_on_save,
622 );
623 merge(
624 &mut settings.ensure_final_newline_on_save,
625 src.ensure_final_newline_on_save,
626 );
627 merge(
628 &mut settings.enable_language_server,
629 src.enable_language_server,
630 );
631 merge(
632 &mut settings.show_copilot_suggestions,
633 src.show_copilot_suggestions,
634 );
635 merge(&mut settings.show_whitespaces, src.show_whitespaces);
636 merge(
637 &mut settings.extend_comment_on_newline,
638 src.extend_comment_on_newline,
639 );
640 merge(&mut settings.inlay_hints, src.inlay_hints);
641 fn merge<T>(target: &mut T, value: Option<T>) {
642 if let Some(value) = value {
643 *target = value;
644 }
645 }
646}