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