language_settings.rs

  1//! Provides `language`-related settings.
  2
  3use crate::{File, Language, LanguageServerName};
  4use anyhow::Result;
  5use collections::{HashMap, HashSet};
  6use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
  7use gpui::AppContext;
  8use itertools::{Either, Itertools};
  9use schemars::{
 10    schema::{InstanceType, ObjectValidation, Schema, SchemaObject},
 11    JsonSchema,
 12};
 13use serde::{Deserialize, Serialize};
 14use settings::{Settings, SettingsLocation, SettingsSources};
 15use std::{num::NonZeroU32, path::Path, sync::Arc};
 16use util::serde::default_true;
 17
 18impl<'a> Into<SettingsLocation<'a>> for &'a dyn File {
 19    fn into(self) -> SettingsLocation<'a> {
 20        SettingsLocation {
 21            worktree_id: self.worktree_id(),
 22            path: self.path().as_ref(),
 23        }
 24    }
 25}
 26
 27/// Initializes the language settings.
 28pub fn init(cx: &mut AppContext) {
 29    AllLanguageSettings::register(cx);
 30}
 31
 32/// Returns the settings for the specified language from the provided file.
 33pub fn language_settings<'a>(
 34    language: Option<&Arc<Language>>,
 35    file: Option<&Arc<dyn File>>,
 36    cx: &'a AppContext,
 37) -> &'a LanguageSettings {
 38    let language_name = language.map(|l| l.name());
 39    all_language_settings(file, cx).language(language_name.as_deref())
 40}
 41
 42/// Returns the settings for all languages from the provided file.
 43pub fn all_language_settings<'a>(
 44    file: Option<&Arc<dyn File>>,
 45    cx: &'a AppContext,
 46) -> &'a AllLanguageSettings {
 47    let location = file.map(|f| f.as_ref().into());
 48    AllLanguageSettings::get(location, cx)
 49}
 50
 51/// The settings for all languages.
 52#[derive(Debug, Clone)]
 53pub struct AllLanguageSettings {
 54    /// The inline completion settings.
 55    pub inline_completions: InlineCompletionSettings,
 56    defaults: LanguageSettings,
 57    languages: HashMap<Arc<str>, LanguageSettings>,
 58    pub(crate) file_types: HashMap<Arc<str>, GlobSet>,
 59}
 60
 61/// The settings for a particular language.
 62#[derive(Debug, Clone, Deserialize)]
 63pub struct LanguageSettings {
 64    /// How many columns a tab should occupy.
 65    pub tab_size: NonZeroU32,
 66    /// Whether to indent lines using tab characters, as opposed to multiple
 67    /// spaces.
 68    pub hard_tabs: bool,
 69    /// How to soft-wrap long lines of text.
 70    pub soft_wrap: SoftWrap,
 71    /// The column at which to soft-wrap lines, for buffers where soft-wrap
 72    /// is enabled.
 73    pub preferred_line_length: u32,
 74    /// Whether to show wrap guides in the editor. Setting this to true will
 75    /// show a guide at the 'preferred_line_length' value if softwrap is set to
 76    /// 'preferred_line_length', and will show any additional guides as specified
 77    /// by the 'wrap_guides' setting.
 78    pub show_wrap_guides: bool,
 79    /// Character counts at which to show wrap guides in the editor.
 80    pub wrap_guides: Vec<usize>,
 81    /// Whether or not to perform a buffer format before saving.
 82    pub format_on_save: FormatOnSave,
 83    /// Whether or not to remove any trailing whitespace from lines of a buffer
 84    /// before saving it.
 85    pub remove_trailing_whitespace_on_save: bool,
 86    /// Whether or not to ensure there's a single newline at the end of a buffer
 87    /// when saving it.
 88    pub ensure_final_newline_on_save: bool,
 89    /// How to perform a buffer format.
 90    pub formatter: Formatter,
 91    /// Zed's Prettier integration settings.
 92    pub prettier: PrettierSettings,
 93    /// Whether to use language servers to provide code intelligence.
 94    pub enable_language_server: bool,
 95    /// The list of language servers to use (or disable) for this language.
 96    ///
 97    /// This array should consist of language server IDs, as well as the following
 98    /// special tokens:
 99    /// - `"!<language_server_id>"` - A language server ID prefixed with a `!` will be disabled.
100    /// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
101    pub language_servers: Vec<Arc<str>>,
102    /// Controls whether inline completions are shown immediately (true)
103    /// or manually by triggering `editor::ShowInlineCompletion` (false).
104    pub show_inline_completions: bool,
105    /// Whether to show tabs and spaces in the editor.
106    pub show_whitespaces: ShowWhitespaceSetting,
107    /// Whether to start a new line with a comment when a previous line is a comment as well.
108    pub extend_comment_on_newline: bool,
109    /// Inlay hint related settings.
110    pub inlay_hints: InlayHintSettings,
111    /// Whether to automatically close brackets.
112    pub use_autoclose: bool,
113    // Controls how the editor handles the autoclosed characters.
114    pub always_treat_brackets_as_autoclosed: bool,
115    /// Which code actions to run on save
116    pub code_actions_on_format: HashMap<String, bool>,
117}
118
119impl LanguageSettings {
120    /// A token representing the rest of the available language servers.
121    const REST_OF_LANGUAGE_SERVERS: &'static str = "...";
122
123    /// Returns the customized list of language servers from the list of
124    /// available language servers.
125    pub fn customized_language_servers(
126        &self,
127        available_language_servers: &[LanguageServerName],
128    ) -> Vec<LanguageServerName> {
129        Self::resolve_language_servers(&self.language_servers, available_language_servers)
130    }
131
132    pub(crate) fn resolve_language_servers(
133        configured_language_servers: &[Arc<str>],
134        available_language_servers: &[LanguageServerName],
135    ) -> Vec<LanguageServerName> {
136        let (disabled_language_servers, enabled_language_servers): (Vec<Arc<str>>, Vec<Arc<str>>) =
137            configured_language_servers.iter().partition_map(
138                |language_server| match language_server.strip_prefix('!') {
139                    Some(disabled) => Either::Left(disabled.into()),
140                    None => Either::Right(language_server.clone()),
141                },
142            );
143
144        let rest = available_language_servers
145            .into_iter()
146            .filter(|&available_language_server| {
147                !disabled_language_servers.contains(&&available_language_server.0)
148                    && !enabled_language_servers.contains(&&available_language_server.0)
149            })
150            .cloned()
151            .collect::<Vec<_>>();
152
153        enabled_language_servers
154            .into_iter()
155            .flat_map(|language_server| {
156                if language_server.as_ref() == Self::REST_OF_LANGUAGE_SERVERS {
157                    rest.clone()
158                } else {
159                    vec![LanguageServerName(language_server.clone())]
160                }
161            })
162            .collect::<Vec<_>>()
163    }
164}
165
166/// The provider that supplies inline completions.
167#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
168#[serde(rename_all = "snake_case")]
169pub enum InlineCompletionProvider {
170    None,
171    #[default]
172    Copilot,
173    Supermaven,
174}
175
176/// The settings for inline completions, such as [GitHub Copilot](https://github.com/features/copilot)
177/// or [Supermaven](https://supermaven.com).
178#[derive(Clone, Debug, Default)]
179pub struct InlineCompletionSettings {
180    /// The provider that supplies inline completions.
181    pub provider: InlineCompletionProvider,
182    /// A list of globs representing files that inline completions should be disabled for.
183    pub disabled_globs: Vec<GlobMatcher>,
184}
185
186/// The settings for all languages.
187#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
188pub struct AllLanguageSettingsContent {
189    /// The settings for enabling/disabling features.
190    #[serde(default)]
191    pub features: Option<FeaturesContent>,
192    /// The inline completion settings.
193    #[serde(default, alias = "copilot")]
194    pub inline_completions: Option<InlineCompletionSettingsContent>,
195    /// The default language settings.
196    #[serde(flatten)]
197    pub defaults: LanguageSettingsContent,
198    /// The settings for individual languages.
199    #[serde(default, alias = "language_overrides")]
200    pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
201    /// Settings for associating file extensions and filenames
202    /// with languages.
203    #[serde(default)]
204    pub file_types: HashMap<Arc<str>, Vec<String>>,
205}
206
207/// The settings for a particular language.
208#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
209pub struct LanguageSettingsContent {
210    /// How many columns a tab should occupy.
211    ///
212    /// Default: 4
213    #[serde(default)]
214    pub tab_size: Option<NonZeroU32>,
215    /// Whether to indent lines using tab characters, as opposed to multiple
216    /// spaces.
217    ///
218    /// Default: false
219    #[serde(default)]
220    pub hard_tabs: Option<bool>,
221    /// How to soft-wrap long lines of text.
222    ///
223    /// Default: none
224    #[serde(default)]
225    pub soft_wrap: Option<SoftWrap>,
226    /// The column at which to soft-wrap lines, for buffers where soft-wrap
227    /// is enabled.
228    ///
229    /// Default: 80
230    #[serde(default)]
231    pub preferred_line_length: Option<u32>,
232    /// Whether to show wrap guides in the editor. Setting this to true will
233    /// show a guide at the 'preferred_line_length' value if softwrap is set to
234    /// 'preferred_line_length', and will show any additional guides as specified
235    /// by the 'wrap_guides' setting.
236    ///
237    /// Default: true
238    #[serde(default)]
239    pub show_wrap_guides: Option<bool>,
240    /// Character counts at which to show wrap guides in the editor.
241    ///
242    /// Default: []
243    #[serde(default)]
244    pub wrap_guides: Option<Vec<usize>>,
245    /// Whether or not to perform a buffer format before saving.
246    ///
247    /// Default: on
248    #[serde(default)]
249    pub format_on_save: Option<FormatOnSave>,
250    /// Whether or not to remove any trailing whitespace from lines of a buffer
251    /// before saving it.
252    ///
253    /// Default: true
254    #[serde(default)]
255    pub remove_trailing_whitespace_on_save: Option<bool>,
256    /// Whether or not to ensure there's a single newline at the end of a buffer
257    /// when saving it.
258    ///
259    /// Default: true
260    #[serde(default)]
261    pub ensure_final_newline_on_save: Option<bool>,
262    /// How to perform a buffer format.
263    ///
264    /// Default: auto
265    #[serde(default)]
266    pub formatter: Option<Formatter>,
267    /// Zed's Prettier integration settings.
268    /// Allows to enable/disable formatting with Prettier
269    /// and configure default Prettier, used when no project-level Prettier installation is found.
270    ///
271    /// Default: off
272    #[serde(default)]
273    pub prettier: Option<PrettierSettings>,
274    /// Whether to use language servers to provide code intelligence.
275    ///
276    /// Default: true
277    #[serde(default)]
278    pub enable_language_server: Option<bool>,
279    /// The list of language servers to use (or disable) for this language.
280    ///
281    /// This array should consist of language server IDs, as well as the following
282    /// special tokens:
283    /// - `"!<language_server_id>"` - A language server ID prefixed with a `!` will be disabled.
284    /// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
285    ///
286    /// Default: ["..."]
287    #[serde(default)]
288    pub language_servers: Option<Vec<Arc<str>>>,
289    /// Controls whether inline completions are shown immediately (true)
290    /// or manually by triggering `editor::ShowInlineCompletion` (false).
291    ///
292    /// Default: true
293    #[serde(default, alias = "show_copilot_suggestions")]
294    pub show_inline_completions: Option<bool>,
295    /// Whether to show tabs and spaces in the editor.
296    #[serde(default)]
297    pub show_whitespaces: Option<ShowWhitespaceSetting>,
298    /// Whether to start a new line with a comment when a previous line is a comment as well.
299    ///
300    /// Default: true
301    #[serde(default)]
302    pub extend_comment_on_newline: Option<bool>,
303    /// Inlay hint related settings.
304    #[serde(default)]
305    pub inlay_hints: Option<InlayHintSettings>,
306    /// Whether to automatically type closing characters for you. For example,
307    /// when you type (, Zed will automatically add a closing ) at the correct position.
308    ///
309    /// Default: true
310    pub use_autoclose: Option<bool>,
311    // Controls how the editor handles the autoclosed characters.
312    // When set to `false`(default), skipping over and auto-removing of the closing characters
313    // happen only for auto-inserted characters.
314    // Otherwise(when `true`), the closing characters are always skipped over and auto-removed
315    // no matter how they were inserted.
316    ///
317    /// Default: false
318    pub always_treat_brackets_as_autoclosed: Option<bool>,
319    /// Which code actions to run on save after the formatter.
320    /// These are not run if formatting is off.
321    ///
322    /// Default: {} (or {"source.organizeImports": true} for Go).
323    pub code_actions_on_format: Option<HashMap<String, bool>>,
324}
325
326/// The contents of the inline completion settings.
327#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
328pub struct InlineCompletionSettingsContent {
329    /// A list of globs representing files that inline completions should be disabled for.
330    #[serde(default)]
331    pub disabled_globs: Option<Vec<String>>,
332}
333
334/// The settings for enabling/disabling features.
335#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
336#[serde(rename_all = "snake_case")]
337pub struct FeaturesContent {
338    /// Whether the GitHub Copilot feature is enabled.
339    pub copilot: Option<bool>,
340    /// Determines which inline completion provider to use.
341    pub inline_completion_provider: Option<InlineCompletionProvider>,
342}
343
344/// Controls the soft-wrapping behavior in the editor.
345#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
346#[serde(rename_all = "snake_case")]
347pub enum SoftWrap {
348    /// Do not soft wrap.
349    None,
350    /// Prefer a single line generally, unless an overly long line is encountered.
351    PreferLine,
352    /// Soft wrap lines that overflow the editor
353    EditorWidth,
354    /// Soft wrap lines at the preferred line length
355    PreferredLineLength,
356}
357
358/// Controls the behavior of formatting files when they are saved.
359#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
360#[serde(rename_all = "snake_case")]
361pub enum FormatOnSave {
362    /// Files should be formatted on save.
363    On,
364    /// Files should not be formatted on save.
365    Off,
366    /// Files should be formatted using the current language server.
367    LanguageServer,
368    /// The external program to use to format the files on save.
369    External {
370        /// The external program to run.
371        command: Arc<str>,
372        /// The arguments to pass to the program.
373        arguments: Arc<[String]>,
374    },
375    /// Files should be formatted using code actions executed by language servers.
376    CodeActions(HashMap<String, bool>),
377}
378
379/// Controls how whitespace should be displayedin the editor.
380#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
381#[serde(rename_all = "snake_case")]
382pub enum ShowWhitespaceSetting {
383    /// Draw whitespace only for the selected text.
384    Selection,
385    /// Do not draw any tabs or spaces.
386    None,
387    /// Draw all invisible symbols.
388    All,
389}
390
391/// Controls which formatter should be used when formatting code.
392#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
393#[serde(rename_all = "snake_case")]
394pub enum Formatter {
395    /// Format files using Zed's Prettier integration (if applicable),
396    /// or falling back to formatting via language server.
397    #[default]
398    Auto,
399    /// Format code using the current language server.
400    LanguageServer,
401    /// Format code using Zed's Prettier integration.
402    Prettier,
403    /// Format code using an external command.
404    External {
405        /// The external program to run.
406        command: Arc<str>,
407        /// The arguments to pass to the program.
408        arguments: Arc<[String]>,
409    },
410    /// Files should be formatted using code actions executed by language servers.
411    CodeActions(HashMap<String, bool>),
412}
413
414/// The settings for inlay hints.
415#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
416pub struct InlayHintSettings {
417    /// Global switch to toggle hints on and off.
418    ///
419    /// Default: false
420    #[serde(default)]
421    pub enabled: bool,
422    /// Whether type hints should be shown.
423    ///
424    /// Default: true
425    #[serde(default = "default_true")]
426    pub show_type_hints: bool,
427    /// Whether parameter hints should be shown.
428    ///
429    /// Default: true
430    #[serde(default = "default_true")]
431    pub show_parameter_hints: bool,
432    /// Whether other hints should be shown.
433    ///
434    /// Default: true
435    #[serde(default = "default_true")]
436    pub show_other_hints: bool,
437    /// Whether or not to debounce inlay hints updates after buffer edits.
438    ///
439    /// Set to 0 to disable debouncing.
440    ///
441    /// Default: 700
442    #[serde(default = "edit_debounce_ms")]
443    pub edit_debounce_ms: u64,
444    /// Whether or not to debounce inlay hints updates after buffer scrolls.
445    ///
446    /// Set to 0 to disable debouncing.
447    ///
448    /// Default: 50
449    #[serde(default = "scroll_debounce_ms")]
450    pub scroll_debounce_ms: u64,
451}
452
453fn edit_debounce_ms() -> u64 {
454    700
455}
456
457fn scroll_debounce_ms() -> u64 {
458    50
459}
460
461impl InlayHintSettings {
462    /// Returns the kinds of inlay hints that are enabled based on the settings.
463    pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
464        let mut kinds = HashSet::default();
465        if self.show_type_hints {
466            kinds.insert(Some(InlayHintKind::Type));
467        }
468        if self.show_parameter_hints {
469            kinds.insert(Some(InlayHintKind::Parameter));
470        }
471        if self.show_other_hints {
472            kinds.insert(None);
473        }
474        kinds
475    }
476}
477
478impl AllLanguageSettings {
479    /// Returns the [`LanguageSettings`] for the language with the specified name.
480    pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
481        if let Some(name) = language_name {
482            if let Some(overrides) = self.languages.get(name) {
483                return overrides;
484            }
485        }
486        &self.defaults
487    }
488
489    /// Returns whether inline completions are enabled for the given path.
490    pub fn inline_completions_enabled_for_path(&self, path: &Path) -> bool {
491        !self
492            .inline_completions
493            .disabled_globs
494            .iter()
495            .any(|glob| glob.is_match(path))
496    }
497
498    /// Returns whether inline completions are enabled for the given language and path.
499    pub fn inline_completions_enabled(
500        &self,
501        language: Option<&Arc<Language>>,
502        path: Option<&Path>,
503    ) -> bool {
504        if let Some(path) = path {
505            if !self.inline_completions_enabled_for_path(path) {
506                return false;
507            }
508        }
509
510        self.language(language.map(|l| l.name()).as_deref())
511            .show_inline_completions
512    }
513}
514
515/// The kind of an inlay hint.
516#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
517pub enum InlayHintKind {
518    /// An inlay hint for a type.
519    Type,
520    /// An inlay hint for a parameter.
521    Parameter,
522}
523
524impl InlayHintKind {
525    /// Returns the [`InlayHintKind`] from the given name.
526    ///
527    /// Returns `None` if `name` does not match any of the expected
528    /// string representations.
529    pub fn from_name(name: &str) -> Option<Self> {
530        match name {
531            "type" => Some(InlayHintKind::Type),
532            "parameter" => Some(InlayHintKind::Parameter),
533            _ => None,
534        }
535    }
536
537    /// Returns the name of this [`InlayHintKind`].
538    pub fn name(&self) -> &'static str {
539        match self {
540            InlayHintKind::Type => "type",
541            InlayHintKind::Parameter => "parameter",
542        }
543    }
544}
545
546impl settings::Settings for AllLanguageSettings {
547    const KEY: Option<&'static str> = None;
548
549    type FileContent = AllLanguageSettingsContent;
550
551    fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
552        let default_value = sources.default;
553
554        // A default is provided for all settings.
555        let mut defaults: LanguageSettings =
556            serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
557
558        let mut languages = HashMap::default();
559        for (language_name, settings) in &default_value.languages {
560            let mut language_settings = defaults.clone();
561            merge_settings(&mut language_settings, settings);
562            languages.insert(language_name.clone(), language_settings);
563        }
564
565        let mut copilot_enabled = default_value.features.as_ref().and_then(|f| f.copilot);
566        let mut inline_completion_provider = default_value
567            .features
568            .as_ref()
569            .and_then(|f| f.inline_completion_provider);
570        let mut completion_globs = default_value
571            .inline_completions
572            .as_ref()
573            .and_then(|c| c.disabled_globs.as_ref())
574            .ok_or_else(Self::missing_default)?;
575
576        let mut file_types: HashMap<Arc<str>, GlobSet> = HashMap::default();
577        for user_settings in sources.customizations() {
578            if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
579                copilot_enabled = Some(copilot);
580            }
581            if let Some(provider) = user_settings
582                .features
583                .as_ref()
584                .and_then(|f| f.inline_completion_provider)
585            {
586                inline_completion_provider = Some(provider);
587            }
588            if let Some(globs) = user_settings
589                .inline_completions
590                .as_ref()
591                .and_then(|f| f.disabled_globs.as_ref())
592            {
593                completion_globs = globs;
594            }
595
596            // A user's global settings override the default global settings and
597            // all default language-specific settings.
598            merge_settings(&mut defaults, &user_settings.defaults);
599            for language_settings in languages.values_mut() {
600                merge_settings(language_settings, &user_settings.defaults);
601            }
602
603            // A user's language-specific settings override default language-specific settings.
604            for (language_name, user_language_settings) in &user_settings.languages {
605                merge_settings(
606                    languages
607                        .entry(language_name.clone())
608                        .or_insert_with(|| defaults.clone()),
609                    user_language_settings,
610                );
611            }
612
613            for (language, suffixes) in &user_settings.file_types {
614                let mut builder = GlobSetBuilder::new();
615
616                for suffix in suffixes {
617                    builder.add(Glob::new(suffix)?);
618                }
619
620                file_types.insert(language.clone(), builder.build()?);
621            }
622        }
623
624        Ok(Self {
625            inline_completions: InlineCompletionSettings {
626                provider: if let Some(provider) = inline_completion_provider {
627                    provider
628                } else if copilot_enabled.unwrap_or(true) {
629                    InlineCompletionProvider::Copilot
630                } else {
631                    InlineCompletionProvider::None
632                },
633                disabled_globs: completion_globs
634                    .iter()
635                    .filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher()))
636                    .collect(),
637            },
638            defaults,
639            languages,
640            file_types,
641        })
642    }
643
644    fn json_schema(
645        generator: &mut schemars::gen::SchemaGenerator,
646        params: &settings::SettingsJsonSchemaParams,
647        _: &AppContext,
648    ) -> schemars::schema::RootSchema {
649        let mut root_schema = generator.root_schema_for::<Self::FileContent>();
650
651        // Create a schema for a 'languages overrides' object, associating editor
652        // settings with specific languages.
653        assert!(root_schema
654            .definitions
655            .contains_key("LanguageSettingsContent"));
656
657        let languages_object_schema = SchemaObject {
658            instance_type: Some(InstanceType::Object.into()),
659            object: Some(Box::new(ObjectValidation {
660                properties: params
661                    .language_names
662                    .iter()
663                    .map(|name| {
664                        (
665                            name.clone(),
666                            Schema::new_ref("#/definitions/LanguageSettingsContent".into()),
667                        )
668                    })
669                    .collect(),
670                ..Default::default()
671            })),
672            ..Default::default()
673        };
674
675        root_schema
676            .definitions
677            .extend([("Languages".into(), languages_object_schema.into())]);
678
679        root_schema
680            .schema
681            .object
682            .as_mut()
683            .unwrap()
684            .properties
685            .extend([
686                (
687                    "languages".to_owned(),
688                    Schema::new_ref("#/definitions/Languages".into()),
689                ),
690                // For backward compatibility
691                (
692                    "language_overrides".to_owned(),
693                    Schema::new_ref("#/definitions/Languages".into()),
694                ),
695            ]);
696
697        root_schema
698    }
699}
700
701fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
702    fn merge<T>(target: &mut T, value: Option<T>) {
703        if let Some(value) = value {
704            *target = value;
705        }
706    }
707
708    merge(&mut settings.tab_size, src.tab_size);
709    merge(&mut settings.hard_tabs, src.hard_tabs);
710    merge(&mut settings.soft_wrap, src.soft_wrap);
711    merge(&mut settings.use_autoclose, src.use_autoclose);
712    merge(
713        &mut settings.always_treat_brackets_as_autoclosed,
714        src.always_treat_brackets_as_autoclosed,
715    );
716    merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
717    merge(&mut settings.wrap_guides, src.wrap_guides.clone());
718    merge(
719        &mut settings.code_actions_on_format,
720        src.code_actions_on_format.clone(),
721    );
722
723    merge(
724        &mut settings.preferred_line_length,
725        src.preferred_line_length,
726    );
727    merge(&mut settings.formatter, src.formatter.clone());
728    merge(&mut settings.prettier, src.prettier.clone());
729    merge(&mut settings.format_on_save, src.format_on_save.clone());
730    merge(
731        &mut settings.remove_trailing_whitespace_on_save,
732        src.remove_trailing_whitespace_on_save,
733    );
734    merge(
735        &mut settings.ensure_final_newline_on_save,
736        src.ensure_final_newline_on_save,
737    );
738    merge(
739        &mut settings.enable_language_server,
740        src.enable_language_server,
741    );
742    merge(&mut settings.language_servers, src.language_servers.clone());
743    merge(
744        &mut settings.show_inline_completions,
745        src.show_inline_completions,
746    );
747    merge(&mut settings.show_whitespaces, src.show_whitespaces);
748    merge(
749        &mut settings.extend_comment_on_newline,
750        src.extend_comment_on_newline,
751    );
752    merge(&mut settings.inlay_hints, src.inlay_hints);
753}
754
755/// Allows to enable/disable formatting with Prettier
756/// and configure default Prettier, used when no project-level Prettier installation is found.
757/// Prettier formatting is disabled by default.
758#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
759pub struct PrettierSettings {
760    /// Enables or disables formatting with Prettier for a given language.
761    #[serde(default)]
762    pub allowed: bool,
763
764    /// Forces Prettier integration to use a specific parser name when formatting files with the language.
765    #[serde(default)]
766    pub parser: Option<String>,
767
768    /// Forces Prettier integration to use specific plugins when formatting files with the language.
769    /// The default Prettier will be installed with these plugins.
770    #[serde(default)]
771    pub plugins: HashSet<String>,
772
773    /// Default Prettier options, in the format as in package.json section for Prettier.
774    /// If project installs Prettier via its package.json, these options will be ignored.
775    #[serde(flatten)]
776    pub options: HashMap<String, serde_json::Value>,
777}
778
779#[cfg(test)]
780mod tests {
781    use super::*;
782
783    #[test]
784    pub fn test_resolve_language_servers() {
785        fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {
786            names
787                .into_iter()
788                .copied()
789                .map(|name| LanguageServerName(name.into()))
790                .collect::<Vec<_>>()
791        }
792
793        let available_language_servers = language_server_names(&[
794            "typescript-language-server",
795            "biome",
796            "deno",
797            "eslint",
798            "tailwind",
799        ]);
800
801        // A value of just `["..."]` is the same as taking all of the available language servers.
802        assert_eq!(
803            LanguageSettings::resolve_language_servers(
804                &[LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()],
805                &available_language_servers,
806            ),
807            available_language_servers
808        );
809
810        // Referencing one of the available language servers will change its order.
811        assert_eq!(
812            LanguageSettings::resolve_language_servers(
813                &[
814                    "biome".into(),
815                    LanguageSettings::REST_OF_LANGUAGE_SERVERS.into(),
816                    "deno".into()
817                ],
818                &available_language_servers
819            ),
820            language_server_names(&[
821                "biome",
822                "typescript-language-server",
823                "eslint",
824                "tailwind",
825                "deno",
826            ])
827        );
828
829        // Negating an available language server removes it from the list.
830        assert_eq!(
831            LanguageSettings::resolve_language_servers(
832                &[
833                    "deno".into(),
834                    "!typescript-language-server".into(),
835                    "!biome".into(),
836                    LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
837                ],
838                &available_language_servers
839            ),
840            language_server_names(&["deno", "eslint", "tailwind"])
841        );
842
843        // Adding a language server not in the list of available language servers adds it to the list.
844        assert_eq!(
845            LanguageSettings::resolve_language_servers(
846                &[
847                    "my-cool-language-server".into(),
848                    LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
849                ],
850                &available_language_servers
851            ),
852            language_server_names(&[
853                "my-cool-language-server",
854                "typescript-language-server",
855                "biome",
856                "deno",
857                "eslint",
858                "tailwind",
859            ])
860        );
861    }
862}