language_settings.rs

   1//! Provides `language`-related settings.
   2
   3use crate::{File, Language, LanguageName, LanguageServerName};
   4use collections::{FxHashMap, HashMap, HashSet};
   5use ec4rs::{
   6    Properties as EditorconfigProperties,
   7    property::{FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs},
   8};
   9use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
  10use gpui::{App, Modifiers};
  11use itertools::{Either, Itertools};
  12use schemars::json_schema;
  13
  14pub use settings::{
  15    CompletionSettingsContent, EditPredictionProvider, EditPredictionsMode, FormatOnSave,
  16    Formatter, FormatterList, InlayHintKind, LanguageSettingsContent, LspInsertMode,
  17    RewrapBehavior, SelectedFormatter, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode,
  18};
  19use settings::{
  20    IndentGuideSettingsContent, LanguageTaskSettingsContent, ParameterizedJsonSchema,
  21    PrettierSettingsContent, Settings, SettingsContent, SettingsLocation, SettingsStore,
  22    SettingsUi,
  23};
  24use shellexpand;
  25use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc};
  26use util::MergeFrom;
  27use util::{ResultExt, schemars::replace_subschema};
  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    pub(crate) file_globs: FxHashMap<Arc<str>, Vec<String>>,
  68}
  69
  70/// The settings for a particular language.
  71#[derive(Debug, Clone)]
  72pub struct LanguageSettings {
  73    /// How many columns a tab should occupy.
  74    pub tab_size: NonZeroU32,
  75    /// Whether to indent lines using tab characters, as opposed to multiple
  76    /// spaces.
  77    pub hard_tabs: bool,
  78    /// How to soft-wrap long lines of text.
  79    pub soft_wrap: settings::SoftWrap,
  80    /// The column at which to soft-wrap lines, for buffers where soft-wrap
  81    /// is enabled.
  82    pub preferred_line_length: u32,
  83    /// Whether to show wrap guides (vertical rulers) in the editor.
  84    /// Setting this to true will show a guide at the 'preferred_line_length' value
  85    /// if softwrap is set to 'preferred_line_length', and will show any
  86    /// additional guides as specified by the 'wrap_guides' setting.
  87    pub show_wrap_guides: bool,
  88    /// Character counts at which to show wrap guides (vertical rulers) in the editor.
  89    pub wrap_guides: Vec<usize>,
  90    /// Indent guide related settings.
  91    pub indent_guides: IndentGuideSettings,
  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: PrettierSettings,
 104    /// Whether to automatically close JSX tags.
 105    pub jsx_tag_auto_close: bool,
 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: settings::ShowWhitespaceSetting,
 128    /// Visible characters used to render whitespace when show_whitespaces is enabled.
 129    pub whitespace_map: settings::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: 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: LanguageTaskSettings,
 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
 165#[derive(Debug, Clone)]
 166pub struct CompletionSettings {
 167    /// Controls how words are completed.
 168    /// For large documents, not all words may be fetched for completion.
 169    ///
 170    /// Default: `fallback`
 171    pub words: WordsCompletionMode,
 172    /// How many characters has to be in the completions query to automatically show the words-based completions.
 173    /// Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command.
 174    ///
 175    /// Default: 3
 176    pub words_min_length: usize,
 177    /// Whether to fetch LSP completions or not.
 178    ///
 179    /// Default: true
 180    pub lsp: bool,
 181    /// When fetching LSP completions, determines how long to wait for a response of a particular server.
 182    /// When set to 0, waits indefinitely.
 183    ///
 184    /// Default: 0
 185    pub lsp_fetch_timeout_ms: u64,
 186    /// Controls how LSP completions are inserted.
 187    ///
 188    /// Default: "replace_suffix"
 189    pub lsp_insert_mode: LspInsertMode,
 190}
 191
 192impl CompletionSettings {
 193    pub fn merge_from(&mut self, src: &Option<CompletionSettingsContent>) {
 194        let Some(src) = src else { return };
 195        self.words.merge_from(&src.words);
 196        self.words_min_length.merge_from(&src.words_min_length);
 197        self.lsp.merge_from(&src.lsp);
 198        self.lsp_fetch_timeout_ms
 199            .merge_from(&src.lsp_fetch_timeout_ms);
 200        self.lsp_insert_mode.merge_from(&src.lsp_insert_mode);
 201    }
 202}
 203
 204/// The settings for indent guides.
 205#[derive(Debug, Clone, PartialEq)]
 206pub struct IndentGuideSettings {
 207    /// Whether to display indent guides in the editor.
 208    ///
 209    /// Default: true
 210    pub enabled: bool,
 211    /// The width of the indent guides in pixels, between 1 and 10.
 212    ///
 213    /// Default: 1
 214    pub line_width: u32,
 215    /// The width of the active indent guide in pixels, between 1 and 10.
 216    ///
 217    /// Default: 1
 218    pub active_line_width: u32,
 219    /// Determines how indent guides are colored.
 220    ///
 221    /// Default: Fixed
 222    pub coloring: settings::IndentGuideColoring,
 223    /// Determines how indent guide backgrounds are colored.
 224    ///
 225    /// Default: Disabled
 226    pub background_coloring: settings::IndentGuideBackgroundColoring,
 227}
 228
 229impl IndentGuideSettings {
 230    pub fn merge_from(&mut self, src: &Option<IndentGuideSettingsContent>) {
 231        let Some(src) = src else { return };
 232
 233        self.enabled.merge_from(&src.enabled);
 234        self.line_width.merge_from(&src.line_width);
 235        self.active_line_width.merge_from(&src.active_line_width);
 236        self.coloring.merge_from(&src.coloring);
 237        self.background_coloring
 238            .merge_from(&src.background_coloring);
 239    }
 240}
 241
 242#[derive(Debug, Clone)]
 243pub struct LanguageTaskSettings {
 244    /// Extra task variables to set for a particular language.
 245    pub variables: HashMap<String, String>,
 246    pub enabled: bool,
 247    /// Use LSP tasks over Zed language extension ones.
 248    /// If no LSP tasks are returned due to error/timeout or regular execution,
 249    /// Zed language extension tasks will be used instead.
 250    ///
 251    /// Other Zed tasks will still be shown:
 252    /// * Zed task from either of the task config file
 253    /// * Zed task from history (e.g. one-off task was spawned before)
 254    pub prefer_lsp: bool,
 255}
 256
 257impl LanguageTaskSettings {
 258    pub fn merge_from(&mut self, src: &Option<LanguageTaskSettingsContent>) {
 259        let Some(src) = src.clone() else {
 260            return;
 261        };
 262        self.variables.extend(src.variables);
 263        self.enabled.merge_from(&src.enabled);
 264        self.prefer_lsp.merge_from(&src.prefer_lsp);
 265    }
 266}
 267
 268/// Allows to enable/disable formatting with Prettier
 269/// and configure default Prettier, used when no project-level Prettier installation is found.
 270/// Prettier formatting is disabled by default.
 271#[derive(Debug, Clone)]
 272pub struct PrettierSettings {
 273    /// Enables or disables formatting with Prettier for a given language.
 274    pub allowed: bool,
 275
 276    /// Forces Prettier integration to use a specific parser name when formatting files with the language.
 277    pub parser: Option<String>,
 278
 279    /// Forces Prettier integration to use specific plugins when formatting files with the language.
 280    /// The default Prettier will be installed with these plugins.
 281    pub plugins: HashSet<String>,
 282
 283    /// Default Prettier options, in the format as in package.json section for Prettier.
 284    /// If project installs Prettier via its package.json, these options will be ignored.
 285    pub options: HashMap<String, serde_json::Value>,
 286}
 287
 288impl PrettierSettings {
 289    pub fn merge_from(&mut self, src: &Option<PrettierSettingsContent>) {
 290        let Some(src) = src.clone() else { return };
 291        self.allowed.merge_from(&src.allowed);
 292        self.parser = src.parser.clone();
 293        self.plugins.extend(src.plugins);
 294        self.options.extend(src.options);
 295    }
 296}
 297
 298impl LanguageSettings {
 299    /// A token representing the rest of the available language servers.
 300    const REST_OF_LANGUAGE_SERVERS: &'static str = "...";
 301
 302    /// Returns the customized list of language servers from the list of
 303    /// available language servers.
 304    pub fn customized_language_servers(
 305        &self,
 306        available_language_servers: &[LanguageServerName],
 307    ) -> Vec<LanguageServerName> {
 308        Self::resolve_language_servers(&self.language_servers, available_language_servers)
 309    }
 310
 311    pub(crate) fn resolve_language_servers(
 312        configured_language_servers: &[String],
 313        available_language_servers: &[LanguageServerName],
 314    ) -> Vec<LanguageServerName> {
 315        let (disabled_language_servers, enabled_language_servers): (
 316            Vec<LanguageServerName>,
 317            Vec<LanguageServerName>,
 318        ) = configured_language_servers.iter().partition_map(
 319            |language_server| match language_server.strip_prefix('!') {
 320                Some(disabled) => Either::Left(LanguageServerName(disabled.to_string().into())),
 321                None => Either::Right(LanguageServerName(language_server.clone().into())),
 322            },
 323        );
 324
 325        let rest = available_language_servers
 326            .iter()
 327            .filter(|&available_language_server| {
 328                !disabled_language_servers.contains(available_language_server)
 329                    && !enabled_language_servers.contains(available_language_server)
 330            })
 331            .cloned()
 332            .collect::<Vec<_>>();
 333
 334        enabled_language_servers
 335            .into_iter()
 336            .flat_map(|language_server| {
 337                if language_server.0.as_ref() == Self::REST_OF_LANGUAGE_SERVERS {
 338                    rest.clone()
 339                } else {
 340                    vec![language_server]
 341                }
 342            })
 343            .collect::<Vec<_>>()
 344    }
 345}
 346
 347// The settings for inlay hints.
 348#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 349pub struct InlayHintSettings {
 350    /// Global switch to toggle hints on and off.
 351    ///
 352    /// Default: false
 353    pub enabled: bool,
 354    /// Global switch to toggle inline values on and off when debugging.
 355    ///
 356    /// Default: true
 357    pub show_value_hints: bool,
 358    /// Whether type hints should be shown.
 359    ///
 360    /// Default: true
 361    pub show_type_hints: bool,
 362    /// Whether parameter hints should be shown.
 363    ///
 364    /// Default: true
 365    pub show_parameter_hints: bool,
 366    /// Whether other hints should be shown.
 367    ///
 368    /// Default: true
 369    pub show_other_hints: bool,
 370    /// Whether to show a background for inlay hints.
 371    ///
 372    /// If set to `true`, the background will use the `hint.background` color
 373    /// from the current theme.
 374    ///
 375    /// Default: false
 376    pub show_background: bool,
 377    /// Whether or not to debounce inlay hints updates after buffer edits.
 378    ///
 379    /// Set to 0 to disable debouncing.
 380    ///
 381    /// Default: 700
 382    pub edit_debounce_ms: u64,
 383    /// Whether or not to debounce inlay hints updates after buffer scrolls.
 384    ///
 385    /// Set to 0 to disable debouncing.
 386    ///
 387    /// Default: 50
 388    pub scroll_debounce_ms: u64,
 389    /// Toggles inlay hints (hides or shows) when the user presses the modifiers specified.
 390    /// If only a subset of the modifiers specified is pressed, hints are not toggled.
 391    /// If no modifiers are specified, this is equivalent to `None`.
 392    ///
 393    /// Default: None
 394    pub toggle_on_modifiers_press: Option<Modifiers>,
 395}
 396
 397impl InlayHintSettings {
 398    /// Returns the kinds of inlay hints that are enabled based on the settings.
 399    pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
 400        let mut kinds = HashSet::default();
 401        if self.show_type_hints {
 402            kinds.insert(Some(InlayHintKind::Type));
 403        }
 404        if self.show_parameter_hints {
 405            kinds.insert(Some(InlayHintKind::Parameter));
 406        }
 407        if self.show_other_hints {
 408            kinds.insert(None);
 409        }
 410        kinds
 411    }
 412}
 413
 414/// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot)
 415/// or [Supermaven](https://supermaven.com).
 416#[derive(Clone, Debug, Default, SettingsUi)]
 417pub struct EditPredictionSettings {
 418    /// The provider that supplies edit predictions.
 419    pub provider: settings::EditPredictionProvider,
 420    /// A list of globs representing files that edit predictions should be disabled for.
 421    /// This list adds to a pre-existing, sensible default set of globs.
 422    /// Any additional ones you add are combined with them.
 423    #[settings_ui(skip)]
 424    pub disabled_globs: Vec<DisabledGlob>,
 425    /// Configures how edit predictions are displayed in the buffer.
 426    pub mode: settings::EditPredictionsMode,
 427    /// Settings specific to GitHub Copilot.
 428    pub copilot: CopilotSettings,
 429    /// Whether edit predictions are enabled in the assistant panel.
 430    /// This setting has no effect if globally disabled.
 431    pub enabled_in_text_threads: bool,
 432}
 433
 434impl EditPredictionSettings {
 435    /// Returns whether edit predictions are enabled for the given path.
 436    pub fn enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
 437        !self.disabled_globs.iter().any(|glob| {
 438            if glob.is_absolute {
 439                file.as_local()
 440                    .is_some_and(|local| glob.matcher.is_match(local.abs_path(cx)))
 441            } else {
 442                glob.matcher.is_match(file.path())
 443            }
 444        })
 445    }
 446}
 447
 448#[derive(Clone, Debug)]
 449pub struct DisabledGlob {
 450    matcher: GlobMatcher,
 451    is_absolute: bool,
 452}
 453
 454#[derive(Clone, Debug, Default, SettingsUi)]
 455pub struct CopilotSettings {
 456    /// HTTP/HTTPS proxy to use for Copilot.
 457    #[settings_ui(skip)]
 458    pub proxy: Option<String>,
 459    /// Disable certificate verification for proxy (not recommended).
 460    pub proxy_no_verify: Option<bool>,
 461    /// Enterprise URI for Copilot.
 462    #[settings_ui(skip)]
 463    pub enterprise_uri: Option<String>,
 464}
 465
 466inventory::submit! {
 467    ParameterizedJsonSchema {
 468        add_and_get_ref: |generator, params, _cx| {
 469            let language_settings_content_ref = generator
 470                .subschema_for::<LanguageSettingsContent>()
 471                .to_value();
 472            replace_subschema::<settings::LanguageToSettingsMap>(generator, || json_schema!({
 473                "type": "object",
 474                "properties": params
 475                    .language_names
 476                    .iter()
 477                    .map(|name| {
 478                        (
 479                            name.clone(),
 480                            language_settings_content_ref.clone(),
 481                        )
 482                    })
 483                    .collect::<serde_json::Map<_, _>>()
 484            }))
 485        }
 486    }
 487}
 488
 489impl AllLanguageSettings {
 490    /// Returns the [`LanguageSettings`] for the language with the specified name.
 491    pub fn language<'a>(
 492        &'a self,
 493        location: Option<SettingsLocation<'a>>,
 494        language_name: Option<&LanguageName>,
 495        cx: &'a App,
 496    ) -> Cow<'a, LanguageSettings> {
 497        let settings = language_name
 498            .and_then(|name| self.languages.get(name))
 499            .unwrap_or(&self.defaults);
 500
 501        let editorconfig_properties = location.and_then(|location| {
 502            cx.global::<SettingsStore>()
 503                .editorconfig_properties(location.worktree_id, location.path)
 504        });
 505        if let Some(editorconfig_properties) = editorconfig_properties {
 506            let mut settings = settings.clone();
 507            merge_with_editorconfig(&mut settings, &editorconfig_properties);
 508            Cow::Owned(settings)
 509        } else {
 510            Cow::Borrowed(settings)
 511        }
 512    }
 513
 514    /// Returns whether edit predictions are enabled for the given path.
 515    pub fn edit_predictions_enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
 516        self.edit_predictions.enabled_for_file(file, cx)
 517    }
 518
 519    /// Returns whether edit predictions are enabled for the given language and path.
 520    pub fn show_edit_predictions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
 521        self.language(None, language.map(|l| l.name()).as_ref(), cx)
 522            .show_edit_predictions
 523    }
 524
 525    /// Returns the edit predictions preview mode for the given language and path.
 526    pub fn edit_predictions_mode(&self) -> EditPredictionsMode {
 527        self.edit_predictions.mode
 528    }
 529}
 530
 531fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
 532    let preferred_line_length = cfg.get::<MaxLineLen>().ok().and_then(|v| match v {
 533        MaxLineLen::Value(u) => Some(u as u32),
 534        MaxLineLen::Off => None,
 535    });
 536    let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
 537        IndentSize::Value(u) => NonZeroU32::new(u as u32),
 538        IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
 539            TabWidth::Value(u) => NonZeroU32::new(u as u32),
 540        }),
 541    });
 542    let hard_tabs = cfg
 543        .get::<IndentStyle>()
 544        .map(|v| v.eq(&IndentStyle::Tabs))
 545        .ok();
 546    let ensure_final_newline_on_save = cfg
 547        .get::<FinalNewline>()
 548        .map(|v| match v {
 549            FinalNewline::Value(b) => b,
 550        })
 551        .ok();
 552    let remove_trailing_whitespace_on_save = cfg
 553        .get::<TrimTrailingWs>()
 554        .map(|v| match v {
 555            TrimTrailingWs::Value(b) => b,
 556        })
 557        .ok();
 558    fn merge<T>(target: &mut T, value: Option<T>) {
 559        if let Some(value) = value {
 560            *target = value;
 561        }
 562    }
 563    merge(&mut settings.preferred_line_length, preferred_line_length);
 564    merge(&mut settings.tab_size, tab_size);
 565    merge(&mut settings.hard_tabs, hard_tabs);
 566    merge(
 567        &mut settings.remove_trailing_whitespace_on_save,
 568        remove_trailing_whitespace_on_save,
 569    );
 570    merge(
 571        &mut settings.ensure_final_newline_on_save,
 572        ensure_final_newline_on_save,
 573    );
 574}
 575
 576impl settings::Settings for AllLanguageSettings {
 577    fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
 578        let all_languages = &content.project.all_languages;
 579        let defaults = all_languages.defaults.clone();
 580        let inlay_hints = defaults.inlay_hints.unwrap();
 581        let completions = defaults.completions.unwrap();
 582        let prettier = defaults.prettier.unwrap();
 583        let indent_guides = defaults.indent_guides.unwrap();
 584        let tasks = defaults.tasks.unwrap();
 585
 586        let default_language_settings = LanguageSettings {
 587            tab_size: defaults.tab_size.unwrap(),
 588            hard_tabs: defaults.hard_tabs.unwrap(),
 589            soft_wrap: defaults.soft_wrap.unwrap(),
 590            preferred_line_length: defaults.preferred_line_length.unwrap(),
 591            show_wrap_guides: defaults.show_wrap_guides.unwrap(),
 592            wrap_guides: defaults.wrap_guides.unwrap(),
 593            indent_guides: IndentGuideSettings {
 594                enabled: indent_guides.enabled.unwrap(),
 595                line_width: indent_guides.line_width.unwrap(),
 596                active_line_width: indent_guides.active_line_width.unwrap(),
 597                coloring: indent_guides.coloring.unwrap(),
 598                background_coloring: indent_guides.background_coloring.unwrap(),
 599            },
 600            format_on_save: defaults.format_on_save.unwrap(),
 601            remove_trailing_whitespace_on_save: defaults
 602                .remove_trailing_whitespace_on_save
 603                .unwrap(),
 604            ensure_final_newline_on_save: defaults.ensure_final_newline_on_save.unwrap(),
 605            formatter: defaults.formatter.unwrap(),
 606            prettier: PrettierSettings {
 607                allowed: prettier.allowed.unwrap(),
 608                parser: prettier.parser,
 609                plugins: prettier.plugins,
 610                options: prettier.options,
 611            },
 612            jsx_tag_auto_close: defaults.jsx_tag_auto_close.unwrap().enabled.unwrap(),
 613            enable_language_server: defaults.enable_language_server.unwrap(),
 614            language_servers: defaults.language_servers.unwrap(),
 615            allow_rewrap: defaults.allow_rewrap.unwrap(),
 616            show_edit_predictions: defaults.show_edit_predictions.unwrap(),
 617            edit_predictions_disabled_in: defaults.edit_predictions_disabled_in.unwrap(),
 618            show_whitespaces: defaults.show_whitespaces.unwrap(),
 619            whitespace_map: defaults.whitespace_map.unwrap(),
 620            extend_comment_on_newline: defaults.extend_comment_on_newline.unwrap(),
 621            inlay_hints: InlayHintSettings {
 622                enabled: inlay_hints.enabled.unwrap(),
 623                show_value_hints: inlay_hints.show_value_hints.unwrap(),
 624                show_type_hints: inlay_hints.show_type_hints.unwrap(),
 625                show_parameter_hints: inlay_hints.show_parameter_hints.unwrap(),
 626                show_other_hints: inlay_hints.show_other_hints.unwrap(),
 627                show_background: inlay_hints.show_background.unwrap(),
 628                edit_debounce_ms: inlay_hints.edit_debounce_ms.unwrap(),
 629                scroll_debounce_ms: inlay_hints.scroll_debounce_ms.unwrap(),
 630                toggle_on_modifiers_press: inlay_hints.toggle_on_modifiers_press,
 631            },
 632            use_autoclose: defaults.use_autoclose.unwrap(),
 633            use_auto_surround: defaults.use_auto_surround.unwrap(),
 634            use_on_type_format: defaults.use_on_type_format.unwrap(),
 635            auto_indent: defaults.auto_indent.unwrap(),
 636            auto_indent_on_paste: defaults.auto_indent_on_paste.unwrap(),
 637            always_treat_brackets_as_autoclosed: defaults
 638                .always_treat_brackets_as_autoclosed
 639                .unwrap(),
 640            code_actions_on_format: defaults.code_actions_on_format.unwrap(),
 641            linked_edits: defaults.linked_edits.unwrap(),
 642            tasks: LanguageTaskSettings {
 643                variables: tasks.variables,
 644                enabled: tasks.enabled.unwrap(),
 645                prefer_lsp: tasks.prefer_lsp.unwrap(),
 646            },
 647            show_completions_on_input: defaults.show_completions_on_input.unwrap(),
 648            show_completion_documentation: defaults.show_completion_documentation.unwrap(),
 649            completions: CompletionSettings {
 650                words: completions.words.unwrap(),
 651                words_min_length: completions.words_min_length.unwrap(),
 652                lsp: completions.lsp.unwrap(),
 653                lsp_fetch_timeout_ms: completions.lsp_fetch_timeout_ms.unwrap(),
 654                lsp_insert_mode: completions.lsp_insert_mode.unwrap(),
 655            },
 656            debuggers: defaults.debuggers.unwrap(),
 657        };
 658
 659        let mut languages = HashMap::default();
 660        for (language_name, settings) in &all_languages.languages.0 {
 661            let mut language_settings = default_language_settings.clone();
 662            merge_settings(&mut language_settings, settings);
 663            languages.insert(LanguageName(language_name.clone()), language_settings);
 664        }
 665
 666        let edit_prediction_provider = all_languages
 667            .features
 668            .as_ref()
 669            .and_then(|f| f.edit_prediction_provider);
 670
 671        let edit_predictions = all_languages.edit_predictions.clone().unwrap();
 672        let edit_predictions_mode = edit_predictions.mode.unwrap();
 673
 674        let disabled_globs: HashSet<&String> = edit_predictions
 675            .disabled_globs
 676            .as_ref()
 677            .unwrap()
 678            .iter()
 679            .collect();
 680
 681        let copilot = edit_predictions.copilot.unwrap();
 682        let copilot_settings = CopilotSettings {
 683            proxy: copilot.proxy,
 684            proxy_no_verify: copilot.proxy_no_verify,
 685            enterprise_uri: copilot.enterprise_uri,
 686        };
 687
 688        let enabled_in_text_threads = edit_predictions.enabled_in_text_threads.unwrap();
 689
 690        let mut file_types: FxHashMap<Arc<str>, GlobSet> = FxHashMap::default();
 691        let mut file_globs: FxHashMap<Arc<str>, Vec<String>> = FxHashMap::default();
 692
 693        for (language, patterns) in &all_languages.file_types {
 694            let mut builder = GlobSetBuilder::new();
 695
 696            for pattern in patterns {
 697                builder.add(Glob::new(pattern).unwrap());
 698            }
 699
 700            file_types.insert(language.clone(), builder.build().unwrap());
 701            file_globs.insert(language.clone(), patterns.clone());
 702        }
 703
 704        Self {
 705            edit_predictions: EditPredictionSettings {
 706                provider: if let Some(provider) = edit_prediction_provider {
 707                    provider
 708                } else {
 709                    EditPredictionProvider::None
 710                },
 711                disabled_globs: disabled_globs
 712                    .iter()
 713                    .filter_map(|g| {
 714                        let expanded_g = shellexpand::tilde(g).into_owned();
 715                        Some(DisabledGlob {
 716                            matcher: globset::Glob::new(&expanded_g).ok()?.compile_matcher(),
 717                            is_absolute: Path::new(&expanded_g).is_absolute(),
 718                        })
 719                    })
 720                    .collect(),
 721                mode: edit_predictions_mode,
 722                copilot: copilot_settings,
 723                enabled_in_text_threads,
 724            },
 725            defaults: default_language_settings,
 726            languages,
 727            file_types,
 728            file_globs,
 729        }
 730    }
 731
 732    fn refine(&mut self, content: &SettingsContent, _cx: &mut App) {
 733        let all_languages = &content.project.all_languages;
 734        if let Some(provider) = all_languages
 735            .features
 736            .as_ref()
 737            .and_then(|f| f.edit_prediction_provider)
 738        {
 739            self.edit_predictions.provider = provider;
 740        }
 741
 742        if let Some(edit_predictions) = all_languages.edit_predictions.as_ref() {
 743            self.edit_predictions
 744                .mode
 745                .merge_from(&edit_predictions.mode);
 746            self.edit_predictions
 747                .enabled_in_text_threads
 748                .merge_from(&edit_predictions.enabled_in_text_threads);
 749
 750            if let Some(disabled_globs) = edit_predictions.disabled_globs.as_ref() {
 751                self.edit_predictions
 752                    .disabled_globs
 753                    .extend(disabled_globs.iter().filter_map(|g| {
 754                        let expanded_g = shellexpand::tilde(g).into_owned();
 755                        Some(DisabledGlob {
 756                            matcher: globset::Glob::new(&expanded_g).ok()?.compile_matcher(),
 757                            is_absolute: Path::new(&expanded_g).is_absolute(),
 758                        })
 759                    }));
 760            }
 761        }
 762
 763        if let Some(proxy) = all_languages
 764            .edit_predictions
 765            .as_ref()
 766            .and_then(|settings| settings.copilot.as_ref()?.proxy.clone())
 767        {
 768            self.edit_predictions.copilot.proxy = Some(proxy);
 769        }
 770
 771        if let Some(proxy_no_verify) = all_languages
 772            .edit_predictions
 773            .as_ref()
 774            .and_then(|settings| settings.copilot.as_ref()?.proxy_no_verify)
 775        {
 776            self.edit_predictions.copilot.proxy_no_verify = Some(proxy_no_verify);
 777        }
 778
 779        if let Some(enterprise_uri) = all_languages
 780            .edit_predictions
 781            .as_ref()
 782            .and_then(|settings| settings.copilot.as_ref()?.enterprise_uri.clone())
 783        {
 784            self.edit_predictions.copilot.enterprise_uri = Some(enterprise_uri);
 785        }
 786
 787        // A user's global settings override the default global settings and
 788        // all default language-specific settings.
 789        merge_settings(&mut self.defaults, &all_languages.defaults);
 790        for language_settings in self.languages.values_mut() {
 791            merge_settings(language_settings, &all_languages.defaults);
 792        }
 793
 794        // A user's language-specific settings override default language-specific settings.
 795        for (language_name, user_language_settings) in &all_languages.languages.0 {
 796            merge_settings(
 797                self.languages
 798                    .entry(LanguageName(language_name.clone()))
 799                    .or_insert_with(|| self.defaults.clone()),
 800                user_language_settings,
 801            );
 802        }
 803
 804        for (language, patterns) in &all_languages.file_types {
 805            let mut builder = GlobSetBuilder::new();
 806
 807            let default_value = self.file_globs.get(&language.clone());
 808
 809            // Merge the default value with the user's value.
 810            if let Some(patterns) = default_value {
 811                for pattern in patterns {
 812                    if let Some(glob) = Glob::new(pattern).log_err() {
 813                        builder.add(glob);
 814                    }
 815                }
 816            }
 817
 818            for pattern in patterns {
 819                if let Some(glob) = Glob::new(pattern).log_err() {
 820                    builder.add(glob);
 821                }
 822            }
 823
 824            self.file_globs
 825                .entry(language.clone())
 826                .or_default()
 827                .extend(patterns.clone());
 828
 829            if let Some(matcher) = builder.build().log_err() {
 830                self.file_types.insert(language.clone(), matcher);
 831            }
 832        }
 833    }
 834
 835    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
 836        let d = &mut current.project.all_languages.defaults;
 837        if let Some(size) = vscode
 838            .read_value("editor.tabSize")
 839            .and_then(|v| v.as_u64())
 840            .and_then(|n| NonZeroU32::new(n as u32))
 841        {
 842            d.tab_size = Some(size);
 843        }
 844        if let Some(v) = vscode.read_bool("editor.insertSpaces") {
 845            d.hard_tabs = Some(!v);
 846        }
 847
 848        vscode.enum_setting("editor.wordWrap", &mut d.soft_wrap, |s| match s {
 849            "on" => Some(SoftWrap::EditorWidth),
 850            "wordWrapColumn" => Some(SoftWrap::PreferLine),
 851            "bounded" => Some(SoftWrap::Bounded),
 852            "off" => Some(SoftWrap::None),
 853            _ => None,
 854        });
 855        vscode.u32_setting("editor.wordWrapColumn", &mut d.preferred_line_length);
 856
 857        if let Some(arr) = vscode
 858            .read_value("editor.rulers")
 859            .and_then(|v| v.as_array())
 860            .map(|v| v.iter().map(|n| n.as_u64().map(|n| n as usize)).collect())
 861        {
 862            d.wrap_guides = arr;
 863        }
 864        if let Some(b) = vscode.read_bool("editor.guides.indentation") {
 865            d.indent_guides.get_or_insert_default().enabled = Some(b);
 866        }
 867
 868        if let Some(b) = vscode.read_bool("editor.guides.formatOnSave") {
 869            d.format_on_save = Some(if b {
 870                FormatOnSave::On
 871            } else {
 872                FormatOnSave::Off
 873            });
 874        }
 875        vscode.bool_setting(
 876            "editor.trimAutoWhitespace",
 877            &mut d.remove_trailing_whitespace_on_save,
 878        );
 879        vscode.bool_setting(
 880            "files.insertFinalNewline",
 881            &mut d.ensure_final_newline_on_save,
 882        );
 883        vscode.bool_setting("editor.inlineSuggest.enabled", &mut d.show_edit_predictions);
 884        vscode.enum_setting("editor.renderWhitespace", &mut d.show_whitespaces, |s| {
 885            Some(match s {
 886                "boundary" => ShowWhitespaceSetting::Boundary,
 887                "trailing" => ShowWhitespaceSetting::Trailing,
 888                "selection" => ShowWhitespaceSetting::Selection,
 889                "all" => ShowWhitespaceSetting::All,
 890                _ => ShowWhitespaceSetting::None,
 891            })
 892        });
 893        vscode.enum_setting(
 894            "editor.autoSurround",
 895            &mut d.use_auto_surround,
 896            |s| match s {
 897                "languageDefined" | "quotes" | "brackets" => Some(true),
 898                "never" => Some(false),
 899                _ => None,
 900            },
 901        );
 902        vscode.bool_setting("editor.formatOnType", &mut d.use_on_type_format);
 903        vscode.bool_setting("editor.linkedEditing", &mut d.linked_edits);
 904        vscode.bool_setting("editor.formatOnPaste", &mut d.auto_indent_on_paste);
 905        vscode.bool_setting(
 906            "editor.suggestOnTriggerCharacters",
 907            &mut d.show_completions_on_input,
 908        );
 909        if let Some(b) = vscode.read_bool("editor.suggest.showWords") {
 910            let mode = if b {
 911                WordsCompletionMode::Enabled
 912            } else {
 913                WordsCompletionMode::Disabled
 914            };
 915            d.completions.get_or_insert_default().words = Some(mode);
 916        }
 917        // TODO: pull ^ out into helper and reuse for per-language settings
 918
 919        // vscodes file association map is inverted from ours, so we flip the mapping before merging
 920        let mut associations: HashMap<Arc<str>, Vec<String>> = HashMap::default();
 921        if let Some(map) = vscode
 922            .read_value("files.associations")
 923            .and_then(|v| v.as_object())
 924        {
 925            for (k, v) in map {
 926                let Some(v) = v.as_str() else { continue };
 927                associations.entry(v.into()).or_default().push(k.clone());
 928            }
 929        }
 930
 931        // TODO: do we want to merge imported globs per filetype? for now we'll just replace
 932        current
 933            .project
 934            .all_languages
 935            .file_types
 936            .extend(associations);
 937
 938        // cursor global ignore list applies to cursor-tab, so transfer it to edit_predictions.disabled_globs
 939        if let Some(disabled_globs) = vscode
 940            .read_value("cursor.general.globalCursorIgnoreList")
 941            .and_then(|v| v.as_array())
 942        {
 943            current
 944                .project
 945                .all_languages
 946                .edit_predictions
 947                .get_or_insert_default()
 948                .disabled_globs
 949                .get_or_insert_default()
 950                .extend(
 951                    disabled_globs
 952                        .iter()
 953                        .filter_map(|glob| glob.as_str())
 954                        .map(|s| s.to_string()),
 955                );
 956        }
 957    }
 958}
 959
 960fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
 961    settings.tab_size.merge_from(&src.tab_size);
 962    settings.tab_size = settings
 963        .tab_size
 964        .clamp(NonZeroU32::new(1).unwrap(), NonZeroU32::new(16).unwrap());
 965
 966    settings.hard_tabs.merge_from(&src.hard_tabs);
 967    settings.soft_wrap.merge_from(&src.soft_wrap);
 968    settings.use_autoclose.merge_from(&src.use_autoclose);
 969    settings
 970        .use_auto_surround
 971        .merge_from(&src.use_auto_surround);
 972    settings
 973        .use_on_type_format
 974        .merge_from(&src.use_on_type_format);
 975    settings.auto_indent.merge_from(&src.auto_indent);
 976    settings
 977        .auto_indent_on_paste
 978        .merge_from(&src.auto_indent_on_paste);
 979    settings
 980        .always_treat_brackets_as_autoclosed
 981        .merge_from(&src.always_treat_brackets_as_autoclosed);
 982    settings.show_wrap_guides.merge_from(&src.show_wrap_guides);
 983    settings.wrap_guides.merge_from(&src.wrap_guides);
 984    settings.indent_guides.merge_from(&src.indent_guides);
 985    settings
 986        .code_actions_on_format
 987        .merge_from(&src.code_actions_on_format.clone());
 988    settings.linked_edits.merge_from(&src.linked_edits);
 989    settings.tasks.merge_from(&src.tasks);
 990
 991    settings
 992        .preferred_line_length
 993        .merge_from(&src.preferred_line_length);
 994    settings.formatter.merge_from(&src.formatter.clone());
 995    settings.prettier.merge_from(&src.prettier.clone());
 996    settings
 997        .jsx_tag_auto_close
 998        .merge_from(&src.jsx_tag_auto_close.as_ref().and_then(|v| v.enabled));
 999    settings
1000        .format_on_save
1001        .merge_from(&src.format_on_save.clone());
1002    settings
1003        .remove_trailing_whitespace_on_save
1004        .merge_from(&src.remove_trailing_whitespace_on_save);
1005    settings
1006        .ensure_final_newline_on_save
1007        .merge_from(&src.ensure_final_newline_on_save);
1008    settings
1009        .enable_language_server
1010        .merge_from(&src.enable_language_server);
1011    settings
1012        .language_servers
1013        .merge_from(&src.language_servers.clone());
1014    settings.allow_rewrap.merge_from(&src.allow_rewrap);
1015    settings
1016        .show_edit_predictions
1017        .merge_from(&src.show_edit_predictions);
1018    settings
1019        .edit_predictions_disabled_in
1020        .merge_from(&src.edit_predictions_disabled_in.clone());
1021    settings.show_whitespaces.merge_from(&src.show_whitespaces);
1022    settings
1023        .whitespace_map
1024        .merge_from(&src.whitespace_map.clone());
1025    settings
1026        .extend_comment_on_newline
1027        .merge_from(&src.extend_comment_on_newline);
1028    if let Some(inlay_hints) = &src.inlay_hints {
1029        settings
1030            .inlay_hints
1031            .enabled
1032            .merge_from(&inlay_hints.enabled);
1033        settings
1034            .inlay_hints
1035            .show_value_hints
1036            .merge_from(&inlay_hints.show_value_hints);
1037        settings
1038            .inlay_hints
1039            .show_type_hints
1040            .merge_from(&inlay_hints.show_type_hints);
1041        settings
1042            .inlay_hints
1043            .show_parameter_hints
1044            .merge_from(&inlay_hints.show_parameter_hints);
1045        settings
1046            .inlay_hints
1047            .show_other_hints
1048            .merge_from(&inlay_hints.show_other_hints);
1049        settings
1050            .inlay_hints
1051            .show_background
1052            .merge_from(&inlay_hints.show_background);
1053        settings
1054            .inlay_hints
1055            .edit_debounce_ms
1056            .merge_from(&inlay_hints.edit_debounce_ms);
1057        settings
1058            .inlay_hints
1059            .scroll_debounce_ms
1060            .merge_from(&inlay_hints.scroll_debounce_ms);
1061        if let Some(toggle_on_modifiers_press) = &inlay_hints.toggle_on_modifiers_press {
1062            settings.inlay_hints.toggle_on_modifiers_press = Some(*toggle_on_modifiers_press);
1063        }
1064    }
1065    settings
1066        .show_completions_on_input
1067        .merge_from(&src.show_completions_on_input);
1068    settings
1069        .show_completion_documentation
1070        .merge_from(&src.show_completion_documentation);
1071    settings.completions.merge_from(&src.completions);
1072}
1073
1074#[derive(Default, Debug, Clone, PartialEq, Eq, SettingsUi)]
1075pub struct JsxTagAutoCloseSettings {
1076    /// Enables or disables auto-closing of JSX tags.
1077    pub enabled: bool,
1078}
1079
1080#[cfg(test)]
1081mod tests {
1082    use super::*;
1083    use gpui::TestAppContext;
1084
1085    #[gpui::test]
1086    fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
1087        use crate::TestFile;
1088        use std::path::PathBuf;
1089
1090        let cx = cx.app.borrow_mut();
1091
1092        let build_settings = |globs: &[&str]| -> EditPredictionSettings {
1093            EditPredictionSettings {
1094                disabled_globs: globs
1095                    .iter()
1096                    .map(|glob_str| {
1097                        #[cfg(windows)]
1098                        let glob_str = {
1099                            let mut g = String::new();
1100
1101                            if glob_str.starts_with('/') {
1102                                g.push_str("C:");
1103                            }
1104
1105                            g.push_str(&glob_str.replace('/', "\\"));
1106                            g
1107                        };
1108                        #[cfg(windows)]
1109                        let glob_str = glob_str.as_str();
1110                        let expanded_glob_str = shellexpand::tilde(glob_str).into_owned();
1111                        DisabledGlob {
1112                            matcher: globset::Glob::new(&expanded_glob_str)
1113                                .unwrap()
1114                                .compile_matcher(),
1115                            is_absolute: Path::new(&expanded_glob_str).is_absolute(),
1116                        }
1117                    })
1118                    .collect(),
1119                ..Default::default()
1120            }
1121        };
1122
1123        const WORKTREE_NAME: &str = "project";
1124        let make_test_file = |segments: &[&str]| -> Arc<dyn File> {
1125            let mut path_buf = PathBuf::new();
1126            path_buf.extend(segments);
1127
1128            Arc::new(TestFile {
1129                path: path_buf.as_path().into(),
1130                root_name: WORKTREE_NAME.to_string(),
1131                local_root: Some(PathBuf::from(if cfg!(windows) {
1132                    "C:\\absolute\\"
1133                } else {
1134                    "/absolute/"
1135                })),
1136            })
1137        };
1138
1139        let test_file = make_test_file(&["src", "test", "file.rs"]);
1140
1141        // Test relative globs
1142        let settings = build_settings(&["*.rs"]);
1143        assert!(!settings.enabled_for_file(&test_file, &cx));
1144        let settings = build_settings(&["*.txt"]);
1145        assert!(settings.enabled_for_file(&test_file, &cx));
1146
1147        // Test absolute globs
1148        let settings = build_settings(&["/absolute/**/*.rs"]);
1149        assert!(!settings.enabled_for_file(&test_file, &cx));
1150        let settings = build_settings(&["/other/**/*.rs"]);
1151        assert!(settings.enabled_for_file(&test_file, &cx));
1152
1153        // Test exact path match relative
1154        let settings = build_settings(&["src/test/file.rs"]);
1155        assert!(!settings.enabled_for_file(&test_file, &cx));
1156        let settings = build_settings(&["src/test/otherfile.rs"]);
1157        assert!(settings.enabled_for_file(&test_file, &cx));
1158
1159        // Test exact path match absolute
1160        let settings = build_settings(&[&format!("/absolute/{}/src/test/file.rs", WORKTREE_NAME)]);
1161        assert!(!settings.enabled_for_file(&test_file, &cx));
1162        let settings = build_settings(&["/other/test/otherfile.rs"]);
1163        assert!(settings.enabled_for_file(&test_file, &cx));
1164
1165        // Test * glob
1166        let settings = build_settings(&["*"]);
1167        assert!(!settings.enabled_for_file(&test_file, &cx));
1168        let settings = build_settings(&["*.txt"]);
1169        assert!(settings.enabled_for_file(&test_file, &cx));
1170
1171        // Test **/* glob
1172        let settings = build_settings(&["**/*"]);
1173        assert!(!settings.enabled_for_file(&test_file, &cx));
1174        let settings = build_settings(&["other/**/*"]);
1175        assert!(settings.enabled_for_file(&test_file, &cx));
1176
1177        // Test directory/** glob
1178        let settings = build_settings(&["src/**"]);
1179        assert!(!settings.enabled_for_file(&test_file, &cx));
1180
1181        let test_file_root: Arc<dyn File> = Arc::new(TestFile {
1182            path: PathBuf::from("file.rs").as_path().into(),
1183            root_name: WORKTREE_NAME.to_string(),
1184            local_root: Some(PathBuf::from("/absolute/")),
1185        });
1186        assert!(settings.enabled_for_file(&test_file_root, &cx));
1187
1188        let settings = build_settings(&["other/**"]);
1189        assert!(settings.enabled_for_file(&test_file, &cx));
1190
1191        // Test **/directory/* glob
1192        let settings = build_settings(&["**/test/*"]);
1193        assert!(!settings.enabled_for_file(&test_file, &cx));
1194        let settings = build_settings(&["**/other/*"]);
1195        assert!(settings.enabled_for_file(&test_file, &cx));
1196
1197        // Test multiple globs
1198        let settings = build_settings(&["*.rs", "*.txt", "src/**"]);
1199        assert!(!settings.enabled_for_file(&test_file, &cx));
1200        let settings = build_settings(&["*.txt", "*.md", "other/**"]);
1201        assert!(settings.enabled_for_file(&test_file, &cx));
1202
1203        // Test dot files
1204        let dot_file = make_test_file(&[".config", "settings.json"]);
1205        let settings = build_settings(&[".*/**"]);
1206        assert!(!settings.enabled_for_file(&dot_file, &cx));
1207
1208        let dot_env_file = make_test_file(&[".env"]);
1209        let settings = build_settings(&[".env"]);
1210        assert!(!settings.enabled_for_file(&dot_env_file, &cx));
1211
1212        // Test tilde expansion
1213        let home = shellexpand::tilde("~").into_owned();
1214        let home_file = make_test_file(&[&home, "test.rs"]);
1215        let settings = build_settings(&["~/test.rs"]);
1216        assert!(!settings.enabled_for_file(&home_file, &cx));
1217    }
1218
1219    #[test]
1220    fn test_resolve_language_servers() {
1221        fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {
1222            names
1223                .iter()
1224                .copied()
1225                .map(|name| LanguageServerName(name.to_string().into()))
1226                .collect::<Vec<_>>()
1227        }
1228
1229        let available_language_servers = language_server_names(&[
1230            "typescript-language-server",
1231            "biome",
1232            "deno",
1233            "eslint",
1234            "tailwind",
1235        ]);
1236
1237        // A value of just `["..."]` is the same as taking all of the available language servers.
1238        assert_eq!(
1239            LanguageSettings::resolve_language_servers(
1240                &[LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()],
1241                &available_language_servers,
1242            ),
1243            available_language_servers
1244        );
1245
1246        // Referencing one of the available language servers will change its order.
1247        assert_eq!(
1248            LanguageSettings::resolve_language_servers(
1249                &[
1250                    "biome".into(),
1251                    LanguageSettings::REST_OF_LANGUAGE_SERVERS.into(),
1252                    "deno".into()
1253                ],
1254                &available_language_servers
1255            ),
1256            language_server_names(&[
1257                "biome",
1258                "typescript-language-server",
1259                "eslint",
1260                "tailwind",
1261                "deno",
1262            ])
1263        );
1264
1265        // Negating an available language server removes it from the list.
1266        assert_eq!(
1267            LanguageSettings::resolve_language_servers(
1268                &[
1269                    "deno".into(),
1270                    "!typescript-language-server".into(),
1271                    "!biome".into(),
1272                    LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1273                ],
1274                &available_language_servers
1275            ),
1276            language_server_names(&["deno", "eslint", "tailwind"])
1277        );
1278
1279        // Adding a language server not in the list of available language servers adds it to the list.
1280        assert_eq!(
1281            LanguageSettings::resolve_language_servers(
1282                &[
1283                    "my-cool-language-server".into(),
1284                    LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1285                ],
1286                &available_language_servers
1287            ),
1288            language_server_names(&[
1289                "my-cool-language-server",
1290                "typescript-language-server",
1291                "biome",
1292                "deno",
1293                "eslint",
1294                "tailwind",
1295            ])
1296        );
1297    }
1298}