language_settings.rs

   1//! Provides `language`-related settings.
   2
   3use crate::{File, Language, LanguageName, LanguageServerName};
   4use anyhow::Result;
   5use collections::{FxHashMap, HashMap, HashSet};
   6use ec4rs::{
   7    Properties as EditorconfigProperties,
   8    property::{FinalNewline, IndentSize, IndentStyle, TabWidth, TrimTrailingWs},
   9};
  10use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
  11use gpui::{App, Modifiers};
  12use itertools::{Either, Itertools};
  13use schemars::{JsonSchema, json_schema};
  14use serde::{
  15    Deserialize, Deserializer, Serialize,
  16    de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor},
  17};
  18
  19use settings::{
  20    ParameterizedJsonSchema, Settings, SettingsLocation, SettingsSources, SettingsStore,
  21};
  22use shellexpand;
  23use std::{borrow::Cow, num::NonZeroU32, path::Path, slice, sync::Arc};
  24use util::schemars::replace_subschema;
  25use util::serde::default_true;
  26
  27/// Initializes the language settings.
  28pub fn init(cx: &mut App) {
  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<LanguageName>,
  35    file: Option<&'a Arc<dyn File>>,
  36    cx: &'a App,
  37) -> Cow<'a, LanguageSettings> {
  38    let location = file.map(|f| SettingsLocation {
  39        worktree_id: f.worktree_id(cx),
  40        path: f.path().as_ref(),
  41    });
  42    AllLanguageSettings::get(location, cx).language(location, language.as_ref(), cx)
  43}
  44
  45/// Returns the settings for all languages from the provided file.
  46pub fn all_language_settings<'a>(
  47    file: Option<&'a Arc<dyn File>>,
  48    cx: &'a App,
  49) -> &'a AllLanguageSettings {
  50    let location = file.map(|f| SettingsLocation {
  51        worktree_id: f.worktree_id(cx),
  52        path: f.path().as_ref(),
  53    });
  54    AllLanguageSettings::get(location, cx)
  55}
  56
  57/// The settings for all languages.
  58#[derive(Debug, Clone)]
  59pub struct AllLanguageSettings {
  60    /// The edit prediction settings.
  61    pub edit_predictions: EditPredictionSettings,
  62    pub defaults: LanguageSettings,
  63    languages: HashMap<LanguageName, LanguageSettings>,
  64    pub(crate) file_types: FxHashMap<Arc<str>, GlobSet>,
  65}
  66
  67/// The settings for a particular language.
  68#[derive(Debug, Clone, Deserialize)]
  69pub struct LanguageSettings {
  70    /// How many columns a tab should occupy.
  71    pub tab_size: NonZeroU32,
  72    /// Whether to indent lines using tab characters, as opposed to multiple
  73    /// spaces.
  74    pub hard_tabs: bool,
  75    /// How to soft-wrap long lines of text.
  76    pub soft_wrap: SoftWrap,
  77    /// The column at which to soft-wrap lines, for buffers where soft-wrap
  78    /// is enabled.
  79    pub preferred_line_length: u32,
  80    /// Whether to show wrap guides (vertical rulers) in the editor.
  81    /// Setting this to true will show a guide at the 'preferred_line_length' value
  82    /// if softwrap is set to 'preferred_line_length', and will show any
  83    /// additional guides as specified by the 'wrap_guides' setting.
  84    pub show_wrap_guides: bool,
  85    /// Character counts at which to show wrap guides (vertical rulers) in the editor.
  86    pub wrap_guides: Vec<usize>,
  87    /// Indent guide related settings.
  88    pub indent_guides: IndentGuideSettings,
  89    /// Whether or not to perform a buffer format before saving.
  90    pub format_on_save: FormatOnSave,
  91    /// Whether or not to remove any trailing whitespace from lines of a buffer
  92    /// before saving it.
  93    pub remove_trailing_whitespace_on_save: bool,
  94    /// Whether or not to ensure there's a single newline at the end of a buffer
  95    /// when saving it.
  96    pub ensure_final_newline_on_save: bool,
  97    /// How to perform a buffer format.
  98    pub formatter: SelectedFormatter,
  99    /// Zed's Prettier integration settings.
 100    pub prettier: PrettierSettings,
 101    /// Whether to automatically close JSX tags.
 102    pub jsx_tag_auto_close: JsxTagAutoCloseSettings,
 103    /// Whether to use language servers to provide code intelligence.
 104    pub enable_language_server: bool,
 105    /// The list of language servers to use (or disable) for this language.
 106    ///
 107    /// This array should consist of language server IDs, as well as the following
 108    /// special tokens:
 109    /// - `"!<language_server_id>"` - A language server ID prefixed with a `!` will be disabled.
 110    /// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
 111    pub language_servers: Vec<String>,
 112    /// Controls where the `editor::Rewrap` action is allowed for this language.
 113    ///
 114    /// Note: This setting has no effect in Vim mode, as rewrap is already
 115    /// allowed everywhere.
 116    pub allow_rewrap: RewrapBehavior,
 117    /// Controls whether edit predictions are shown immediately (true)
 118    /// or manually by triggering `editor::ShowEditPrediction` (false).
 119    pub show_edit_predictions: bool,
 120    /// Controls whether edit predictions are shown in the given language
 121    /// scopes.
 122    pub edit_predictions_disabled_in: Vec<String>,
 123    /// Whether to show tabs and spaces in the editor.
 124    pub show_whitespaces: ShowWhitespaceSetting,
 125    /// Whether to start a new line with a comment when a previous line is a comment as well.
 126    pub extend_comment_on_newline: bool,
 127    /// Inlay hint related settings.
 128    pub inlay_hints: InlayHintSettings,
 129    /// Whether to automatically close brackets.
 130    pub use_autoclose: bool,
 131    /// Whether to automatically surround text with brackets.
 132    pub use_auto_surround: bool,
 133    /// Whether to use additional LSP queries to format (and amend) the code after
 134    /// every "trigger" symbol input, defined by LSP server capabilities.
 135    pub use_on_type_format: bool,
 136    /// Whether indentation of pasted content should be adjusted based on the context.
 137    pub auto_indent_on_paste: bool,
 138    /// Controls how the editor handles the autoclosed characters.
 139    pub always_treat_brackets_as_autoclosed: bool,
 140    /// Which code actions to run on save
 141    pub code_actions_on_format: HashMap<String, bool>,
 142    /// Whether to perform linked edits
 143    pub linked_edits: bool,
 144    /// Task configuration for this language.
 145    pub tasks: LanguageTaskConfig,
 146    /// Whether to pop the completions menu while typing in an editor without
 147    /// explicitly requesting it.
 148    pub show_completions_on_input: bool,
 149    /// Whether to display inline and alongside documentation for items in the
 150    /// completions menu.
 151    pub show_completion_documentation: bool,
 152    /// Completion settings for this language.
 153    pub completions: CompletionSettings,
 154    /// Preferred debuggers for this language.
 155    pub debuggers: Vec<String>,
 156}
 157
 158impl LanguageSettings {
 159    /// A token representing the rest of the available language servers.
 160    const REST_OF_LANGUAGE_SERVERS: &'static str = "...";
 161
 162    /// Returns the customized list of language servers from the list of
 163    /// available language servers.
 164    pub fn customized_language_servers(
 165        &self,
 166        available_language_servers: &[LanguageServerName],
 167    ) -> Vec<LanguageServerName> {
 168        Self::resolve_language_servers(&self.language_servers, available_language_servers)
 169    }
 170
 171    pub(crate) fn resolve_language_servers(
 172        configured_language_servers: &[String],
 173        available_language_servers: &[LanguageServerName],
 174    ) -> Vec<LanguageServerName> {
 175        let (disabled_language_servers, enabled_language_servers): (
 176            Vec<LanguageServerName>,
 177            Vec<LanguageServerName>,
 178        ) = configured_language_servers.iter().partition_map(
 179            |language_server| match language_server.strip_prefix('!') {
 180                Some(disabled) => Either::Left(LanguageServerName(disabled.to_string().into())),
 181                None => Either::Right(LanguageServerName(language_server.clone().into())),
 182            },
 183        );
 184
 185        let rest = available_language_servers
 186            .iter()
 187            .filter(|&available_language_server| {
 188                !disabled_language_servers.contains(&available_language_server)
 189                    && !enabled_language_servers.contains(&available_language_server)
 190            })
 191            .cloned()
 192            .collect::<Vec<_>>();
 193
 194        enabled_language_servers
 195            .into_iter()
 196            .flat_map(|language_server| {
 197                if language_server.0.as_ref() == Self::REST_OF_LANGUAGE_SERVERS {
 198                    rest.clone()
 199                } else {
 200                    vec![language_server.clone()]
 201                }
 202            })
 203            .collect::<Vec<_>>()
 204    }
 205}
 206
 207/// The provider that supplies edit predictions.
 208#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
 209#[serde(rename_all = "snake_case")]
 210pub enum EditPredictionProvider {
 211    None,
 212    #[default]
 213    Copilot,
 214    Supermaven,
 215    Zed,
 216}
 217
 218impl EditPredictionProvider {
 219    pub fn is_zed(&self) -> bool {
 220        match self {
 221            EditPredictionProvider::Zed => true,
 222            EditPredictionProvider::None
 223            | EditPredictionProvider::Copilot
 224            | EditPredictionProvider::Supermaven => false,
 225        }
 226    }
 227}
 228
 229/// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot)
 230/// or [Supermaven](https://supermaven.com).
 231#[derive(Clone, Debug, Default)]
 232pub struct EditPredictionSettings {
 233    /// The provider that supplies edit predictions.
 234    pub provider: EditPredictionProvider,
 235    /// A list of globs representing files that edit predictions should be disabled for.
 236    /// This list adds to a pre-existing, sensible default set of globs.
 237    /// Any additional ones you add are combined with them.
 238    pub disabled_globs: Vec<DisabledGlob>,
 239    /// Configures how edit predictions are displayed in the buffer.
 240    pub mode: EditPredictionsMode,
 241    /// Settings specific to GitHub Copilot.
 242    pub copilot: CopilotSettings,
 243    /// Whether edit predictions are enabled in the assistant panel.
 244    /// This setting has no effect if globally disabled.
 245    pub enabled_in_text_threads: bool,
 246}
 247
 248impl EditPredictionSettings {
 249    /// Returns whether edit predictions are enabled for the given path.
 250    pub fn enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
 251        !self.disabled_globs.iter().any(|glob| {
 252            if glob.is_absolute {
 253                file.as_local()
 254                    .map_or(false, |local| glob.matcher.is_match(local.abs_path(cx)))
 255            } else {
 256                glob.matcher.is_match(file.path())
 257            }
 258        })
 259    }
 260}
 261
 262#[derive(Clone, Debug)]
 263pub struct DisabledGlob {
 264    matcher: GlobMatcher,
 265    is_absolute: bool,
 266}
 267
 268/// The mode in which edit predictions should be displayed.
 269#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
 270#[serde(rename_all = "snake_case")]
 271pub enum EditPredictionsMode {
 272    /// If provider supports it, display inline when holding modifier key (e.g., alt).
 273    /// Otherwise, eager preview is used.
 274    #[serde(alias = "auto")]
 275    Subtle,
 276    /// Display inline when there are no language server completions available.
 277    #[default]
 278    #[serde(alias = "eager_preview")]
 279    Eager,
 280}
 281
 282#[derive(Clone, Debug, Default)]
 283pub struct CopilotSettings {
 284    /// HTTP/HTTPS proxy to use for Copilot.
 285    pub proxy: Option<String>,
 286    /// Disable certificate verification for proxy (not recommended).
 287    pub proxy_no_verify: Option<bool>,
 288    /// Enterprise URI for Copilot.
 289    pub enterprise_uri: Option<String>,
 290}
 291
 292/// The settings for all languages.
 293#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
 294pub struct AllLanguageSettingsContent {
 295    /// The settings for enabling/disabling features.
 296    #[serde(default)]
 297    pub features: Option<FeaturesContent>,
 298    /// The edit prediction settings.
 299    #[serde(default)]
 300    pub edit_predictions: Option<EditPredictionSettingsContent>,
 301    /// The default language settings.
 302    #[serde(flatten)]
 303    pub defaults: LanguageSettingsContent,
 304    /// The settings for individual languages.
 305    #[serde(default)]
 306    pub languages: LanguageToSettingsMap,
 307    /// Settings for associating file extensions and filenames
 308    /// with languages.
 309    #[serde(default)]
 310    pub file_types: HashMap<Arc<str>, Vec<String>>,
 311}
 312
 313/// Map from language name to settings. Its `ParameterizedJsonSchema` allows only known language
 314/// names in the keys.
 315#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
 316pub struct LanguageToSettingsMap(pub HashMap<LanguageName, LanguageSettingsContent>);
 317
 318inventory::submit! {
 319    ParameterizedJsonSchema {
 320        add_and_get_ref: |generator, params, _cx| {
 321            let language_settings_content_ref = generator
 322                .subschema_for::<LanguageSettingsContent>()
 323                .to_value();
 324            replace_subschema::<LanguageToSettingsMap>(generator, || json_schema!({
 325                "type": "object",
 326                "properties": params
 327                    .language_names
 328                    .iter()
 329                    .map(|name| {
 330                        (
 331                            name.clone(),
 332                            language_settings_content_ref.clone(),
 333                        )
 334                    })
 335                    .collect::<serde_json::Map<_, _>>()
 336            }))
 337        }
 338    }
 339}
 340
 341/// Controls how completions are processed for this language.
 342#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 343#[serde(rename_all = "snake_case")]
 344pub struct CompletionSettings {
 345    /// Controls how words are completed.
 346    /// For large documents, not all words may be fetched for completion.
 347    ///
 348    /// Default: `fallback`
 349    #[serde(default = "default_words_completion_mode")]
 350    pub words: WordsCompletionMode,
 351    /// Whether to fetch LSP completions or not.
 352    ///
 353    /// Default: true
 354    #[serde(default = "default_true")]
 355    pub lsp: bool,
 356    /// When fetching LSP completions, determines how long to wait for a response of a particular server.
 357    /// When set to 0, waits indefinitely.
 358    ///
 359    /// Default: 0
 360    #[serde(default = "default_lsp_fetch_timeout_ms")]
 361    pub lsp_fetch_timeout_ms: u64,
 362    /// Controls how LSP completions are inserted.
 363    ///
 364    /// Default: "replace_suffix"
 365    #[serde(default = "default_lsp_insert_mode")]
 366    pub lsp_insert_mode: LspInsertMode,
 367}
 368
 369/// Controls how document's words are completed.
 370#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 371#[serde(rename_all = "snake_case")]
 372pub enum WordsCompletionMode {
 373    /// Always fetch document's words for completions along with LSP completions.
 374    Enabled,
 375    /// Only if LSP response errors or times out,
 376    /// use document's words to show completions.
 377    Fallback,
 378    /// Never fetch or complete document's words for completions.
 379    /// (Word-based completions can still be queried via a separate action)
 380    Disabled,
 381}
 382
 383#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 384#[serde(rename_all = "snake_case")]
 385pub enum LspInsertMode {
 386    /// Replaces text before the cursor, using the `insert` range described in the LSP specification.
 387    Insert,
 388    /// Replaces text before and after the cursor, using the `replace` range described in the LSP specification.
 389    Replace,
 390    /// Behaves like `"replace"` if the text that would be replaced is a subsequence of the completion text,
 391    /// and like `"insert"` otherwise.
 392    ReplaceSubsequence,
 393    /// Behaves like `"replace"` if the text after the cursor is a suffix of the completion, and like
 394    /// `"insert"` otherwise.
 395    ReplaceSuffix,
 396}
 397
 398fn default_words_completion_mode() -> WordsCompletionMode {
 399    WordsCompletionMode::Fallback
 400}
 401
 402fn default_lsp_insert_mode() -> LspInsertMode {
 403    LspInsertMode::ReplaceSuffix
 404}
 405
 406fn default_lsp_fetch_timeout_ms() -> u64 {
 407    0
 408}
 409
 410/// The settings for a particular language.
 411#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
 412pub struct LanguageSettingsContent {
 413    /// How many columns a tab should occupy.
 414    ///
 415    /// Default: 4
 416    #[serde(default)]
 417    pub tab_size: Option<NonZeroU32>,
 418    /// Whether to indent lines using tab characters, as opposed to multiple
 419    /// spaces.
 420    ///
 421    /// Default: false
 422    #[serde(default)]
 423    pub hard_tabs: Option<bool>,
 424    /// How to soft-wrap long lines of text.
 425    ///
 426    /// Default: none
 427    #[serde(default)]
 428    pub soft_wrap: Option<SoftWrap>,
 429    /// The column at which to soft-wrap lines, for buffers where soft-wrap
 430    /// is enabled.
 431    ///
 432    /// Default: 80
 433    #[serde(default)]
 434    pub preferred_line_length: Option<u32>,
 435    /// Whether to show wrap guides in the editor. Setting this to true will
 436    /// show a guide at the 'preferred_line_length' value if softwrap is set to
 437    /// 'preferred_line_length', and will show any additional guides as specified
 438    /// by the 'wrap_guides' setting.
 439    ///
 440    /// Default: true
 441    #[serde(default)]
 442    pub show_wrap_guides: Option<bool>,
 443    /// Character counts at which to show wrap guides in the editor.
 444    ///
 445    /// Default: []
 446    #[serde(default)]
 447    pub wrap_guides: Option<Vec<usize>>,
 448    /// Indent guide related settings.
 449    #[serde(default)]
 450    pub indent_guides: Option<IndentGuideSettings>,
 451    /// Whether or not to perform a buffer format before saving.
 452    ///
 453    /// Default: on
 454    #[serde(default)]
 455    pub format_on_save: Option<FormatOnSave>,
 456    /// Whether or not to remove any trailing whitespace from lines of a buffer
 457    /// before saving it.
 458    ///
 459    /// Default: true
 460    #[serde(default)]
 461    pub remove_trailing_whitespace_on_save: Option<bool>,
 462    /// Whether or not to ensure there's a single newline at the end of a buffer
 463    /// when saving it.
 464    ///
 465    /// Default: true
 466    #[serde(default)]
 467    pub ensure_final_newline_on_save: Option<bool>,
 468    /// How to perform a buffer format.
 469    ///
 470    /// Default: auto
 471    #[serde(default)]
 472    pub formatter: Option<SelectedFormatter>,
 473    /// Zed's Prettier integration settings.
 474    /// Allows to enable/disable formatting with Prettier
 475    /// and configure default Prettier, used when no project-level Prettier installation is found.
 476    ///
 477    /// Default: off
 478    #[serde(default)]
 479    pub prettier: Option<PrettierSettings>,
 480    /// Whether to automatically close JSX tags.
 481    #[serde(default)]
 482    pub jsx_tag_auto_close: Option<JsxTagAutoCloseSettings>,
 483    /// Whether to use language servers to provide code intelligence.
 484    ///
 485    /// Default: true
 486    #[serde(default)]
 487    pub enable_language_server: Option<bool>,
 488    /// The list of language servers to use (or disable) for this language.
 489    ///
 490    /// This array should consist of language server IDs, as well as the following
 491    /// special tokens:
 492    /// - `"!<language_server_id>"` - A language server ID prefixed with a `!` will be disabled.
 493    /// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
 494    ///
 495    /// Default: ["..."]
 496    #[serde(default)]
 497    pub language_servers: Option<Vec<String>>,
 498    /// Controls where the `editor::Rewrap` action is allowed for this language.
 499    ///
 500    /// Note: This setting has no effect in Vim mode, as rewrap is already
 501    /// allowed everywhere.
 502    ///
 503    /// Default: "in_comments"
 504    #[serde(default)]
 505    pub allow_rewrap: Option<RewrapBehavior>,
 506    /// Controls whether edit predictions are shown immediately (true)
 507    /// or manually by triggering `editor::ShowEditPrediction` (false).
 508    ///
 509    /// Default: true
 510    #[serde(default)]
 511    pub show_edit_predictions: Option<bool>,
 512    /// Controls whether edit predictions are shown in the given language
 513    /// scopes.
 514    ///
 515    /// Example: ["string", "comment"]
 516    ///
 517    /// Default: []
 518    #[serde(default)]
 519    pub edit_predictions_disabled_in: Option<Vec<String>>,
 520    /// Whether to show tabs and spaces in the editor.
 521    #[serde(default)]
 522    pub show_whitespaces: Option<ShowWhitespaceSetting>,
 523    /// Whether to start a new line with a comment when a previous line is a comment as well.
 524    ///
 525    /// Default: true
 526    #[serde(default)]
 527    pub extend_comment_on_newline: Option<bool>,
 528    /// Inlay hint related settings.
 529    #[serde(default)]
 530    pub inlay_hints: Option<InlayHintSettings>,
 531    /// Whether to automatically type closing characters for you. For example,
 532    /// when you type (, Zed will automatically add a closing ) at the correct position.
 533    ///
 534    /// Default: true
 535    pub use_autoclose: Option<bool>,
 536    /// Whether to automatically surround text with characters for you. For example,
 537    /// when you select text and type (, Zed will automatically surround text with ().
 538    ///
 539    /// Default: true
 540    pub use_auto_surround: Option<bool>,
 541    /// Controls how the editor handles the autoclosed characters.
 542    /// When set to `false`(default), skipping over and auto-removing of the closing characters
 543    /// happen only for auto-inserted characters.
 544    /// Otherwise(when `true`), the closing characters are always skipped over and auto-removed
 545    /// no matter how they were inserted.
 546    ///
 547    /// Default: false
 548    pub always_treat_brackets_as_autoclosed: Option<bool>,
 549    /// Whether to use additional LSP queries to format (and amend) the code after
 550    /// every "trigger" symbol input, defined by LSP server capabilities.
 551    ///
 552    /// Default: true
 553    pub use_on_type_format: Option<bool>,
 554    /// Which code actions to run on save after the formatter.
 555    /// These are not run if formatting is off.
 556    ///
 557    /// Default: {} (or {"source.organizeImports": true} for Go).
 558    pub code_actions_on_format: Option<HashMap<String, bool>>,
 559    /// Whether to perform linked edits of associated ranges, if the language server supports it.
 560    /// For example, when editing opening <html> tag, the contents of the closing </html> tag will be edited as well.
 561    ///
 562    /// Default: true
 563    pub linked_edits: Option<bool>,
 564    /// Whether indentation of pasted content should be adjusted based on the context.
 565    ///
 566    /// Default: true
 567    pub auto_indent_on_paste: Option<bool>,
 568    /// Task configuration for this language.
 569    ///
 570    /// Default: {}
 571    pub tasks: Option<LanguageTaskConfig>,
 572    /// Whether to pop the completions menu while typing in an editor without
 573    /// explicitly requesting it.
 574    ///
 575    /// Default: true
 576    pub show_completions_on_input: Option<bool>,
 577    /// Whether to display inline and alongside documentation for items in the
 578    /// completions menu.
 579    ///
 580    /// Default: true
 581    pub show_completion_documentation: Option<bool>,
 582    /// Controls how completions are processed for this language.
 583    pub completions: Option<CompletionSettings>,
 584    /// Preferred debuggers for this language.
 585    ///
 586    /// Default: []
 587    pub debuggers: Option<Vec<String>>,
 588}
 589
 590/// The behavior of `editor::Rewrap`.
 591#[derive(Debug, PartialEq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
 592#[serde(rename_all = "snake_case")]
 593pub enum RewrapBehavior {
 594    /// Only rewrap within comments.
 595    #[default]
 596    InComments,
 597    /// Only rewrap within the current selection(s).
 598    InSelections,
 599    /// Allow rewrapping anywhere.
 600    Anywhere,
 601}
 602
 603/// The contents of the edit prediction settings.
 604#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
 605pub struct EditPredictionSettingsContent {
 606    /// A list of globs representing files that edit predictions should be disabled for.
 607    /// This list adds to a pre-existing, sensible default set of globs.
 608    /// Any additional ones you add are combined with them.
 609    #[serde(default)]
 610    pub disabled_globs: Option<Vec<String>>,
 611    /// The mode used to display edit predictions in the buffer.
 612    /// Provider support required.
 613    #[serde(default)]
 614    pub mode: EditPredictionsMode,
 615    /// Settings specific to GitHub Copilot.
 616    #[serde(default)]
 617    pub copilot: CopilotSettingsContent,
 618    /// Whether edit predictions are enabled in the assistant prompt editor.
 619    /// This has no effect if globally disabled.
 620    #[serde(default = "default_true")]
 621    pub enabled_in_text_threads: bool,
 622}
 623
 624#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
 625pub struct CopilotSettingsContent {
 626    /// HTTP/HTTPS proxy to use for Copilot.
 627    ///
 628    /// Default: none
 629    #[serde(default)]
 630    pub proxy: Option<String>,
 631    /// Disable certificate verification for the proxy (not recommended).
 632    ///
 633    /// Default: false
 634    #[serde(default)]
 635    pub proxy_no_verify: Option<bool>,
 636    /// Enterprise URI for Copilot.
 637    ///
 638    /// Default: none
 639    #[serde(default)]
 640    pub enterprise_uri: Option<String>,
 641}
 642
 643/// The settings for enabling/disabling features.
 644#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
 645#[serde(rename_all = "snake_case")]
 646pub struct FeaturesContent {
 647    /// Determines which edit prediction provider to use.
 648    pub edit_prediction_provider: Option<EditPredictionProvider>,
 649}
 650
 651/// Controls the soft-wrapping behavior in the editor.
 652#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 653#[serde(rename_all = "snake_case")]
 654pub enum SoftWrap {
 655    /// Prefer a single line generally, unless an overly long line is encountered.
 656    None,
 657    /// Deprecated: use None instead. Left to avoid breaking existing users' configs.
 658    /// Prefer a single line generally, unless an overly long line is encountered.
 659    PreferLine,
 660    /// Soft wrap lines that exceed the editor width.
 661    EditorWidth,
 662    /// Soft wrap lines at the preferred line length.
 663    PreferredLineLength,
 664    /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
 665    Bounded,
 666}
 667
 668/// Controls the behavior of formatting files when they are saved.
 669#[derive(Debug, Clone, PartialEq, Eq)]
 670pub enum FormatOnSave {
 671    /// Files should be formatted on save.
 672    On,
 673    /// Files should not be formatted on save.
 674    Off,
 675    List(FormatterList),
 676}
 677
 678impl JsonSchema for FormatOnSave {
 679    fn schema_name() -> Cow<'static, str> {
 680        "OnSaveFormatter".into()
 681    }
 682
 683    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
 684        let formatter_schema = Formatter::json_schema(generator);
 685
 686        json_schema!({
 687            "oneOf": [
 688                {
 689                    "type": "array",
 690                    "items": formatter_schema
 691                },
 692                {
 693                    "type": "string",
 694                    "enum": ["on", "off", "language_server"]
 695                },
 696                formatter_schema
 697            ]
 698        })
 699    }
 700}
 701
 702impl Serialize for FormatOnSave {
 703    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
 704    where
 705        S: serde::Serializer,
 706    {
 707        match self {
 708            Self::On => serializer.serialize_str("on"),
 709            Self::Off => serializer.serialize_str("off"),
 710            Self::List(list) => list.serialize(serializer),
 711        }
 712    }
 713}
 714
 715impl<'de> Deserialize<'de> for FormatOnSave {
 716    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
 717    where
 718        D: Deserializer<'de>,
 719    {
 720        struct FormatDeserializer;
 721
 722        impl<'d> Visitor<'d> for FormatDeserializer {
 723            type Value = FormatOnSave;
 724
 725            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
 726                formatter.write_str("a valid on-save formatter kind")
 727            }
 728            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
 729            where
 730                E: serde::de::Error,
 731            {
 732                if v == "on" {
 733                    Ok(Self::Value::On)
 734                } else if v == "off" {
 735                    Ok(Self::Value::Off)
 736                } else if v == "language_server" {
 737                    Ok(Self::Value::List(FormatterList::Single(
 738                        Formatter::LanguageServer { name: None },
 739                    )))
 740                } else {
 741                    let ret: Result<FormatterList, _> =
 742                        Deserialize::deserialize(v.into_deserializer());
 743                    ret.map(Self::Value::List)
 744                }
 745            }
 746            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
 747            where
 748                A: MapAccess<'d>,
 749            {
 750                let ret: Result<FormatterList, _> =
 751                    Deserialize::deserialize(de::value::MapAccessDeserializer::new(map));
 752                ret.map(Self::Value::List)
 753            }
 754            fn visit_seq<A>(self, map: A) -> Result<Self::Value, A::Error>
 755            where
 756                A: SeqAccess<'d>,
 757            {
 758                let ret: Result<FormatterList, _> =
 759                    Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map));
 760                ret.map(Self::Value::List)
 761            }
 762        }
 763        deserializer.deserialize_any(FormatDeserializer)
 764    }
 765}
 766
 767/// Controls how whitespace should be displayedin the editor.
 768#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 769#[serde(rename_all = "snake_case")]
 770pub enum ShowWhitespaceSetting {
 771    /// Draw whitespace only for the selected text.
 772    Selection,
 773    /// Do not draw any tabs or spaces.
 774    None,
 775    /// Draw all invisible symbols.
 776    All,
 777    /// Draw whitespaces at boundaries only.
 778    ///
 779    /// For a whitespace to be on a boundary, any of the following conditions need to be met:
 780    /// - It is a tab
 781    /// - It is adjacent to an edge (start or end)
 782    /// - It is adjacent to a whitespace (left or right)
 783    Boundary,
 784    /// Draw whitespaces only after non-whitespace characters.
 785    Trailing,
 786}
 787
 788/// Controls which formatter should be used when formatting code.
 789#[derive(Clone, Debug, Default, PartialEq, Eq)]
 790pub enum SelectedFormatter {
 791    /// Format files using Zed's Prettier integration (if applicable),
 792    /// or falling back to formatting via language server.
 793    #[default]
 794    Auto,
 795    List(FormatterList),
 796}
 797
 798impl JsonSchema for SelectedFormatter {
 799    fn schema_name() -> Cow<'static, str> {
 800        "Formatter".into()
 801    }
 802
 803    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
 804        let formatter_schema = Formatter::json_schema(generator);
 805
 806        json_schema!({
 807            "oneOf": [
 808                {
 809                    "type": "array",
 810                    "items": formatter_schema
 811                },
 812                {
 813                    "type": "string",
 814                    "enum": ["auto", "language_server"]
 815                },
 816                formatter_schema
 817            ]
 818        })
 819    }
 820}
 821
 822impl Serialize for SelectedFormatter {
 823    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
 824    where
 825        S: serde::Serializer,
 826    {
 827        match self {
 828            SelectedFormatter::Auto => serializer.serialize_str("auto"),
 829            SelectedFormatter::List(list) => list.serialize(serializer),
 830        }
 831    }
 832}
 833
 834impl<'de> Deserialize<'de> for SelectedFormatter {
 835    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
 836    where
 837        D: Deserializer<'de>,
 838    {
 839        struct FormatDeserializer;
 840
 841        impl<'d> Visitor<'d> for FormatDeserializer {
 842            type Value = SelectedFormatter;
 843
 844            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
 845                formatter.write_str("a valid formatter kind")
 846            }
 847            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
 848            where
 849                E: serde::de::Error,
 850            {
 851                if v == "auto" {
 852                    Ok(Self::Value::Auto)
 853                } else if v == "language_server" {
 854                    Ok(Self::Value::List(FormatterList::Single(
 855                        Formatter::LanguageServer { name: None },
 856                    )))
 857                } else {
 858                    let ret: Result<FormatterList, _> =
 859                        Deserialize::deserialize(v.into_deserializer());
 860                    ret.map(SelectedFormatter::List)
 861                }
 862            }
 863            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
 864            where
 865                A: MapAccess<'d>,
 866            {
 867                let ret: Result<FormatterList, _> =
 868                    Deserialize::deserialize(de::value::MapAccessDeserializer::new(map));
 869                ret.map(SelectedFormatter::List)
 870            }
 871            fn visit_seq<A>(self, map: A) -> Result<Self::Value, A::Error>
 872            where
 873                A: SeqAccess<'d>,
 874            {
 875                let ret: Result<FormatterList, _> =
 876                    Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map));
 877                ret.map(SelectedFormatter::List)
 878            }
 879        }
 880        deserializer.deserialize_any(FormatDeserializer)
 881    }
 882}
 883
 884/// Controls which formatters should be used when formatting code.
 885#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 886#[serde(untagged)]
 887pub enum FormatterList {
 888    Single(Formatter),
 889    Vec(Vec<Formatter>),
 890}
 891
 892impl AsRef<[Formatter]> for FormatterList {
 893    fn as_ref(&self) -> &[Formatter] {
 894        match &self {
 895            Self::Single(single) => slice::from_ref(single),
 896            Self::Vec(v) => v,
 897        }
 898    }
 899}
 900
 901/// Controls which formatter should be used when formatting code. If there are multiple formatters, they are executed in the order of declaration.
 902#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 903#[serde(rename_all = "snake_case")]
 904pub enum Formatter {
 905    /// Format code using the current language server.
 906    LanguageServer { name: Option<String> },
 907    /// Format code using Zed's Prettier integration.
 908    Prettier,
 909    /// Format code using an external command.
 910    External {
 911        /// The external program to run.
 912        command: Arc<str>,
 913        /// The arguments to pass to the program.
 914        arguments: Option<Arc<[String]>>,
 915    },
 916    /// Files should be formatted using code actions executed by language servers.
 917    CodeActions(HashMap<String, bool>),
 918}
 919
 920/// The settings for indent guides.
 921#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
 922pub struct IndentGuideSettings {
 923    /// Whether to display indent guides in the editor.
 924    ///
 925    /// Default: true
 926    #[serde(default = "default_true")]
 927    pub enabled: bool,
 928    /// The width of the indent guides in pixels, between 1 and 10.
 929    ///
 930    /// Default: 1
 931    #[serde(default = "line_width")]
 932    pub line_width: u32,
 933    /// The width of the active indent guide in pixels, between 1 and 10.
 934    ///
 935    /// Default: 1
 936    #[serde(default = "active_line_width")]
 937    pub active_line_width: u32,
 938    /// Determines how indent guides are colored.
 939    ///
 940    /// Default: Fixed
 941    #[serde(default)]
 942    pub coloring: IndentGuideColoring,
 943    /// Determines how indent guide backgrounds are colored.
 944    ///
 945    /// Default: Disabled
 946    #[serde(default)]
 947    pub background_coloring: IndentGuideBackgroundColoring,
 948}
 949
 950fn line_width() -> u32 {
 951    1
 952}
 953
 954fn active_line_width() -> u32 {
 955    line_width()
 956}
 957
 958/// Determines how indent guides are colored.
 959#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
 960#[serde(rename_all = "snake_case")]
 961pub enum IndentGuideColoring {
 962    /// Do not render any lines for indent guides.
 963    Disabled,
 964    /// Use the same color for all indentation levels.
 965    #[default]
 966    Fixed,
 967    /// Use a different color for each indentation level.
 968    IndentAware,
 969}
 970
 971/// Determines how indent guide backgrounds are colored.
 972#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
 973#[serde(rename_all = "snake_case")]
 974pub enum IndentGuideBackgroundColoring {
 975    /// Do not render any background for indent guides.
 976    #[default]
 977    Disabled,
 978    /// Use a different color for each indentation level.
 979    IndentAware,
 980}
 981
 982/// The settings for inlay hints.
 983#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 984pub struct InlayHintSettings {
 985    /// Global switch to toggle hints on and off.
 986    ///
 987    /// Default: false
 988    #[serde(default)]
 989    pub enabled: bool,
 990    /// Global switch to toggle inline values on and off when debugging.
 991    ///
 992    /// Default: true
 993    #[serde(default = "default_true")]
 994    pub show_value_hints: bool,
 995    /// Whether type hints should be shown.
 996    ///
 997    /// Default: true
 998    #[serde(default = "default_true")]
 999    pub show_type_hints: bool,
1000    /// Whether parameter hints should be shown.
1001    ///
1002    /// Default: true
1003    #[serde(default = "default_true")]
1004    pub show_parameter_hints: bool,
1005    /// Whether other hints should be shown.
1006    ///
1007    /// Default: true
1008    #[serde(default = "default_true")]
1009    pub show_other_hints: bool,
1010    /// Whether to show a background for inlay hints.
1011    ///
1012    /// If set to `true`, the background will use the `hint.background` color
1013    /// from the current theme.
1014    ///
1015    /// Default: false
1016    #[serde(default)]
1017    pub show_background: bool,
1018    /// Whether or not to debounce inlay hints updates after buffer edits.
1019    ///
1020    /// Set to 0 to disable debouncing.
1021    ///
1022    /// Default: 700
1023    #[serde(default = "edit_debounce_ms")]
1024    pub edit_debounce_ms: u64,
1025    /// Whether or not to debounce inlay hints updates after buffer scrolls.
1026    ///
1027    /// Set to 0 to disable debouncing.
1028    ///
1029    /// Default: 50
1030    #[serde(default = "scroll_debounce_ms")]
1031    pub scroll_debounce_ms: u64,
1032    /// Toggles inlay hints (hides or shows) when the user presses the modifiers specified.
1033    /// If only a subset of the modifiers specified is pressed, hints are not toggled.
1034    /// If no modifiers are specified, this is equivalent to `None`.
1035    ///
1036    /// Default: None
1037    #[serde(default)]
1038    pub toggle_on_modifiers_press: Option<Modifiers>,
1039}
1040
1041fn edit_debounce_ms() -> u64 {
1042    700
1043}
1044
1045fn scroll_debounce_ms() -> u64 {
1046    50
1047}
1048
1049/// The task settings for a particular language.
1050#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema)]
1051pub struct LanguageTaskConfig {
1052    /// Extra task variables to set for a particular language.
1053    #[serde(default)]
1054    pub variables: HashMap<String, String>,
1055    #[serde(default = "default_true")]
1056    pub enabled: bool,
1057    /// Use LSP tasks over Zed language extension ones.
1058    /// If no LSP tasks are returned due to error/timeout or regular execution,
1059    /// Zed language extension tasks will be used instead.
1060    ///
1061    /// Other Zed tasks will still be shown:
1062    /// * Zed task from either of the task config file
1063    /// * Zed task from history (e.g. one-off task was spawned before)
1064    #[serde(default = "default_true")]
1065    pub prefer_lsp: bool,
1066}
1067
1068impl InlayHintSettings {
1069    /// Returns the kinds of inlay hints that are enabled based on the settings.
1070    pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
1071        let mut kinds = HashSet::default();
1072        if self.show_type_hints {
1073            kinds.insert(Some(InlayHintKind::Type));
1074        }
1075        if self.show_parameter_hints {
1076            kinds.insert(Some(InlayHintKind::Parameter));
1077        }
1078        if self.show_other_hints {
1079            kinds.insert(None);
1080        }
1081        kinds
1082    }
1083}
1084
1085impl AllLanguageSettings {
1086    /// Returns the [`LanguageSettings`] for the language with the specified name.
1087    pub fn language<'a>(
1088        &'a self,
1089        location: Option<SettingsLocation<'a>>,
1090        language_name: Option<&LanguageName>,
1091        cx: &'a App,
1092    ) -> Cow<'a, LanguageSettings> {
1093        let settings = language_name
1094            .and_then(|name| self.languages.get(name))
1095            .unwrap_or(&self.defaults);
1096
1097        let editorconfig_properties = location.and_then(|location| {
1098            cx.global::<SettingsStore>()
1099                .editorconfig_properties(location.worktree_id, location.path)
1100        });
1101        if let Some(editorconfig_properties) = editorconfig_properties {
1102            let mut settings = settings.clone();
1103            merge_with_editorconfig(&mut settings, &editorconfig_properties);
1104            Cow::Owned(settings)
1105        } else {
1106            Cow::Borrowed(settings)
1107        }
1108    }
1109
1110    /// Returns whether edit predictions are enabled for the given path.
1111    pub fn edit_predictions_enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
1112        self.edit_predictions.enabled_for_file(file, cx)
1113    }
1114
1115    /// Returns whether edit predictions are enabled for the given language and path.
1116    pub fn show_edit_predictions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
1117        self.language(None, language.map(|l| l.name()).as_ref(), cx)
1118            .show_edit_predictions
1119    }
1120
1121    /// Returns the edit predictions preview mode for the given language and path.
1122    pub fn edit_predictions_mode(&self) -> EditPredictionsMode {
1123        self.edit_predictions.mode
1124    }
1125}
1126
1127fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
1128    let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
1129        IndentSize::Value(u) => NonZeroU32::new(u as u32),
1130        IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
1131            TabWidth::Value(u) => NonZeroU32::new(u as u32),
1132        }),
1133    });
1134    let hard_tabs = cfg
1135        .get::<IndentStyle>()
1136        .map(|v| v.eq(&IndentStyle::Tabs))
1137        .ok();
1138    let ensure_final_newline_on_save = cfg
1139        .get::<FinalNewline>()
1140        .map(|v| match v {
1141            FinalNewline::Value(b) => b,
1142        })
1143        .ok();
1144    let remove_trailing_whitespace_on_save = cfg
1145        .get::<TrimTrailingWs>()
1146        .map(|v| match v {
1147            TrimTrailingWs::Value(b) => b,
1148        })
1149        .ok();
1150    fn merge<T>(target: &mut T, value: Option<T>) {
1151        if let Some(value) = value {
1152            *target = value;
1153        }
1154    }
1155    merge(&mut settings.tab_size, tab_size);
1156    merge(&mut settings.hard_tabs, hard_tabs);
1157    merge(
1158        &mut settings.remove_trailing_whitespace_on_save,
1159        remove_trailing_whitespace_on_save,
1160    );
1161    merge(
1162        &mut settings.ensure_final_newline_on_save,
1163        ensure_final_newline_on_save,
1164    );
1165}
1166
1167/// The kind of an inlay hint.
1168#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1169pub enum InlayHintKind {
1170    /// An inlay hint for a type.
1171    Type,
1172    /// An inlay hint for a parameter.
1173    Parameter,
1174}
1175
1176impl InlayHintKind {
1177    /// Returns the [`InlayHintKind`] from the given name.
1178    ///
1179    /// Returns `None` if `name` does not match any of the expected
1180    /// string representations.
1181    pub fn from_name(name: &str) -> Option<Self> {
1182        match name {
1183            "type" => Some(InlayHintKind::Type),
1184            "parameter" => Some(InlayHintKind::Parameter),
1185            _ => None,
1186        }
1187    }
1188
1189    /// Returns the name of this [`InlayHintKind`].
1190    pub fn name(&self) -> &'static str {
1191        match self {
1192            InlayHintKind::Type => "type",
1193            InlayHintKind::Parameter => "parameter",
1194        }
1195    }
1196}
1197
1198impl settings::Settings for AllLanguageSettings {
1199    const KEY: Option<&'static str> = None;
1200
1201    type FileContent = AllLanguageSettingsContent;
1202
1203    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1204        let default_value = sources.default;
1205
1206        // A default is provided for all settings.
1207        let mut defaults: LanguageSettings =
1208            serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
1209
1210        let mut languages = HashMap::default();
1211        for (language_name, settings) in &default_value.languages.0 {
1212            let mut language_settings = defaults.clone();
1213            merge_settings(&mut language_settings, settings);
1214            languages.insert(language_name.clone(), language_settings);
1215        }
1216
1217        let mut edit_prediction_provider = default_value
1218            .features
1219            .as_ref()
1220            .and_then(|f| f.edit_prediction_provider);
1221        let mut edit_predictions_mode = default_value
1222            .edit_predictions
1223            .as_ref()
1224            .map(|edit_predictions| edit_predictions.mode)
1225            .ok_or_else(Self::missing_default)?;
1226
1227        let mut completion_globs: HashSet<&String> = default_value
1228            .edit_predictions
1229            .as_ref()
1230            .and_then(|c| c.disabled_globs.as_ref())
1231            .map(|globs| globs.iter().collect())
1232            .ok_or_else(Self::missing_default)?;
1233
1234        let mut copilot_settings = default_value
1235            .edit_predictions
1236            .as_ref()
1237            .map(|settings| CopilotSettings {
1238                proxy: settings.copilot.proxy.clone(),
1239                proxy_no_verify: settings.copilot.proxy_no_verify,
1240                enterprise_uri: settings.copilot.enterprise_uri.clone(),
1241            })
1242            .unwrap_or_default();
1243
1244        let mut enabled_in_text_threads = default_value
1245            .edit_predictions
1246            .as_ref()
1247            .map(|settings| settings.enabled_in_text_threads)
1248            .unwrap_or(true);
1249
1250        let mut file_types: FxHashMap<Arc<str>, GlobSet> = FxHashMap::default();
1251
1252        for (language, patterns) in &default_value.file_types {
1253            let mut builder = GlobSetBuilder::new();
1254
1255            for pattern in patterns {
1256                builder.add(Glob::new(pattern)?);
1257            }
1258
1259            file_types.insert(language.clone(), builder.build()?);
1260        }
1261
1262        for user_settings in sources.customizations() {
1263            if let Some(provider) = user_settings
1264                .features
1265                .as_ref()
1266                .and_then(|f| f.edit_prediction_provider)
1267            {
1268                edit_prediction_provider = Some(provider);
1269            }
1270
1271            if let Some(edit_predictions) = user_settings.edit_predictions.as_ref() {
1272                edit_predictions_mode = edit_predictions.mode;
1273                enabled_in_text_threads = edit_predictions.enabled_in_text_threads;
1274
1275                if let Some(disabled_globs) = edit_predictions.disabled_globs.as_ref() {
1276                    completion_globs.extend(disabled_globs.iter());
1277                }
1278            }
1279
1280            if let Some(proxy) = user_settings
1281                .edit_predictions
1282                .as_ref()
1283                .and_then(|settings| settings.copilot.proxy.clone())
1284            {
1285                copilot_settings.proxy = Some(proxy);
1286            }
1287
1288            if let Some(proxy_no_verify) = user_settings
1289                .edit_predictions
1290                .as_ref()
1291                .and_then(|settings| settings.copilot.proxy_no_verify)
1292            {
1293                copilot_settings.proxy_no_verify = Some(proxy_no_verify);
1294            }
1295
1296            if let Some(enterprise_uri) = user_settings
1297                .edit_predictions
1298                .as_ref()
1299                .and_then(|settings| settings.copilot.enterprise_uri.clone())
1300            {
1301                copilot_settings.enterprise_uri = Some(enterprise_uri);
1302            }
1303
1304            // A user's global settings override the default global settings and
1305            // all default language-specific settings.
1306            merge_settings(&mut defaults, &user_settings.defaults);
1307            for language_settings in languages.values_mut() {
1308                merge_settings(language_settings, &user_settings.defaults);
1309            }
1310
1311            // A user's language-specific settings override default language-specific settings.
1312            for (language_name, user_language_settings) in &user_settings.languages.0 {
1313                merge_settings(
1314                    languages
1315                        .entry(language_name.clone())
1316                        .or_insert_with(|| defaults.clone()),
1317                    user_language_settings,
1318                );
1319            }
1320
1321            for (language, patterns) in &user_settings.file_types {
1322                let mut builder = GlobSetBuilder::new();
1323
1324                let default_value = default_value.file_types.get(&language.clone());
1325
1326                // Merge the default value with the user's value.
1327                if let Some(patterns) = default_value {
1328                    for pattern in patterns {
1329                        builder.add(Glob::new(pattern)?);
1330                    }
1331                }
1332
1333                for pattern in patterns {
1334                    builder.add(Glob::new(pattern)?);
1335                }
1336
1337                file_types.insert(language.clone(), builder.build()?);
1338            }
1339        }
1340
1341        Ok(Self {
1342            edit_predictions: EditPredictionSettings {
1343                provider: if let Some(provider) = edit_prediction_provider {
1344                    provider
1345                } else {
1346                    EditPredictionProvider::None
1347                },
1348                disabled_globs: completion_globs
1349                    .iter()
1350                    .filter_map(|g| {
1351                        let expanded_g = shellexpand::tilde(g).into_owned();
1352                        Some(DisabledGlob {
1353                            matcher: globset::Glob::new(&expanded_g).ok()?.compile_matcher(),
1354                            is_absolute: Path::new(&expanded_g).is_absolute(),
1355                        })
1356                    })
1357                    .collect(),
1358                mode: edit_predictions_mode,
1359                copilot: copilot_settings,
1360                enabled_in_text_threads,
1361            },
1362            defaults,
1363            languages,
1364            file_types,
1365        })
1366    }
1367
1368    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
1369        let d = &mut current.defaults;
1370        if let Some(size) = vscode
1371            .read_value("editor.tabSize")
1372            .and_then(|v| v.as_u64())
1373            .and_then(|n| NonZeroU32::new(n as u32))
1374        {
1375            d.tab_size = Some(size);
1376        }
1377        if let Some(v) = vscode.read_bool("editor.insertSpaces") {
1378            d.hard_tabs = Some(!v);
1379        }
1380
1381        vscode.enum_setting("editor.wordWrap", &mut d.soft_wrap, |s| match s {
1382            "on" => Some(SoftWrap::EditorWidth),
1383            "wordWrapColumn" => Some(SoftWrap::PreferLine),
1384            "bounded" => Some(SoftWrap::Bounded),
1385            "off" => Some(SoftWrap::None),
1386            _ => None,
1387        });
1388        vscode.u32_setting("editor.wordWrapColumn", &mut d.preferred_line_length);
1389
1390        if let Some(arr) = vscode
1391            .read_value("editor.rulers")
1392            .and_then(|v| v.as_array())
1393            .map(|v| v.iter().map(|n| n.as_u64().map(|n| n as usize)).collect())
1394        {
1395            d.wrap_guides = arr;
1396        }
1397        if let Some(b) = vscode.read_bool("editor.guides.indentation") {
1398            if let Some(guide_settings) = d.indent_guides.as_mut() {
1399                guide_settings.enabled = b;
1400            } else {
1401                d.indent_guides = Some(IndentGuideSettings {
1402                    enabled: b,
1403                    ..Default::default()
1404                });
1405            }
1406        }
1407
1408        if let Some(b) = vscode.read_bool("editor.guides.formatOnSave") {
1409            d.format_on_save = Some(if b {
1410                FormatOnSave::On
1411            } else {
1412                FormatOnSave::Off
1413            });
1414        }
1415        vscode.bool_setting(
1416            "editor.trimAutoWhitespace",
1417            &mut d.remove_trailing_whitespace_on_save,
1418        );
1419        vscode.bool_setting(
1420            "files.insertFinalNewline",
1421            &mut d.ensure_final_newline_on_save,
1422        );
1423        vscode.bool_setting("editor.inlineSuggest.enabled", &mut d.show_edit_predictions);
1424        vscode.enum_setting("editor.renderWhitespace", &mut d.show_whitespaces, |s| {
1425            Some(match s {
1426                "boundary" => ShowWhitespaceSetting::Boundary,
1427                "trailing" => ShowWhitespaceSetting::Trailing,
1428                "selection" => ShowWhitespaceSetting::Selection,
1429                "all" => ShowWhitespaceSetting::All,
1430                _ => ShowWhitespaceSetting::None,
1431            })
1432        });
1433        vscode.enum_setting(
1434            "editor.autoSurround",
1435            &mut d.use_auto_surround,
1436            |s| match s {
1437                "languageDefined" | "quotes" | "brackets" => Some(true),
1438                "never" => Some(false),
1439                _ => None,
1440            },
1441        );
1442        vscode.bool_setting("editor.formatOnType", &mut d.use_on_type_format);
1443        vscode.bool_setting("editor.linkedEditing", &mut d.linked_edits);
1444        vscode.bool_setting("editor.formatOnPaste", &mut d.auto_indent_on_paste);
1445        vscode.bool_setting(
1446            "editor.suggestOnTriggerCharacters",
1447            &mut d.show_completions_on_input,
1448        );
1449        if let Some(b) = vscode.read_bool("editor.suggest.showWords") {
1450            let mode = if b {
1451                WordsCompletionMode::Enabled
1452            } else {
1453                WordsCompletionMode::Disabled
1454            };
1455            if let Some(completion_settings) = d.completions.as_mut() {
1456                completion_settings.words = mode;
1457            } else {
1458                d.completions = Some(CompletionSettings {
1459                    words: mode,
1460                    lsp: true,
1461                    lsp_fetch_timeout_ms: 0,
1462                    lsp_insert_mode: LspInsertMode::ReplaceSuffix,
1463                });
1464            }
1465        }
1466        // TODO: pull ^ out into helper and reuse for per-language settings
1467
1468        // vscodes file association map is inverted from ours, so we flip the mapping before merging
1469        let mut associations: HashMap<Arc<str>, Vec<String>> = HashMap::default();
1470        if let Some(map) = vscode
1471            .read_value("files.associations")
1472            .and_then(|v| v.as_object())
1473        {
1474            for (k, v) in map {
1475                let Some(v) = v.as_str() else { continue };
1476                associations.entry(v.into()).or_default().push(k.clone());
1477            }
1478        }
1479
1480        // TODO: do we want to merge imported globs per filetype? for now we'll just replace
1481        current.file_types.extend(associations);
1482
1483        // cursor global ignore list applies to cursor-tab, so transfer it to edit_predictions.disabled_globs
1484        if let Some(disabled_globs) = vscode
1485            .read_value("cursor.general.globalCursorIgnoreList")
1486            .and_then(|v| v.as_array())
1487        {
1488            current
1489                .edit_predictions
1490                .get_or_insert_default()
1491                .disabled_globs
1492                .get_or_insert_default()
1493                .extend(
1494                    disabled_globs
1495                        .iter()
1496                        .filter_map(|glob| glob.as_str())
1497                        .map(|s| s.to_string()),
1498                );
1499        }
1500    }
1501}
1502
1503fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
1504    fn merge<T>(target: &mut T, value: Option<T>) {
1505        if let Some(value) = value {
1506            *target = value;
1507        }
1508    }
1509
1510    merge(&mut settings.tab_size, src.tab_size);
1511    settings.tab_size = settings
1512        .tab_size
1513        .clamp(NonZeroU32::new(1).unwrap(), NonZeroU32::new(16).unwrap());
1514
1515    merge(&mut settings.hard_tabs, src.hard_tabs);
1516    merge(&mut settings.soft_wrap, src.soft_wrap);
1517    merge(&mut settings.use_autoclose, src.use_autoclose);
1518    merge(&mut settings.use_auto_surround, src.use_auto_surround);
1519    merge(&mut settings.use_on_type_format, src.use_on_type_format);
1520    merge(&mut settings.auto_indent_on_paste, src.auto_indent_on_paste);
1521    merge(
1522        &mut settings.always_treat_brackets_as_autoclosed,
1523        src.always_treat_brackets_as_autoclosed,
1524    );
1525    merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
1526    merge(&mut settings.wrap_guides, src.wrap_guides.clone());
1527    merge(&mut settings.indent_guides, src.indent_guides);
1528    merge(
1529        &mut settings.code_actions_on_format,
1530        src.code_actions_on_format.clone(),
1531    );
1532    merge(&mut settings.linked_edits, src.linked_edits);
1533    merge(&mut settings.tasks, src.tasks.clone());
1534
1535    merge(
1536        &mut settings.preferred_line_length,
1537        src.preferred_line_length,
1538    );
1539    merge(&mut settings.formatter, src.formatter.clone());
1540    merge(&mut settings.prettier, src.prettier.clone());
1541    merge(
1542        &mut settings.jsx_tag_auto_close,
1543        src.jsx_tag_auto_close.clone(),
1544    );
1545    merge(&mut settings.format_on_save, src.format_on_save.clone());
1546    merge(
1547        &mut settings.remove_trailing_whitespace_on_save,
1548        src.remove_trailing_whitespace_on_save,
1549    );
1550    merge(
1551        &mut settings.ensure_final_newline_on_save,
1552        src.ensure_final_newline_on_save,
1553    );
1554    merge(
1555        &mut settings.enable_language_server,
1556        src.enable_language_server,
1557    );
1558    merge(&mut settings.language_servers, src.language_servers.clone());
1559    merge(&mut settings.allow_rewrap, src.allow_rewrap);
1560    merge(
1561        &mut settings.show_edit_predictions,
1562        src.show_edit_predictions,
1563    );
1564    merge(
1565        &mut settings.edit_predictions_disabled_in,
1566        src.edit_predictions_disabled_in.clone(),
1567    );
1568    merge(&mut settings.show_whitespaces, src.show_whitespaces);
1569    merge(
1570        &mut settings.extend_comment_on_newline,
1571        src.extend_comment_on_newline,
1572    );
1573    merge(&mut settings.inlay_hints, src.inlay_hints);
1574    merge(
1575        &mut settings.show_completions_on_input,
1576        src.show_completions_on_input,
1577    );
1578    merge(
1579        &mut settings.show_completion_documentation,
1580        src.show_completion_documentation,
1581    );
1582    merge(&mut settings.completions, src.completions);
1583}
1584
1585/// Allows to enable/disable formatting with Prettier
1586/// and configure default Prettier, used when no project-level Prettier installation is found.
1587/// Prettier formatting is disabled by default.
1588#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1589pub struct PrettierSettings {
1590    /// Enables or disables formatting with Prettier for a given language.
1591    #[serde(default)]
1592    pub allowed: bool,
1593
1594    /// Forces Prettier integration to use a specific parser name when formatting files with the language.
1595    #[serde(default)]
1596    pub parser: Option<String>,
1597
1598    /// Forces Prettier integration to use specific plugins when formatting files with the language.
1599    /// The default Prettier will be installed with these plugins.
1600    #[serde(default)]
1601    pub plugins: HashSet<String>,
1602
1603    /// Default Prettier options, in the format as in package.json section for Prettier.
1604    /// If project installs Prettier via its package.json, these options will be ignored.
1605    #[serde(flatten)]
1606    pub options: HashMap<String, serde_json::Value>,
1607}
1608
1609#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1610pub struct JsxTagAutoCloseSettings {
1611    /// Enables or disables auto-closing of JSX tags.
1612    #[serde(default)]
1613    pub enabled: bool,
1614}
1615
1616#[cfg(test)]
1617mod tests {
1618    use gpui::TestAppContext;
1619
1620    use super::*;
1621
1622    #[test]
1623    fn test_formatter_deserialization() {
1624        let raw_auto = "{\"formatter\": \"auto\"}";
1625        let settings: LanguageSettingsContent = serde_json::from_str(raw_auto).unwrap();
1626        assert_eq!(settings.formatter, Some(SelectedFormatter::Auto));
1627        let raw = "{\"formatter\": \"language_server\"}";
1628        let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1629        assert_eq!(
1630            settings.formatter,
1631            Some(SelectedFormatter::List(FormatterList::Single(
1632                Formatter::LanguageServer { name: None }
1633            )))
1634        );
1635        let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}]}";
1636        let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1637        assert_eq!(
1638            settings.formatter,
1639            Some(SelectedFormatter::List(FormatterList::Vec(vec![
1640                Formatter::LanguageServer { name: None }
1641            ])))
1642        );
1643        let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"prettier\"]}";
1644        let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1645        assert_eq!(
1646            settings.formatter,
1647            Some(SelectedFormatter::List(FormatterList::Vec(vec![
1648                Formatter::LanguageServer { name: None },
1649                Formatter::Prettier
1650            ])))
1651        );
1652    }
1653
1654    #[test]
1655    fn test_formatter_deserialization_invalid() {
1656        let raw_auto = "{\"formatter\": {}}";
1657        let result: Result<LanguageSettingsContent, _> = serde_json::from_str(raw_auto);
1658        assert!(result.is_err());
1659    }
1660
1661    #[gpui::test]
1662    fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
1663        use crate::TestFile;
1664        use std::path::PathBuf;
1665
1666        let cx = cx.app.borrow_mut();
1667
1668        let build_settings = |globs: &[&str]| -> EditPredictionSettings {
1669            EditPredictionSettings {
1670                disabled_globs: globs
1671                    .iter()
1672                    .map(|glob_str| {
1673                        #[cfg(windows)]
1674                        let glob_str = {
1675                            let mut g = String::new();
1676
1677                            if glob_str.starts_with('/') {
1678                                g.push_str("C:");
1679                            }
1680
1681                            g.push_str(&glob_str.replace('/', "\\"));
1682                            g
1683                        };
1684                        #[cfg(windows)]
1685                        let glob_str = glob_str.as_str();
1686                        let expanded_glob_str = shellexpand::tilde(glob_str).into_owned();
1687                        DisabledGlob {
1688                            matcher: globset::Glob::new(&expanded_glob_str)
1689                                .unwrap()
1690                                .compile_matcher(),
1691                            is_absolute: Path::new(&expanded_glob_str).is_absolute(),
1692                        }
1693                    })
1694                    .collect(),
1695                ..Default::default()
1696            }
1697        };
1698
1699        const WORKTREE_NAME: &str = "project";
1700        let make_test_file = |segments: &[&str]| -> Arc<dyn File> {
1701            let mut path_buf = PathBuf::new();
1702            path_buf.extend(segments);
1703
1704            Arc::new(TestFile {
1705                path: path_buf.as_path().into(),
1706                root_name: WORKTREE_NAME.to_string(),
1707                local_root: Some(PathBuf::from(if cfg!(windows) {
1708                    "C:\\absolute\\"
1709                } else {
1710                    "/absolute/"
1711                })),
1712            })
1713        };
1714
1715        let test_file = make_test_file(&["src", "test", "file.rs"]);
1716
1717        // Test relative globs
1718        let settings = build_settings(&["*.rs"]);
1719        assert!(!settings.enabled_for_file(&test_file, &cx));
1720        let settings = build_settings(&["*.txt"]);
1721        assert!(settings.enabled_for_file(&test_file, &cx));
1722
1723        // Test absolute globs
1724        let settings = build_settings(&["/absolute/**/*.rs"]);
1725        assert!(!settings.enabled_for_file(&test_file, &cx));
1726        let settings = build_settings(&["/other/**/*.rs"]);
1727        assert!(settings.enabled_for_file(&test_file, &cx));
1728
1729        // Test exact path match relative
1730        let settings = build_settings(&["src/test/file.rs"]);
1731        assert!(!settings.enabled_for_file(&test_file, &cx));
1732        let settings = build_settings(&["src/test/otherfile.rs"]);
1733        assert!(settings.enabled_for_file(&test_file, &cx));
1734
1735        // Test exact path match absolute
1736        let settings = build_settings(&[&format!("/absolute/{}/src/test/file.rs", WORKTREE_NAME)]);
1737        assert!(!settings.enabled_for_file(&test_file, &cx));
1738        let settings = build_settings(&["/other/test/otherfile.rs"]);
1739        assert!(settings.enabled_for_file(&test_file, &cx));
1740
1741        // Test * glob
1742        let settings = build_settings(&["*"]);
1743        assert!(!settings.enabled_for_file(&test_file, &cx));
1744        let settings = build_settings(&["*.txt"]);
1745        assert!(settings.enabled_for_file(&test_file, &cx));
1746
1747        // Test **/* glob
1748        let settings = build_settings(&["**/*"]);
1749        assert!(!settings.enabled_for_file(&test_file, &cx));
1750        let settings = build_settings(&["other/**/*"]);
1751        assert!(settings.enabled_for_file(&test_file, &cx));
1752
1753        // Test directory/** glob
1754        let settings = build_settings(&["src/**"]);
1755        assert!(!settings.enabled_for_file(&test_file, &cx));
1756
1757        let test_file_root: Arc<dyn File> = Arc::new(TestFile {
1758            path: PathBuf::from("file.rs").as_path().into(),
1759            root_name: WORKTREE_NAME.to_string(),
1760            local_root: Some(PathBuf::from("/absolute/")),
1761        });
1762        assert!(settings.enabled_for_file(&test_file_root, &cx));
1763
1764        let settings = build_settings(&["other/**"]);
1765        assert!(settings.enabled_for_file(&test_file, &cx));
1766
1767        // Test **/directory/* glob
1768        let settings = build_settings(&["**/test/*"]);
1769        assert!(!settings.enabled_for_file(&test_file, &cx));
1770        let settings = build_settings(&["**/other/*"]);
1771        assert!(settings.enabled_for_file(&test_file, &cx));
1772
1773        // Test multiple globs
1774        let settings = build_settings(&["*.rs", "*.txt", "src/**"]);
1775        assert!(!settings.enabled_for_file(&test_file, &cx));
1776        let settings = build_settings(&["*.txt", "*.md", "other/**"]);
1777        assert!(settings.enabled_for_file(&test_file, &cx));
1778
1779        // Test dot files
1780        let dot_file = make_test_file(&[".config", "settings.json"]);
1781        let settings = build_settings(&[".*/**"]);
1782        assert!(!settings.enabled_for_file(&dot_file, &cx));
1783
1784        let dot_env_file = make_test_file(&[".env"]);
1785        let settings = build_settings(&[".env"]);
1786        assert!(!settings.enabled_for_file(&dot_env_file, &cx));
1787
1788        // Test tilde expansion
1789        let home = shellexpand::tilde("~").into_owned().to_string();
1790        let home_file = make_test_file(&[&home, "test.rs"]);
1791        let settings = build_settings(&["~/test.rs"]);
1792        assert!(!settings.enabled_for_file(&home_file, &cx));
1793    }
1794
1795    #[test]
1796    fn test_resolve_language_servers() {
1797        fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {
1798            names
1799                .iter()
1800                .copied()
1801                .map(|name| LanguageServerName(name.to_string().into()))
1802                .collect::<Vec<_>>()
1803        }
1804
1805        let available_language_servers = language_server_names(&[
1806            "typescript-language-server",
1807            "biome",
1808            "deno",
1809            "eslint",
1810            "tailwind",
1811        ]);
1812
1813        // A value of just `["..."]` is the same as taking all of the available language servers.
1814        assert_eq!(
1815            LanguageSettings::resolve_language_servers(
1816                &[LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()],
1817                &available_language_servers,
1818            ),
1819            available_language_servers
1820        );
1821
1822        // Referencing one of the available language servers will change its order.
1823        assert_eq!(
1824            LanguageSettings::resolve_language_servers(
1825                &[
1826                    "biome".into(),
1827                    LanguageSettings::REST_OF_LANGUAGE_SERVERS.into(),
1828                    "deno".into()
1829                ],
1830                &available_language_servers
1831            ),
1832            language_server_names(&[
1833                "biome",
1834                "typescript-language-server",
1835                "eslint",
1836                "tailwind",
1837                "deno",
1838            ])
1839        );
1840
1841        // Negating an available language server removes it from the list.
1842        assert_eq!(
1843            LanguageSettings::resolve_language_servers(
1844                &[
1845                    "deno".into(),
1846                    "!typescript-language-server".into(),
1847                    "!biome".into(),
1848                    LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1849                ],
1850                &available_language_servers
1851            ),
1852            language_server_names(&["deno", "eslint", "tailwind"])
1853        );
1854
1855        // Adding a language server not in the list of available language servers adds it to the list.
1856        assert_eq!(
1857            LanguageSettings::resolve_language_servers(
1858                &[
1859                    "my-cool-language-server".into(),
1860                    LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1861                ],
1862                &available_language_servers
1863            ),
1864            language_server_names(&[
1865                "my-cool-language-server",
1866                "typescript-language-server",
1867                "biome",
1868                "deno",
1869                "eslint",
1870                "tailwind",
1871            ])
1872        );
1873    }
1874}