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, MaxLineLen, TabWidth, TrimTrailingWs},
   9};
  10use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
  11use gpui::{App, Modifiers, SharedString};
  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    FormatOnSave, IndentGuideSettingsContent, LanguageSettingsContent, ParameterizedJsonSchema,
  21    RewrapBehavior, Settings, SettingsKey, SettingsLocation, SettingsSources, SettingsStore,
  22    SettingsUi,
  23};
  24use shellexpand;
  25use std::{borrow::Cow, num::NonZeroU32, path::Path, slice, sync::Arc};
  26use util::schemars::replace_subschema;
  27use util::serde::default_true;
  28
  29/// Initializes the language settings.
  30pub fn init(cx: &mut App) {
  31    AllLanguageSettings::register(cx);
  32}
  33
  34/// Returns the settings for the specified language from the provided file.
  35pub fn language_settings<'a>(
  36    language: Option<LanguageName>,
  37    file: Option<&'a Arc<dyn File>>,
  38    cx: &'a App,
  39) -> Cow<'a, LanguageSettings> {
  40    let location = file.map(|f| SettingsLocation {
  41        worktree_id: f.worktree_id(cx),
  42        path: f.path().as_ref(),
  43    });
  44    AllLanguageSettings::get(location, cx).language(location, language.as_ref(), cx)
  45}
  46
  47/// Returns the settings for all languages from the provided file.
  48pub fn all_language_settings<'a>(
  49    file: Option<&'a Arc<dyn File>>,
  50    cx: &'a App,
  51) -> &'a AllLanguageSettings {
  52    let location = file.map(|f| SettingsLocation {
  53        worktree_id: f.worktree_id(cx),
  54        path: f.path().as_ref(),
  55    });
  56    AllLanguageSettings::get(location, cx)
  57}
  58
  59/// The settings for all languages.
  60#[derive(Debug, Clone)]
  61pub struct AllLanguageSettings {
  62    /// The edit prediction settings.
  63    pub edit_predictions: EditPredictionSettings,
  64    pub defaults: LanguageSettings,
  65    languages: HashMap<LanguageName, LanguageSettings>,
  66    pub(crate) file_types: FxHashMap<Arc<str>, GlobSet>,
  67}
  68
  69/// The settings for a particular language.
  70#[derive(Debug, Clone, Deserialize)]
  71pub struct LanguageSettings {
  72    /// How many columns a tab should occupy.
  73    pub tab_size: NonZeroU32,
  74    /// Whether to indent lines using tab characters, as opposed to multiple
  75    /// spaces.
  76    pub hard_tabs: bool,
  77    /// How to soft-wrap long lines of text.
  78    pub soft_wrap: SoftWrap,
  79    /// The column at which to soft-wrap lines, for buffers where soft-wrap
  80    /// is enabled.
  81    pub preferred_line_length: u32,
  82    /// Whether to show wrap guides (vertical rulers) in the editor.
  83    /// Setting this to true will show a guide at the 'preferred_line_length' value
  84    /// if softwrap is set to 'preferred_line_length', and will show any
  85    /// additional guides as specified by the 'wrap_guides' setting.
  86    pub show_wrap_guides: bool,
  87    /// Character counts at which to show wrap guides (vertical rulers) in the editor.
  88    pub wrap_guides: Vec<usize>,
  89    /// Indent guide related settings.
  90    /// todo!() shouldthis be not the content type?
  91    pub indent_guides: IndentGuideSettingsContent,
  92    /// Whether or not to perform a buffer format before saving.
  93    pub format_on_save: FormatOnSave,
  94    /// Whether or not to remove any trailing whitespace from lines of a buffer
  95    /// before saving it.
  96    pub remove_trailing_whitespace_on_save: bool,
  97    /// Whether or not to ensure there's a single newline at the end of a buffer
  98    /// when saving it.
  99    pub ensure_final_newline_on_save: bool,
 100    /// How to perform a buffer format.
 101    pub formatter: settings::SelectedFormatter,
 102    /// Zed's Prettier integration settings.
 103    pub prettier: settings::PrettierSettingsContent,
 104    /// Whether to automatically close JSX tags.
 105    pub jsx_tag_auto_close: JsxTagAutoCloseSettings,
 106    /// Whether to use language servers to provide code intelligence.
 107    pub enable_language_server: bool,
 108    /// The list of language servers to use (or disable) for this language.
 109    ///
 110    /// This array should consist of language server IDs, as well as the following
 111    /// special tokens:
 112    /// - `"!<language_server_id>"` - A language server ID prefixed with a `!` will be disabled.
 113    /// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
 114    pub language_servers: Vec<String>,
 115    /// Controls where the `editor::Rewrap` action is allowed for this language.
 116    ///
 117    /// Note: This setting has no effect in Vim mode, as rewrap is already
 118    /// allowed everywhere.
 119    pub allow_rewrap: RewrapBehavior,
 120    /// Controls whether edit predictions are shown immediately (true)
 121    /// or manually by triggering `editor::ShowEditPrediction` (false).
 122    pub show_edit_predictions: bool,
 123    /// Controls whether edit predictions are shown in the given language
 124    /// scopes.
 125    pub edit_predictions_disabled_in: Vec<String>,
 126    /// Whether to show tabs and spaces in the editor.
 127    pub show_whitespaces: ShowWhitespaceSetting,
 128    /// Visible characters used to render whitespace when show_whitespaces is enabled.
 129    pub whitespace_map: WhitespaceMap,
 130    /// Whether to start a new line with a comment when a previous line is a comment as well.
 131    pub extend_comment_on_newline: bool,
 132    /// Inlay hint related settings.
 133    pub inlay_hints: settings::InlayHintSettings,
 134    /// Whether to automatically close brackets.
 135    pub use_autoclose: bool,
 136    /// Whether to automatically surround text with brackets.
 137    pub use_auto_surround: bool,
 138    /// Whether to use additional LSP queries to format (and amend) the code after
 139    /// every "trigger" symbol input, defined by LSP server capabilities.
 140    pub use_on_type_format: bool,
 141    /// Whether indentation should be adjusted based on the context whilst typing.
 142    pub auto_indent: bool,
 143    /// Whether indentation of pasted content should be adjusted based on the context.
 144    pub auto_indent_on_paste: bool,
 145    /// Controls how the editor handles the autoclosed characters.
 146    pub always_treat_brackets_as_autoclosed: bool,
 147    /// Which code actions to run on save
 148    pub code_actions_on_format: HashMap<String, bool>,
 149    /// Whether to perform linked edits
 150    pub linked_edits: bool,
 151    /// Task configuration for this language.
 152    pub tasks: LanguageTaskConfig,
 153    /// Whether to pop the completions menu while typing in an editor without
 154    /// explicitly requesting it.
 155    pub show_completions_on_input: bool,
 156    /// Whether to display inline and alongside documentation for items in the
 157    /// completions menu.
 158    pub show_completion_documentation: bool,
 159    /// Completion settings for this language.
 160    pub completions: CompletionSettings,
 161    /// Preferred debuggers for this language.
 162    pub debuggers: Vec<String>,
 163}
 164
 165impl LanguageSettings {
 166    /// A token representing the rest of the available language servers.
 167    const REST_OF_LANGUAGE_SERVERS: &'static str = "...";
 168
 169    /// Returns the customized list of language servers from the list of
 170    /// available language servers.
 171    pub fn customized_language_servers(
 172        &self,
 173        available_language_servers: &[LanguageServerName],
 174    ) -> Vec<LanguageServerName> {
 175        Self::resolve_language_servers(&self.language_servers, available_language_servers)
 176    }
 177
 178    pub(crate) fn resolve_language_servers(
 179        configured_language_servers: &[String],
 180        available_language_servers: &[LanguageServerName],
 181    ) -> Vec<LanguageServerName> {
 182        let (disabled_language_servers, enabled_language_servers): (
 183            Vec<LanguageServerName>,
 184            Vec<LanguageServerName>,
 185        ) = configured_language_servers.iter().partition_map(
 186            |language_server| match language_server.strip_prefix('!') {
 187                Some(disabled) => Either::Left(LanguageServerName(disabled.to_string().into())),
 188                None => Either::Right(LanguageServerName(language_server.clone().into())),
 189            },
 190        );
 191
 192        let rest = available_language_servers
 193            .iter()
 194            .filter(|&available_language_server| {
 195                !disabled_language_servers.contains(available_language_server)
 196                    && !enabled_language_servers.contains(available_language_server)
 197            })
 198            .cloned()
 199            .collect::<Vec<_>>();
 200
 201        enabled_language_servers
 202            .into_iter()
 203            .flat_map(|language_server| {
 204                if language_server.0.as_ref() == Self::REST_OF_LANGUAGE_SERVERS {
 205                    rest.clone()
 206                } else {
 207                    vec![language_server]
 208                }
 209            })
 210            .collect::<Vec<_>>()
 211    }
 212}
 213
 214/// The provider that supplies edit predictions.
 215#[derive(
 216    Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema, SettingsUi,
 217)]
 218#[serde(rename_all = "snake_case")]
 219pub enum EditPredictionProvider {
 220    None,
 221    #[default]
 222    Copilot,
 223    Supermaven,
 224    Zed,
 225}
 226
 227impl EditPredictionProvider {
 228    pub fn is_zed(&self) -> bool {
 229        match self {
 230            EditPredictionProvider::Zed => true,
 231            EditPredictionProvider::None
 232            | EditPredictionProvider::Copilot
 233            | EditPredictionProvider::Supermaven => false,
 234        }
 235    }
 236}
 237
 238/// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot)
 239/// or [Supermaven](https://supermaven.com).
 240#[derive(Clone, Debug, Default, SettingsUi)]
 241pub struct EditPredictionSettings {
 242    /// The provider that supplies edit predictions.
 243    pub provider: EditPredictionProvider,
 244    /// A list of globs representing files that edit predictions should be disabled for.
 245    /// This list adds to a pre-existing, sensible default set of globs.
 246    /// Any additional ones you add are combined with them.
 247    #[settings_ui(skip)]
 248    pub disabled_globs: Vec<DisabledGlob>,
 249    /// Configures how edit predictions are displayed in the buffer.
 250    pub mode: EditPredictionsMode,
 251    /// Settings specific to GitHub Copilot.
 252    pub copilot: CopilotSettings,
 253    /// Whether edit predictions are enabled in the assistant panel.
 254    /// This setting has no effect if globally disabled.
 255    pub enabled_in_text_threads: bool,
 256}
 257
 258impl EditPredictionSettings {
 259    /// Returns whether edit predictions are enabled for the given path.
 260    pub fn enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
 261        !self.disabled_globs.iter().any(|glob| {
 262            if glob.is_absolute {
 263                file.as_local()
 264                    .is_some_and(|local| glob.matcher.is_match(local.abs_path(cx)))
 265            } else {
 266                glob.matcher.is_match(file.path())
 267            }
 268        })
 269    }
 270}
 271
 272#[derive(Clone, Debug)]
 273pub struct DisabledGlob {
 274    matcher: GlobMatcher,
 275    is_absolute: bool,
 276}
 277
 278/// The mode in which edit predictions should be displayed.
 279#[derive(
 280    Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema, SettingsUi,
 281)]
 282#[serde(rename_all = "snake_case")]
 283pub enum EditPredictionsMode {
 284    /// If provider supports it, display inline when holding modifier key (e.g., alt).
 285    /// Otherwise, eager preview is used.
 286    #[serde(alias = "auto")]
 287    Subtle,
 288    /// Display inline when there are no language server completions available.
 289    #[default]
 290    #[serde(alias = "eager_preview")]
 291    Eager,
 292}
 293
 294#[derive(Clone, Debug, Default, SettingsUi)]
 295pub struct CopilotSettings {
 296    /// HTTP/HTTPS proxy to use for Copilot.
 297    #[settings_ui(skip)]
 298    pub proxy: Option<String>,
 299    /// Disable certificate verification for proxy (not recommended).
 300    pub proxy_no_verify: Option<bool>,
 301    /// Enterprise URI for Copilot.
 302    #[settings_ui(skip)]
 303    pub enterprise_uri: Option<String>,
 304}
 305
 306inventory::submit! {
 307    ParameterizedJsonSchema {
 308        add_and_get_ref: |generator, params, _cx| {
 309            let language_settings_content_ref = generator
 310                .subschema_for::<LanguageSettingsContent>()
 311                .to_value();
 312            replace_subschema::<settings::LanguageToSettingsMap>(generator, || json_schema!({
 313                "type": "object",
 314                "properties": params
 315                    .language_names
 316                    .iter()
 317                    .map(|name| {
 318                        (
 319                            name.clone(),
 320                            language_settings_content_ref.clone(),
 321                        )
 322                    })
 323                    .collect::<serde_json::Map<_, _>>()
 324            }))
 325        }
 326    }
 327}
 328
 329/// Controls how completions are processed for this language.
 330#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi)]
 331#[serde(rename_all = "snake_case")]
 332pub struct CompletionSettings {
 333    /// Controls how words are completed.
 334    /// For large documents, not all words may be fetched for completion.
 335    ///
 336    /// Default: `fallback`
 337    #[serde(default = "default_words_completion_mode")]
 338    pub words: WordsCompletionMode,
 339    /// How many characters has to be in the completions query to automatically show the words-based completions.
 340    /// Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command.
 341    ///
 342    /// Default: 3
 343    #[serde(default = "default_3")]
 344    pub words_min_length: usize,
 345    /// Whether to fetch LSP completions or not.
 346    ///
 347    /// Default: true
 348    #[serde(default = "default_true")]
 349    pub lsp: bool,
 350    /// When fetching LSP completions, determines how long to wait for a response of a particular server.
 351    /// When set to 0, waits indefinitely.
 352    ///
 353    /// Default: 0
 354    #[serde(default)]
 355    pub lsp_fetch_timeout_ms: u64,
 356    /// Controls how LSP completions are inserted.
 357    ///
 358    /// Default: "replace_suffix"
 359    #[serde(default = "default_lsp_insert_mode")]
 360    pub lsp_insert_mode: LspInsertMode,
 361}
 362
 363/// Controls how document's words are completed.
 364#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 365#[serde(rename_all = "snake_case")]
 366pub enum WordsCompletionMode {
 367    /// Always fetch document's words for completions along with LSP completions.
 368    Enabled,
 369    /// Only if LSP response errors or times out,
 370    /// use document's words to show completions.
 371    Fallback,
 372    /// Never fetch or complete document's words for completions.
 373    /// (Word-based completions can still be queried via a separate action)
 374    Disabled,
 375}
 376
 377#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 378#[serde(rename_all = "snake_case")]
 379pub enum LspInsertMode {
 380    /// Replaces text before the cursor, using the `insert` range described in the LSP specification.
 381    Insert,
 382    /// Replaces text before and after the cursor, using the `replace` range described in the LSP specification.
 383    Replace,
 384    /// Behaves like `"replace"` if the text that would be replaced is a subsequence of the completion text,
 385    /// and like `"insert"` otherwise.
 386    ReplaceSubsequence,
 387    /// Behaves like `"replace"` if the text after the cursor is a suffix of the completion, and like
 388    /// `"insert"` otherwise.
 389    ReplaceSuffix,
 390}
 391
 392fn default_words_completion_mode() -> WordsCompletionMode {
 393    WordsCompletionMode::Fallback
 394}
 395
 396fn default_lsp_insert_mode() -> LspInsertMode {
 397    LspInsertMode::ReplaceSuffix
 398}
 399
 400fn default_3() -> usize {
 401    3
 402}
 403
 404/// Controls how whitespace should be displayedin the editor.
 405#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi)]
 406#[serde(rename_all = "snake_case")]
 407pub enum ShowWhitespaceSetting {
 408    /// Draw whitespace only for the selected text.
 409    Selection,
 410    /// Do not draw any tabs or spaces.
 411    None,
 412    /// Draw all invisible symbols.
 413    All,
 414    /// Draw whitespaces at boundaries only.
 415    ///
 416    /// For a whitespace to be on a boundary, any of the following conditions need to be met:
 417    /// - It is a tab
 418    /// - It is adjacent to an edge (start or end)
 419    /// - It is adjacent to a whitespace (left or right)
 420    Boundary,
 421    /// Draw whitespaces only after non-whitespace characters.
 422    Trailing,
 423}
 424
 425#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, SettingsUi)]
 426pub struct WhitespaceMap {
 427    #[serde(default)]
 428    pub space: Option<String>,
 429    #[serde(default)]
 430    pub tab: Option<String>,
 431}
 432
 433impl WhitespaceMap {
 434    pub fn space(&self) -> SharedString {
 435        self.space
 436            .as_ref()
 437            .map_or_else(|| SharedString::from(""), |s| SharedString::from(s))
 438    }
 439
 440    pub fn tab(&self) -> SharedString {
 441        self.tab
 442            .as_ref()
 443            .map_or_else(|| SharedString::from(""), |s| SharedString::from(s))
 444    }
 445}
 446
 447/// The settings for indent guides.
 448#[derive(
 449    Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, SettingsUi,
 450)]
 451pub struct IndentGuideSettings {
 452    /// Whether to display indent guides in the editor.
 453    ///
 454    /// Default: true
 455    #[serde(default = "default_true")]
 456    pub enabled: bool,
 457    /// The width of the indent guides in pixels, between 1 and 10.
 458    ///
 459    /// Default: 1
 460    #[serde(default = "line_width")]
 461    pub line_width: u32,
 462    /// The width of the active indent guide in pixels, between 1 and 10.
 463    ///
 464    /// Default: 1
 465    #[serde(default = "active_line_width")]
 466    pub active_line_width: u32,
 467    /// Determines how indent guides are colored.
 468    ///
 469    /// Default: Fixed
 470    #[serde(default)]
 471    pub coloring: IndentGuideColoring,
 472    /// Determines how indent guide backgrounds are colored.
 473    ///
 474    /// Default: Disabled
 475    #[serde(default)]
 476    pub background_coloring: IndentGuideBackgroundColoring,
 477}
 478
 479fn line_width() -> u32 {
 480    1
 481}
 482
 483fn active_line_width() -> u32 {
 484    line_width()
 485}
 486
 487/// Determines how indent guides are colored.
 488#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
 489#[serde(rename_all = "snake_case")]
 490pub enum IndentGuideColoring {
 491    /// Do not render any lines for indent guides.
 492    Disabled,
 493    /// Use the same color for all indentation levels.
 494    #[default]
 495    Fixed,
 496    /// Use a different color for each indentation level.
 497    IndentAware,
 498}
 499
 500/// Determines how indent guide backgrounds are colored.
 501#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
 502#[serde(rename_all = "snake_case")]
 503pub enum IndentGuideBackgroundColoring {
 504    /// Do not render any background for indent guides.
 505    #[default]
 506    Disabled,
 507    /// Use a different color for each indentation level.
 508    IndentAware,
 509}
 510
 511/// The settings for inlay hints.
 512#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)]
 513pub struct InlayHintSettings {
 514    /// Global switch to toggle hints on and off.
 515    ///
 516    /// Default: false
 517    #[serde(default)]
 518    pub enabled: bool,
 519    /// Global switch to toggle inline values on and off when debugging.
 520    ///
 521    /// Default: true
 522    #[serde(default = "default_true")]
 523    pub show_value_hints: bool,
 524    /// Whether type hints should be shown.
 525    ///
 526    /// Default: true
 527    #[serde(default = "default_true")]
 528    pub show_type_hints: bool,
 529    /// Whether parameter hints should be shown.
 530    ///
 531    /// Default: true
 532    #[serde(default = "default_true")]
 533    pub show_parameter_hints: bool,
 534    /// Whether other hints should be shown.
 535    ///
 536    /// Default: true
 537    #[serde(default = "default_true")]
 538    pub show_other_hints: bool,
 539    /// Whether to show a background for inlay hints.
 540    ///
 541    /// If set to `true`, the background will use the `hint.background` color
 542    /// from the current theme.
 543    ///
 544    /// Default: false
 545    #[serde(default)]
 546    pub show_background: bool,
 547    /// Whether or not to debounce inlay hints updates after buffer edits.
 548    ///
 549    /// Set to 0 to disable debouncing.
 550    ///
 551    /// Default: 700
 552    #[serde(default = "edit_debounce_ms")]
 553    pub edit_debounce_ms: u64,
 554    /// Whether or not to debounce inlay hints updates after buffer scrolls.
 555    ///
 556    /// Set to 0 to disable debouncing.
 557    ///
 558    /// Default: 50
 559    #[serde(default = "scroll_debounce_ms")]
 560    pub scroll_debounce_ms: u64,
 561    /// Toggles inlay hints (hides or shows) when the user presses the modifiers specified.
 562    /// If only a subset of the modifiers specified is pressed, hints are not toggled.
 563    /// If no modifiers are specified, this is equivalent to `None`.
 564    ///
 565    /// Default: None
 566    #[serde(default)]
 567    pub toggle_on_modifiers_press: Option<Modifiers>,
 568}
 569
 570fn edit_debounce_ms() -> u64 {
 571    700
 572}
 573
 574fn scroll_debounce_ms() -> u64 {
 575    50
 576}
 577
 578/// The task settings for a particular language.
 579#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema, SettingsUi)]
 580pub struct LanguageTaskConfig {
 581    /// Extra task variables to set for a particular language.
 582    #[serde(default)]
 583    pub variables: HashMap<String, String>,
 584    #[serde(default = "default_true")]
 585    pub enabled: bool,
 586    /// Use LSP tasks over Zed language extension ones.
 587    /// If no LSP tasks are returned due to error/timeout or regular execution,
 588    /// Zed language extension tasks will be used instead.
 589    ///
 590    /// Other Zed tasks will still be shown:
 591    /// * Zed task from either of the task config file
 592    /// * Zed task from history (e.g. one-off task was spawned before)
 593    #[serde(default = "default_true")]
 594    pub prefer_lsp: bool,
 595}
 596
 597impl InlayHintSettings {
 598    /// Returns the kinds of inlay hints that are enabled based on the settings.
 599    pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
 600        let mut kinds = HashSet::default();
 601        if self.show_type_hints {
 602            kinds.insert(Some(InlayHintKind::Type));
 603        }
 604        if self.show_parameter_hints {
 605            kinds.insert(Some(InlayHintKind::Parameter));
 606        }
 607        if self.show_other_hints {
 608            kinds.insert(None);
 609        }
 610        kinds
 611    }
 612}
 613
 614impl AllLanguageSettings {
 615    /// Returns the [`LanguageSettings`] for the language with the specified name.
 616    pub fn language<'a>(
 617        &'a self,
 618        location: Option<SettingsLocation<'a>>,
 619        language_name: Option<&LanguageName>,
 620        cx: &'a App,
 621    ) -> Cow<'a, LanguageSettings> {
 622        let settings = language_name
 623            .and_then(|name| self.languages.get(name))
 624            .unwrap_or(&self.defaults);
 625
 626        let editorconfig_properties = location.and_then(|location| {
 627            cx.global::<SettingsStore>()
 628                .editorconfig_properties(location.worktree_id, location.path)
 629        });
 630        if let Some(editorconfig_properties) = editorconfig_properties {
 631            let mut settings = settings.clone();
 632            merge_with_editorconfig(&mut settings, &editorconfig_properties);
 633            Cow::Owned(settings)
 634        } else {
 635            Cow::Borrowed(settings)
 636        }
 637    }
 638
 639    /// Returns whether edit predictions are enabled for the given path.
 640    pub fn edit_predictions_enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
 641        self.edit_predictions.enabled_for_file(file, cx)
 642    }
 643
 644    /// Returns whether edit predictions are enabled for the given language and path.
 645    pub fn show_edit_predictions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
 646        self.language(None, language.map(|l| l.name()).as_ref(), cx)
 647            .show_edit_predictions
 648    }
 649
 650    /// Returns the edit predictions preview mode for the given language and path.
 651    pub fn edit_predictions_mode(&self) -> EditPredictionsMode {
 652        self.edit_predictions.mode
 653    }
 654}
 655
 656fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
 657    let preferred_line_length = cfg.get::<MaxLineLen>().ok().and_then(|v| match v {
 658        MaxLineLen::Value(u) => Some(u as u32),
 659        MaxLineLen::Off => None,
 660    });
 661    let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
 662        IndentSize::Value(u) => NonZeroU32::new(u as u32),
 663        IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
 664            TabWidth::Value(u) => NonZeroU32::new(u as u32),
 665        }),
 666    });
 667    let hard_tabs = cfg
 668        .get::<IndentStyle>()
 669        .map(|v| v.eq(&IndentStyle::Tabs))
 670        .ok();
 671    let ensure_final_newline_on_save = cfg
 672        .get::<FinalNewline>()
 673        .map(|v| match v {
 674            FinalNewline::Value(b) => b,
 675        })
 676        .ok();
 677    let remove_trailing_whitespace_on_save = cfg
 678        .get::<TrimTrailingWs>()
 679        .map(|v| match v {
 680            TrimTrailingWs::Value(b) => b,
 681        })
 682        .ok();
 683    fn merge<T>(target: &mut T, value: Option<T>) {
 684        if let Some(value) = value {
 685            *target = value;
 686        }
 687    }
 688    merge(&mut settings.preferred_line_length, preferred_line_length);
 689    merge(&mut settings.tab_size, tab_size);
 690    merge(&mut settings.hard_tabs, hard_tabs);
 691    merge(
 692        &mut settings.remove_trailing_whitespace_on_save,
 693        remove_trailing_whitespace_on_save,
 694    );
 695    merge(
 696        &mut settings.ensure_final_newline_on_save,
 697        ensure_final_newline_on_save,
 698    );
 699}
 700
 701/// The kind of an inlay hint.
 702#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 703pub enum InlayHintKind {
 704    /// An inlay hint for a type.
 705    Type,
 706    /// An inlay hint for a parameter.
 707    Parameter,
 708}
 709
 710impl InlayHintKind {
 711    /// Returns the [`InlayHintKind`] from the given name.
 712    ///
 713    /// Returns `None` if `name` does not match any of the expected
 714    /// string representations.
 715    pub fn from_name(name: &str) -> Option<Self> {
 716        match name {
 717            "type" => Some(InlayHintKind::Type),
 718            "parameter" => Some(InlayHintKind::Parameter),
 719            _ => None,
 720        }
 721    }
 722
 723    /// Returns the name of this [`InlayHintKind`].
 724    pub fn name(&self) -> &'static str {
 725        match self {
 726            InlayHintKind::Type => "type",
 727            InlayHintKind::Parameter => "parameter",
 728        }
 729    }
 730}
 731
 732impl settings::Settings for AllLanguageSettings {
 733    fn from_default(content: &settings::SettingsContent, cx: &mut App) -> Option<Self> {
 734        let defaults = content.project.all_languages.defaults;
 735        let default_language_settings = LanguageSettings {
 736            tab_size: defaults.tab_size?,
 737            hard_tabs: defaults.hard_tabs?,
 738            soft_wrap: defaults.soft_wrap?,
 739            preferred_line_length: defaults.preferred_line_length?,
 740            show_wrap_guides: defaults.show_wrap_guides?,
 741            wrap_guides: defaults.wrap_guides?,
 742            indent_guides: defaults.indent_guides?,
 743            format_on_save: defaults.format_on_save?,
 744            remove_trailing_whitespace_on_save: defaults.remove_trailing_whitespace_on_save?,
 745            ensure_final_newline_on_save: defaults.ensure_final_newline_on_save?,
 746            formatter: defaults.formatter?,
 747            prettier: defaults.prettier?,
 748            jsx_tag_auto_close: defaults.jsx_tag_auto_close?,
 749            enable_language_server: defaults.enable_language_server?,
 750            language_servers: defaults.language_servers?,
 751            allow_rewrap: defaults.allow_rewrap?,
 752            show_edit_predictions: defaults.show_edit_predictions?,
 753            edit_predictions_disabled_in: defaults.edit_predictions_disabled_in?,
 754            show_whitespaces: defaults.show_whitespaces?,
 755            whitespace_map: defaults.whitespace_map?,
 756            extend_comment_on_newline: defaults.extend_comment_on_newline?,
 757            inlay_hints: defaults.inlay_hints?,
 758            use_autoclose: defaults.use_autoclose?,
 759            use_auto_surround: defaults.use_auto_surround?,
 760            use_on_type_format: defaults.use_on_type_format?,
 761            auto_indent: defaults.auto_indent?,
 762            auto_indent_on_paste: defaults.auto_indent_on_paste?,
 763            always_treat_brackets_as_autoclosed: defaults.always_treat_brackets_as_autoclosed?,
 764            code_actions_on_format: defaults.code_actions_on_format?,
 765            linked_edits: defaults.linked_edits?,
 766            tasks: defaults.tasks?,
 767            show_completions_on_input: defaults.show_completions_on_input?,
 768            show_completion_documentation: defaults.show_completion_documentation?,
 769            completions: defaults.completions?,
 770            debuggers: defaults.debuggers?,
 771        };
 772
 773        todo!();
 774    }
 775
 776    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
 777        let default_value = sources.default;
 778
 779        // A default is provided for all settings.
 780        let mut defaults: LanguageSettings =
 781            serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
 782
 783        let mut languages = HashMap::default();
 784        for (language_name, settings) in &default_value.languages.0 {
 785            let mut language_settings = defaults.clone();
 786            merge_settings(&mut language_settings, settings);
 787            languages.insert(language_name.clone(), language_settings);
 788        }
 789
 790        let mut edit_prediction_provider = default_value
 791            .features
 792            .as_ref()
 793            .and_then(|f| f.edit_prediction_provider);
 794        let mut edit_predictions_mode = default_value
 795            .edit_predictions
 796            .as_ref()
 797            .map(|edit_predictions| edit_predictions.mode)
 798            .ok_or_else(Self::missing_default)?;
 799
 800        let mut completion_globs: HashSet<&String> = default_value
 801            .edit_predictions
 802            .as_ref()
 803            .and_then(|c| c.disabled_globs.as_ref())
 804            .map(|globs| globs.iter().collect())
 805            .ok_or_else(Self::missing_default)?;
 806
 807        let mut copilot_settings = default_value
 808            .edit_predictions
 809            .as_ref()
 810            .map(|settings| CopilotSettings {
 811                proxy: settings.copilot.proxy.clone(),
 812                proxy_no_verify: settings.copilot.proxy_no_verify,
 813                enterprise_uri: settings.copilot.enterprise_uri.clone(),
 814            })
 815            .unwrap_or_default();
 816
 817        let mut enabled_in_text_threads = default_value
 818            .edit_predictions
 819            .as_ref()
 820            .map(|settings| settings.enabled_in_text_threads)
 821            .unwrap_or(true);
 822
 823        let mut file_types: FxHashMap<Arc<str>, GlobSet> = FxHashMap::default();
 824
 825        for (language, patterns) in &default_value.file_types {
 826            let mut builder = GlobSetBuilder::new();
 827
 828            for pattern in patterns {
 829                builder.add(Glob::new(pattern)?);
 830            }
 831
 832            file_types.insert(language.clone(), builder.build()?);
 833        }
 834
 835        for user_settings in sources.customizations() {
 836            if let Some(provider) = user_settings
 837                .features
 838                .as_ref()
 839                .and_then(|f| f.edit_prediction_provider)
 840            {
 841                edit_prediction_provider = Some(provider);
 842            }
 843
 844            if let Some(edit_predictions) = user_settings.edit_predictions.as_ref() {
 845                edit_predictions_mode = edit_predictions.mode;
 846                enabled_in_text_threads = edit_predictions.enabled_in_text_threads;
 847
 848                if let Some(disabled_globs) = edit_predictions.disabled_globs.as_ref() {
 849                    completion_globs.extend(disabled_globs.iter());
 850                }
 851            }
 852
 853            if let Some(proxy) = user_settings
 854                .edit_predictions
 855                .as_ref()
 856                .and_then(|settings| settings.copilot.proxy.clone())
 857            {
 858                copilot_settings.proxy = Some(proxy);
 859            }
 860
 861            if let Some(proxy_no_verify) = user_settings
 862                .edit_predictions
 863                .as_ref()
 864                .and_then(|settings| settings.copilot.proxy_no_verify)
 865            {
 866                copilot_settings.proxy_no_verify = Some(proxy_no_verify);
 867            }
 868
 869            if let Some(enterprise_uri) = user_settings
 870                .edit_predictions
 871                .as_ref()
 872                .and_then(|settings| settings.copilot.enterprise_uri.clone())
 873            {
 874                copilot_settings.enterprise_uri = Some(enterprise_uri);
 875            }
 876
 877            // A user's global settings override the default global settings and
 878            // all default language-specific settings.
 879            merge_settings(&mut defaults, &user_settings.defaults);
 880            for language_settings in languages.values_mut() {
 881                merge_settings(language_settings, &user_settings.defaults);
 882            }
 883
 884            // A user's language-specific settings override default language-specific settings.
 885            for (language_name, user_language_settings) in &user_settings.languages.0 {
 886                merge_settings(
 887                    languages
 888                        .entry(language_name.clone())
 889                        .or_insert_with(|| defaults.clone()),
 890                    user_language_settings,
 891                );
 892            }
 893
 894            for (language, patterns) in &user_settings.file_types {
 895                let mut builder = GlobSetBuilder::new();
 896
 897                let default_value = default_value.file_types.get(&language.clone());
 898
 899                // Merge the default value with the user's value.
 900                if let Some(patterns) = default_value {
 901                    for pattern in patterns {
 902                        builder.add(Glob::new(pattern)?);
 903                    }
 904                }
 905
 906                for pattern in patterns {
 907                    builder.add(Glob::new(pattern)?);
 908                }
 909
 910                file_types.insert(language.clone(), builder.build()?);
 911            }
 912        }
 913
 914        Ok(Self {
 915            edit_predictions: EditPredictionSettings {
 916                provider: if let Some(provider) = edit_prediction_provider {
 917                    provider
 918                } else {
 919                    EditPredictionProvider::None
 920                },
 921                disabled_globs: completion_globs
 922                    .iter()
 923                    .filter_map(|g| {
 924                        let expanded_g = shellexpand::tilde(g).into_owned();
 925                        Some(DisabledGlob {
 926                            matcher: globset::Glob::new(&expanded_g).ok()?.compile_matcher(),
 927                            is_absolute: Path::new(&expanded_g).is_absolute(),
 928                        })
 929                    })
 930                    .collect(),
 931                mode: edit_predictions_mode,
 932                copilot: copilot_settings,
 933                enabled_in_text_threads,
 934            },
 935            defaults,
 936            languages,
 937            file_types,
 938        })
 939    }
 940
 941    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
 942        let d = &mut current.defaults;
 943        if let Some(size) = vscode
 944            .read_value("editor.tabSize")
 945            .and_then(|v| v.as_u64())
 946            .and_then(|n| NonZeroU32::new(n as u32))
 947        {
 948            d.tab_size = Some(size);
 949        }
 950        if let Some(v) = vscode.read_bool("editor.insertSpaces") {
 951            d.hard_tabs = Some(!v);
 952        }
 953
 954        vscode.enum_setting("editor.wordWrap", &mut d.soft_wrap, |s| match s {
 955            "on" => Some(SoftWrap::EditorWidth),
 956            "wordWrapColumn" => Some(SoftWrap::PreferLine),
 957            "bounded" => Some(SoftWrap::Bounded),
 958            "off" => Some(SoftWrap::None),
 959            _ => None,
 960        });
 961        vscode.u32_setting("editor.wordWrapColumn", &mut d.preferred_line_length);
 962
 963        if let Some(arr) = vscode
 964            .read_value("editor.rulers")
 965            .and_then(|v| v.as_array())
 966            .map(|v| v.iter().map(|n| n.as_u64().map(|n| n as usize)).collect())
 967        {
 968            d.wrap_guides = arr;
 969        }
 970        if let Some(b) = vscode.read_bool("editor.guides.indentation") {
 971            if let Some(guide_settings) = d.indent_guides.as_mut() {
 972                guide_settings.enabled = b;
 973            } else {
 974                d.indent_guides = Some(IndentGuideSettings {
 975                    enabled: b,
 976                    ..Default::default()
 977                });
 978            }
 979        }
 980
 981        if let Some(b) = vscode.read_bool("editor.guides.formatOnSave") {
 982            d.format_on_save = Some(if b {
 983                FormatOnSave::On
 984            } else {
 985                FormatOnSave::Off
 986            });
 987        }
 988        vscode.bool_setting(
 989            "editor.trimAutoWhitespace",
 990            &mut d.remove_trailing_whitespace_on_save,
 991        );
 992        vscode.bool_setting(
 993            "files.insertFinalNewline",
 994            &mut d.ensure_final_newline_on_save,
 995        );
 996        vscode.bool_setting("editor.inlineSuggest.enabled", &mut d.show_edit_predictions);
 997        vscode.enum_setting("editor.renderWhitespace", &mut d.show_whitespaces, |s| {
 998            Some(match s {
 999                "boundary" => ShowWhitespaceSetting::Boundary,
1000                "trailing" => ShowWhitespaceSetting::Trailing,
1001                "selection" => ShowWhitespaceSetting::Selection,
1002                "all" => ShowWhitespaceSetting::All,
1003                _ => ShowWhitespaceSetting::None,
1004            })
1005        });
1006        vscode.enum_setting(
1007            "editor.autoSurround",
1008            &mut d.use_auto_surround,
1009            |s| match s {
1010                "languageDefined" | "quotes" | "brackets" => Some(true),
1011                "never" => Some(false),
1012                _ => None,
1013            },
1014        );
1015        vscode.bool_setting("editor.formatOnType", &mut d.use_on_type_format);
1016        vscode.bool_setting("editor.linkedEditing", &mut d.linked_edits);
1017        vscode.bool_setting("editor.formatOnPaste", &mut d.auto_indent_on_paste);
1018        vscode.bool_setting(
1019            "editor.suggestOnTriggerCharacters",
1020            &mut d.show_completions_on_input,
1021        );
1022        if let Some(b) = vscode.read_bool("editor.suggest.showWords") {
1023            let mode = if b {
1024                WordsCompletionMode::Enabled
1025            } else {
1026                WordsCompletionMode::Disabled
1027            };
1028            if let Some(completion_settings) = d.completions.as_mut() {
1029                completion_settings.words = mode;
1030            } else {
1031                d.completions = Some(CompletionSettings {
1032                    words: mode,
1033                    words_min_length: 3,
1034                    lsp: true,
1035                    lsp_fetch_timeout_ms: 0,
1036                    lsp_insert_mode: LspInsertMode::ReplaceSuffix,
1037                });
1038            }
1039        }
1040        // TODO: pull ^ out into helper and reuse for per-language settings
1041
1042        // vscodes file association map is inverted from ours, so we flip the mapping before merging
1043        let mut associations: HashMap<Arc<str>, Vec<String>> = HashMap::default();
1044        if let Some(map) = vscode
1045            .read_value("files.associations")
1046            .and_then(|v| v.as_object())
1047        {
1048            for (k, v) in map {
1049                let Some(v) = v.as_str() else { continue };
1050                associations.entry(v.into()).or_default().push(k.clone());
1051            }
1052        }
1053
1054        // TODO: do we want to merge imported globs per filetype? for now we'll just replace
1055        current.file_types.extend(associations);
1056
1057        // cursor global ignore list applies to cursor-tab, so transfer it to edit_predictions.disabled_globs
1058        if let Some(disabled_globs) = vscode
1059            .read_value("cursor.general.globalCursorIgnoreList")
1060            .and_then(|v| v.as_array())
1061        {
1062            current
1063                .edit_predictions
1064                .get_or_insert_default()
1065                .disabled_globs
1066                .get_or_insert_default()
1067                .extend(
1068                    disabled_globs
1069                        .iter()
1070                        .filter_map(|glob| glob.as_str())
1071                        .map(|s| s.to_string()),
1072                );
1073        }
1074    }
1075}
1076
1077fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
1078    fn merge<T>(target: &mut T, value: Option<T>) {
1079        if let Some(value) = value {
1080            *target = value;
1081        }
1082    }
1083
1084    merge(&mut settings.tab_size, src.tab_size);
1085    settings.tab_size = settings
1086        .tab_size
1087        .clamp(NonZeroU32::new(1).unwrap(), NonZeroU32::new(16).unwrap());
1088
1089    merge(&mut settings.hard_tabs, src.hard_tabs);
1090    merge(&mut settings.soft_wrap, src.soft_wrap);
1091    merge(&mut settings.use_autoclose, src.use_autoclose);
1092    merge(&mut settings.use_auto_surround, src.use_auto_surround);
1093    merge(&mut settings.use_on_type_format, src.use_on_type_format);
1094    merge(&mut settings.auto_indent, src.auto_indent);
1095    merge(&mut settings.auto_indent_on_paste, src.auto_indent_on_paste);
1096    merge(
1097        &mut settings.always_treat_brackets_as_autoclosed,
1098        src.always_treat_brackets_as_autoclosed,
1099    );
1100    merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
1101    merge(&mut settings.wrap_guides, src.wrap_guides.clone());
1102    merge(&mut settings.indent_guides, src.indent_guides);
1103    merge(
1104        &mut settings.code_actions_on_format,
1105        src.code_actions_on_format.clone(),
1106    );
1107    merge(&mut settings.linked_edits, src.linked_edits);
1108    merge(&mut settings.tasks, src.tasks.clone());
1109
1110    merge(
1111        &mut settings.preferred_line_length,
1112        src.preferred_line_length,
1113    );
1114    merge(&mut settings.formatter, src.formatter.clone());
1115    merge(&mut settings.prettier, src.prettier.clone());
1116    merge(
1117        &mut settings.jsx_tag_auto_close,
1118        src.jsx_tag_auto_close.clone(),
1119    );
1120    merge(&mut settings.format_on_save, src.format_on_save.clone());
1121    merge(
1122        &mut settings.remove_trailing_whitespace_on_save,
1123        src.remove_trailing_whitespace_on_save,
1124    );
1125    merge(
1126        &mut settings.ensure_final_newline_on_save,
1127        src.ensure_final_newline_on_save,
1128    );
1129    merge(
1130        &mut settings.enable_language_server,
1131        src.enable_language_server,
1132    );
1133    merge(&mut settings.language_servers, src.language_servers.clone());
1134    merge(&mut settings.allow_rewrap, src.allow_rewrap);
1135    merge(
1136        &mut settings.show_edit_predictions,
1137        src.show_edit_predictions,
1138    );
1139    merge(
1140        &mut settings.edit_predictions_disabled_in,
1141        src.edit_predictions_disabled_in.clone(),
1142    );
1143    merge(&mut settings.show_whitespaces, src.show_whitespaces);
1144    merge(&mut settings.whitespace_map, src.whitespace_map.clone());
1145    merge(
1146        &mut settings.extend_comment_on_newline,
1147        src.extend_comment_on_newline,
1148    );
1149    merge(&mut settings.inlay_hints, src.inlay_hints);
1150    merge(
1151        &mut settings.show_completions_on_input,
1152        src.show_completions_on_input,
1153    );
1154    merge(
1155        &mut settings.show_completion_documentation,
1156        src.show_completion_documentation,
1157    );
1158    merge(&mut settings.completions, src.completions);
1159}
1160
1161/// Allows to enable/disable formatting with Prettier
1162/// and configure default Prettier, used when no project-level Prettier installation is found.
1163/// Prettier formatting is disabled by default.
1164#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, SettingsUi)]
1165pub struct PrettierSettings {
1166    /// Enables or disables formatting with Prettier for a given language.
1167    #[serde(default)]
1168    pub allowed: bool,
1169
1170    /// Forces Prettier integration to use a specific parser name when formatting files with the language.
1171    #[serde(default)]
1172    pub parser: Option<String>,
1173
1174    /// Forces Prettier integration to use specific plugins when formatting files with the language.
1175    /// The default Prettier will be installed with these plugins.
1176    #[serde(default)]
1177    #[settings_ui(skip)]
1178    pub plugins: HashSet<String>,
1179
1180    /// Default Prettier options, in the format as in package.json section for Prettier.
1181    /// If project installs Prettier via its package.json, these options will be ignored.
1182    #[serde(flatten)]
1183    #[settings_ui(skip)]
1184    pub options: HashMap<String, serde_json::Value>,
1185}
1186
1187#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, SettingsUi)]
1188pub struct JsxTagAutoCloseSettings {
1189    /// Enables or disables auto-closing of JSX tags.
1190    #[serde(default)]
1191    pub enabled: bool,
1192}
1193
1194#[cfg(test)]
1195mod tests {
1196    use gpui::TestAppContext;
1197
1198    use super::*;
1199
1200    #[test]
1201    fn test_formatter_deserialization() {
1202        let raw_auto = "{\"formatter\": \"auto\"}";
1203        let settings: LanguageSettingsContent = serde_json::from_str(raw_auto).unwrap();
1204        assert_eq!(settings.formatter, Some(SelectedFormatter::Auto));
1205        let raw = "{\"formatter\": \"language_server\"}";
1206        let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1207        assert_eq!(
1208            settings.formatter,
1209            Some(SelectedFormatter::List(FormatterList::Single(
1210                Formatter::LanguageServer { name: None }
1211            )))
1212        );
1213        let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}]}";
1214        let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1215        assert_eq!(
1216            settings.formatter,
1217            Some(SelectedFormatter::List(FormatterList::Vec(vec![
1218                Formatter::LanguageServer { name: None }
1219            ])))
1220        );
1221        let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"prettier\"]}";
1222        let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1223        assert_eq!(
1224            settings.formatter,
1225            Some(SelectedFormatter::List(FormatterList::Vec(vec![
1226                Formatter::LanguageServer { name: None },
1227                Formatter::Prettier
1228            ])))
1229        );
1230    }
1231
1232    #[test]
1233    fn test_formatter_deserialization_invalid() {
1234        let raw_auto = "{\"formatter\": {}}";
1235        let result: Result<LanguageSettingsContent, _> = serde_json::from_str(raw_auto);
1236        assert!(result.is_err());
1237    }
1238
1239    #[gpui::test]
1240    fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
1241        use crate::TestFile;
1242        use std::path::PathBuf;
1243
1244        let cx = cx.app.borrow_mut();
1245
1246        let build_settings = |globs: &[&str]| -> EditPredictionSettings {
1247            EditPredictionSettings {
1248                disabled_globs: globs
1249                    .iter()
1250                    .map(|glob_str| {
1251                        #[cfg(windows)]
1252                        let glob_str = {
1253                            let mut g = String::new();
1254
1255                            if glob_str.starts_with('/') {
1256                                g.push_str("C:");
1257                            }
1258
1259                            g.push_str(&glob_str.replace('/', "\\"));
1260                            g
1261                        };
1262                        #[cfg(windows)]
1263                        let glob_str = glob_str.as_str();
1264                        let expanded_glob_str = shellexpand::tilde(glob_str).into_owned();
1265                        DisabledGlob {
1266                            matcher: globset::Glob::new(&expanded_glob_str)
1267                                .unwrap()
1268                                .compile_matcher(),
1269                            is_absolute: Path::new(&expanded_glob_str).is_absolute(),
1270                        }
1271                    })
1272                    .collect(),
1273                ..Default::default()
1274            }
1275        };
1276
1277        const WORKTREE_NAME: &str = "project";
1278        let make_test_file = |segments: &[&str]| -> Arc<dyn File> {
1279            let mut path_buf = PathBuf::new();
1280            path_buf.extend(segments);
1281
1282            Arc::new(TestFile {
1283                path: path_buf.as_path().into(),
1284                root_name: WORKTREE_NAME.to_string(),
1285                local_root: Some(PathBuf::from(if cfg!(windows) {
1286                    "C:\\absolute\\"
1287                } else {
1288                    "/absolute/"
1289                })),
1290            })
1291        };
1292
1293        let test_file = make_test_file(&["src", "test", "file.rs"]);
1294
1295        // Test relative globs
1296        let settings = build_settings(&["*.rs"]);
1297        assert!(!settings.enabled_for_file(&test_file, &cx));
1298        let settings = build_settings(&["*.txt"]);
1299        assert!(settings.enabled_for_file(&test_file, &cx));
1300
1301        // Test absolute globs
1302        let settings = build_settings(&["/absolute/**/*.rs"]);
1303        assert!(!settings.enabled_for_file(&test_file, &cx));
1304        let settings = build_settings(&["/other/**/*.rs"]);
1305        assert!(settings.enabled_for_file(&test_file, &cx));
1306
1307        // Test exact path match relative
1308        let settings = build_settings(&["src/test/file.rs"]);
1309        assert!(!settings.enabled_for_file(&test_file, &cx));
1310        let settings = build_settings(&["src/test/otherfile.rs"]);
1311        assert!(settings.enabled_for_file(&test_file, &cx));
1312
1313        // Test exact path match absolute
1314        let settings = build_settings(&[&format!("/absolute/{}/src/test/file.rs", WORKTREE_NAME)]);
1315        assert!(!settings.enabled_for_file(&test_file, &cx));
1316        let settings = build_settings(&["/other/test/otherfile.rs"]);
1317        assert!(settings.enabled_for_file(&test_file, &cx));
1318
1319        // Test * glob
1320        let settings = build_settings(&["*"]);
1321        assert!(!settings.enabled_for_file(&test_file, &cx));
1322        let settings = build_settings(&["*.txt"]);
1323        assert!(settings.enabled_for_file(&test_file, &cx));
1324
1325        // Test **/* glob
1326        let settings = build_settings(&["**/*"]);
1327        assert!(!settings.enabled_for_file(&test_file, &cx));
1328        let settings = build_settings(&["other/**/*"]);
1329        assert!(settings.enabled_for_file(&test_file, &cx));
1330
1331        // Test directory/** glob
1332        let settings = build_settings(&["src/**"]);
1333        assert!(!settings.enabled_for_file(&test_file, &cx));
1334
1335        let test_file_root: Arc<dyn File> = Arc::new(TestFile {
1336            path: PathBuf::from("file.rs").as_path().into(),
1337            root_name: WORKTREE_NAME.to_string(),
1338            local_root: Some(PathBuf::from("/absolute/")),
1339        });
1340        assert!(settings.enabled_for_file(&test_file_root, &cx));
1341
1342        let settings = build_settings(&["other/**"]);
1343        assert!(settings.enabled_for_file(&test_file, &cx));
1344
1345        // Test **/directory/* glob
1346        let settings = build_settings(&["**/test/*"]);
1347        assert!(!settings.enabled_for_file(&test_file, &cx));
1348        let settings = build_settings(&["**/other/*"]);
1349        assert!(settings.enabled_for_file(&test_file, &cx));
1350
1351        // Test multiple globs
1352        let settings = build_settings(&["*.rs", "*.txt", "src/**"]);
1353        assert!(!settings.enabled_for_file(&test_file, &cx));
1354        let settings = build_settings(&["*.txt", "*.md", "other/**"]);
1355        assert!(settings.enabled_for_file(&test_file, &cx));
1356
1357        // Test dot files
1358        let dot_file = make_test_file(&[".config", "settings.json"]);
1359        let settings = build_settings(&[".*/**"]);
1360        assert!(!settings.enabled_for_file(&dot_file, &cx));
1361
1362        let dot_env_file = make_test_file(&[".env"]);
1363        let settings = build_settings(&[".env"]);
1364        assert!(!settings.enabled_for_file(&dot_env_file, &cx));
1365
1366        // Test tilde expansion
1367        let home = shellexpand::tilde("~").into_owned();
1368        let home_file = make_test_file(&[&home, "test.rs"]);
1369        let settings = build_settings(&["~/test.rs"]);
1370        assert!(!settings.enabled_for_file(&home_file, &cx));
1371    }
1372
1373    #[test]
1374    fn test_resolve_language_servers() {
1375        fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {
1376            names
1377                .iter()
1378                .copied()
1379                .map(|name| LanguageServerName(name.to_string().into()))
1380                .collect::<Vec<_>>()
1381        }
1382
1383        let available_language_servers = language_server_names(&[
1384            "typescript-language-server",
1385            "biome",
1386            "deno",
1387            "eslint",
1388            "tailwind",
1389        ]);
1390
1391        // A value of just `["..."]` is the same as taking all of the available language servers.
1392        assert_eq!(
1393            LanguageSettings::resolve_language_servers(
1394                &[LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()],
1395                &available_language_servers,
1396            ),
1397            available_language_servers
1398        );
1399
1400        // Referencing one of the available language servers will change its order.
1401        assert_eq!(
1402            LanguageSettings::resolve_language_servers(
1403                &[
1404                    "biome".into(),
1405                    LanguageSettings::REST_OF_LANGUAGE_SERVERS.into(),
1406                    "deno".into()
1407                ],
1408                &available_language_servers
1409            ),
1410            language_server_names(&[
1411                "biome",
1412                "typescript-language-server",
1413                "eslint",
1414                "tailwind",
1415                "deno",
1416            ])
1417        );
1418
1419        // Negating an available language server removes it from the list.
1420        assert_eq!(
1421            LanguageSettings::resolve_language_servers(
1422                &[
1423                    "deno".into(),
1424                    "!typescript-language-server".into(),
1425                    "!biome".into(),
1426                    LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1427                ],
1428                &available_language_servers
1429            ),
1430            language_server_names(&["deno", "eslint", "tailwind"])
1431        );
1432
1433        // Adding a language server not in the list of available language servers adds it to the list.
1434        assert_eq!(
1435            LanguageSettings::resolve_language_servers(
1436                &[
1437                    "my-cool-language-server".into(),
1438                    LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1439                ],
1440                &available_language_servers
1441            ),
1442            language_server_names(&[
1443                "my-cool-language-server",
1444                "typescript-language-server",
1445                "biome",
1446                "deno",
1447                "eslint",
1448                "tailwind",
1449            ])
1450        );
1451    }
1452}