Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:51:04.433Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

language_settings.rs

1513 lines · 61.1 KB · rust
1//! Provides `language`-related settings.
2
3use crate::{
4    Buffer, BufferSnapshot, File, Language, LanguageName, LanguageServerName, ModelineSettings,
5};
6use collections::{FxHashMap, HashMap, HashSet};
7use ec4rs::{
8    Properties as EditorconfigProperties,
9    property::{
10        EndOfLine, FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs,
11    },
12};
13use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
14use gpui::{App, Modifiers, SharedString};
15use itertools::Itertools;
16use settings::{DocumentFoldingRanges, DocumentSymbols, IntoGpui, SemanticTokens};
17
18pub use settings::{
19    AutoIndentMode, CompletionSettingsContent, ConfiguredLanguageServer,
20    EditPredictionDataCollectionChoice, EditPredictionPromptFormatContent, EditPredictionProvider,
21    EditPredictionsMode, FormatOnSave, Formatter, FormatterList, InlayHintKind,
22    LanguageSettingsContent, LineEndingSetting, LspInsertMode, REST_OF_LANGUAGE_SERVERS,
23    RewrapBehavior, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode,
24};
25use settings::{RegisterSetting, Settings, SettingsLocation, SettingsStore, merge_from::MergeFrom};
26use shellexpand;
27use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc};
28use text::ToOffset;
29
30/// Returns the settings for all languages from the provided file.
31pub fn all_language_settings<'a>(
32    file: Option<&'a Arc<dyn File>>,
33    cx: &'a App,
34) -> &'a AllLanguageSettings {
35    let location = file.map(|f| SettingsLocation {
36        worktree_id: f.worktree_id(cx),
37        path: f.path().as_ref(),
38    });
39    AllLanguageSettings::get(location, cx)
40}
41
42/// The settings for all languages.
43#[derive(Debug, Clone, RegisterSetting)]
44pub struct AllLanguageSettings {
45    /// The edit prediction settings.
46    pub edit_predictions: EditPredictionSettings,
47    pub defaults: LanguageSettings,
48    languages: HashMap<LanguageName, LanguageSettings>,
49    pub file_types: FxHashMap<Arc<str>, (GlobSet, Vec<String>)>,
50}
51
52#[derive(Debug, Clone, PartialEq)]
53pub struct WhitespaceMap {
54    pub space: SharedString,
55    pub tab: SharedString,
56}
57
58/// The settings for a particular language.
59#[derive(Debug, Clone, PartialEq)]
60pub struct LanguageSettings {
61    /// How many columns a tab should occupy.
62    pub tab_size: NonZeroU32,
63    /// Whether to indent lines using tab characters, as opposed to multiple
64    /// spaces.
65    pub hard_tabs: bool,
66    /// How to soft-wrap long lines of text.
67    pub soft_wrap: settings::SoftWrap,
68    /// The column at which to soft-wrap lines, for buffers where soft-wrap
69    /// is enabled.
70    pub preferred_line_length: u32,
71    /// Whether to show wrap guides (vertical rulers) in the editor.
72    /// Setting this to true will show a guide at the 'preferred_line_length' value
73    /// if softwrap is set to 'preferred_line_length', and will show any
74    /// additional guides as specified by the 'wrap_guides' setting.
75    pub show_wrap_guides: bool,
76    /// Character counts at which to show wrap guides (vertical rulers) in the editor.
77    pub wrap_guides: Vec<usize>,
78    /// Indent guide related settings.
79    pub indent_guides: IndentGuideSettings,
80    /// Whether or not to perform a buffer format before saving.
81    pub format_on_save: FormatOnSave,
82    /// Whether or not to remove any trailing whitespace from lines of a buffer
83    /// before saving it.
84    pub remove_trailing_whitespace_on_save: bool,
85    /// Whether or not to ensure there's a single newline at the end of a buffer
86    /// when saving it.
87    pub ensure_final_newline_on_save: bool,
88    /// How line endings are initialized for new files and normalized during
89    /// format and save.
90    pub line_ending: LineEndingSetting,
91    /// How to perform a buffer format.
92    pub formatter: settings::FormatterList,
93    /// Zed's Prettier integration settings.
94    pub prettier: PrettierSettings,
95    /// Whether to automatically close JSX tags.
96    pub jsx_tag_auto_close: bool,
97    /// Whether to use language servers to provide code intelligence.
98    pub enable_language_server: bool,
99    /// The list of language servers to use (or disable) for this language.
100    ///
101    /// This array should consist of language server IDs, as well as the following
102    /// special tokens:
103    /// - `"!<language_server_id>"` - A language server ID prefixed with a `!` will be disabled.
104    /// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
105    pub language_servers: Vec<ConfiguredLanguageServer>,
106    /// Controls how semantic tokens from language servers are used for syntax highlighting.
107    pub semantic_tokens: SemanticTokens,
108    /// Controls whether folding ranges from language servers are used instead of
109    /// tree-sitter and indent-based folding.
110    pub document_folding_ranges: DocumentFoldingRanges,
111    /// Controls the source of document symbols used for outlines and breadcrumbs.
112    pub document_symbols: DocumentSymbols,
113    /// Controls where the `editor::Rewrap` action is allowed for this language.
114    ///
115    /// Note: This setting has no effect in Vim mode, as rewrap is already
116    /// allowed everywhere.
117    pub allow_rewrap: RewrapBehavior,
118    /// Controls whether edit predictions are shown immediately (true)
119    /// or manually by triggering `editor::ShowEditPrediction` (false).
120    pub show_edit_predictions: bool,
121    /// Controls whether edit predictions are shown in the given language
122    /// scopes.
123    pub edit_predictions_disabled_in: Vec<String>,
124    /// Whether to show tabs and spaces in the editor.
125    pub show_whitespaces: settings::ShowWhitespaceSetting,
126    /// Visible characters used to render whitespace when show_whitespaces is enabled.
127    pub whitespace_map: WhitespaceMap,
128    /// Whether to start a new line with a comment when a previous line is a comment as well.
129    pub extend_comment_on_newline: bool,
130    /// Whether to continue markdown lists when pressing enter.
131    pub extend_list_on_newline: bool,
132    /// Whether to indent list items when pressing tab after a list marker.
133    pub indent_list_on_tab: bool,
134    /// Inlay hint related settings.
135    pub inlay_hints: InlayHintSettings,
136    /// Whether to automatically close brackets.
137    pub use_autoclose: bool,
138    /// Whether to automatically surround text with brackets.
139    pub use_auto_surround: bool,
140    /// Whether to use additional LSP queries to format (and amend) the code after
141    /// every "trigger" symbol input, defined by LSP server capabilities.
142    pub use_on_type_format: bool,
143    /// Controls automatic indentation behavior when typing.
144    pub auto_indent: AutoIndentMode,
145    /// Whether indentation of pasted content should be adjusted based on the context.
146    pub auto_indent_on_paste: bool,
147    /// Controls how the editor handles the autoclosed characters.
148    pub always_treat_brackets_as_autoclosed: bool,
149    /// Which code actions to run on save
150    pub code_actions_on_format: HashMap<String, bool>,
151    /// Whether to perform linked edits
152    pub linked_edits: bool,
153    /// Task configuration for this language.
154    pub tasks: LanguageTaskSettings,
155    /// Whether to pop the completions menu while typing in an editor without
156    /// explicitly requesting it.
157    pub show_completions_on_input: bool,
158    /// Whether to display inline and alongside documentation for items in the
159    /// completions menu.
160    pub show_completion_documentation: bool,
161    /// Completion settings for this language.
162    pub completions: CompletionSettings,
163    /// Preferred debuggers for this language.
164    pub debuggers: Vec<String>,
165    /// Whether to enable word diff highlighting in the editor.
166    ///
167    /// When enabled, changed words within modified lines are highlighted
168    /// to show exactly what changed.
169    ///
170    /// Default: `true`
171    pub word_diff_enabled: bool,
172    /// Whether to use tree-sitter bracket queries to detect and colorize the brackets in the editor.
173    pub colorize_brackets: bool,
174}
175
176#[derive(Debug, Clone, PartialEq)]
177pub struct CompletionSettings {
178    /// Controls how words are completed.
179    /// For large documents, not all words may be fetched for completion.
180    ///
181    /// Default: `fallback`
182    pub words: WordsCompletionMode,
183    /// How many characters has to be in the completions query to automatically show the words-based completions.
184    /// Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command.
185    ///
186    /// Default: 3
187    pub words_min_length: usize,
188    /// Whether to fetch LSP completions or not.
189    ///
190    /// Default: true
191    pub lsp: bool,
192    /// When fetching LSP completions, determines how long to wait for a response of a particular server.
193    /// When set to 0, waits indefinitely.
194    ///
195    /// Default: 0
196    pub lsp_fetch_timeout_ms: u64,
197    /// Controls how LSP completions are inserted.
198    ///
199    /// Default: "replace_suffix"
200    pub lsp_insert_mode: LspInsertMode,
201}
202
203/// The settings for indent guides.
204#[derive(Debug, Clone, PartialEq)]
205pub struct IndentGuideSettings {
206    /// Whether to display indent guides in the editor.
207    ///
208    /// Default: true
209    pub enabled: bool,
210    /// The width of the indent guides in pixels, between 1 and 10.
211    ///
212    /// Default: 1
213    pub line_width: u32,
214    /// The width of the active indent guide in pixels, between 1 and 10.
215    ///
216    /// Default: 1
217    pub active_line_width: u32,
218    /// Determines how indent guides are colored.
219    ///
220    /// Default: Fixed
221    pub coloring: settings::IndentGuideColoring,
222    /// Determines how indent guide backgrounds are colored.
223    ///
224    /// Default: Disabled
225    pub background_coloring: settings::IndentGuideBackgroundColoring,
226}
227
228impl IndentGuideSettings {
229    /// Returns the clamped line width in pixels for an indent guide based on
230    /// whether it is active, or `None` when line coloring is disabled.
231    pub fn visible_line_width(&self, active: bool) -> Option<u32> {
232        if self.coloring == settings::IndentGuideColoring::Disabled {
233            return None;
234        }
235        let width = if active {
236            self.active_line_width
237        } else {
238            self.line_width
239        };
240        Some(width.clamp(1, 10))
241    }
242}
243
244#[derive(Debug, Clone, PartialEq)]
245pub struct LanguageTaskSettings {
246    /// Extra task variables to set for a particular language.
247    pub variables: HashMap<String, String>,
248    pub enabled: bool,
249    /// Use LSP tasks over Zed language extension ones.
250    /// If no LSP tasks are returned due to error/timeout or regular execution,
251    /// Zed language extension tasks will be used instead.
252    ///
253    /// Other Zed tasks will still be shown:
254    /// * Zed task from either of the task config file
255    /// * Zed task from history (e.g. one-off task was spawned before)
256    pub prefer_lsp: bool,
257}
258
259/// Allows to enable/disable formatting with Prettier
260/// and configure default Prettier, used when no project-level Prettier installation is found.
261/// Prettier formatting is disabled by default.
262#[derive(Debug, Clone, PartialEq)]
263pub struct PrettierSettings {
264    /// Enables or disables formatting with Prettier for a given language.
265    pub allowed: bool,
266
267    /// Forces Prettier integration to use a specific parser name when formatting files with the language.
268    pub parser: Option<String>,
269
270    /// Forces Prettier integration to use specific plugins when formatting files with the language.
271    /// The default Prettier will be installed with these plugins.
272    pub plugins: HashSet<String>,
273
274    /// Default Prettier options, in the format as in package.json section for Prettier.
275    /// If project installs Prettier via its package.json, these options will be ignored.
276    pub options: HashMap<String, serde_json::Value>,
277}
278
279impl LanguageSettings {
280    pub fn for_buffer<'a>(buffer: &'a Buffer, cx: &'a App) -> Cow<'a, LanguageSettings> {
281        Self::resolve(Some(buffer), None, cx)
282    }
283
284    pub fn for_buffer_at<'a, D: ToOffset>(
285        buffer: &'a Buffer,
286        position: D,
287        cx: &'a App,
288    ) -> Cow<'a, LanguageSettings> {
289        let language = buffer.language_at(position);
290        Self::resolve(Some(buffer), language.map(|l| l.name()).as_ref(), cx)
291    }
292
293    pub fn for_buffer_snapshot<'a>(
294        buffer: &'a BufferSnapshot,
295        offset: Option<usize>,
296        cx: &'a App,
297    ) -> Cow<'a, LanguageSettings> {
298        let location = buffer.file().map(|f| SettingsLocation {
299            worktree_id: f.worktree_id(cx),
300            path: f.path().as_ref(),
301        });
302
303        let language = if let Some(offset) = offset {
304            buffer.language_at(offset)
305        } else {
306            buffer.language()
307        };
308
309        let mut settings = AllLanguageSettings::get(location, cx).language(
310            location,
311            language.map(|l| l.name()).as_ref(),
312            cx,
313        );
314
315        if let Some(modeline) = buffer.modeline() {
316            merge_with_modeline(settings.to_mut(), modeline);
317        }
318
319        settings
320    }
321
322    pub fn resolve<'a>(
323        buffer: Option<&'a Buffer>,
324        override_language: Option<&LanguageName>,
325        cx: &'a App,
326    ) -> Cow<'a, LanguageSettings> {
327        let Some(buffer) = buffer else {
328            return AllLanguageSettings::get(None, cx).language(None, override_language, cx);
329        };
330        let location = buffer.file().map(|f| SettingsLocation {
331            worktree_id: f.worktree_id(cx),
332            path: f.path().as_ref(),
333        });
334        let all = AllLanguageSettings::get(location, cx);
335        let mut settings = if override_language.is_none() {
336            all.language(location, buffer.language().map(|l| l.name()).as_ref(), cx)
337        } else {
338            all.language(location, override_language, cx)
339        };
340
341        if let Some(modeline) = buffer.modeline() {
342            merge_with_modeline(settings.to_mut(), modeline);
343        }
344
345        settings
346    }
347
348    /// Returns the customized list of language servers from the list of
349    /// available language servers.
350    pub fn customized_language_servers(
351        &self,
352        available_language_servers: &[LanguageServerName],
353    ) -> Vec<LanguageServerName> {
354        Self::resolve_language_servers(&self.language_servers, available_language_servers)
355    }
356
357    pub(crate) fn resolve_language_servers(
358        configured_language_servers: &[ConfiguredLanguageServer],
359        available_language_servers: &[LanguageServerName],
360    ) -> Vec<LanguageServerName> {
361        let (disabled_language_servers, enabled_language_servers): (
362            Vec<LanguageServerName>,
363            Vec<LanguageServerName>,
364        ) = configured_language_servers
365            .iter()
366            .partition_map(|language_server| {
367                let name = LanguageServerName(language_server.name.clone().into());
368                if language_server.disabled {
369                    itertools::Either::Left(name)
370                } else {
371                    itertools::Either::Right(name)
372                }
373            });
374
375        let rest = available_language_servers
376            .iter()
377            .filter(|&available_language_server| {
378                !disabled_language_servers.contains(available_language_server)
379                    && !enabled_language_servers.contains(available_language_server)
380            })
381            .cloned()
382            .collect::<Vec<_>>();
383
384        enabled_language_servers
385            .into_iter()
386            .flat_map(|language_server| {
387                if language_server.0.as_ref() == REST_OF_LANGUAGE_SERVERS {
388                    rest.clone()
389                } else {
390                    vec![language_server]
391                }
392            })
393            .collect::<Vec<_>>()
394    }
395}
396
397// The settings for inlay hints.
398#[derive(Copy, Clone, Debug, PartialEq, Eq)]
399pub struct InlayHintSettings {
400    /// Global switch to toggle hints on and off.
401    ///
402    /// Default: false
403    pub enabled: bool,
404    /// Global switch to toggle inline values on and off when debugging.
405    ///
406    /// Default: true
407    pub show_value_hints: bool,
408    /// Whether type hints should be shown.
409    ///
410    /// Default: true
411    pub show_type_hints: bool,
412    /// Whether parameter hints should be shown.
413    ///
414    /// Default: true
415    pub show_parameter_hints: bool,
416    /// Whether other hints should be shown.
417    ///
418    /// Default: true
419    pub show_other_hints: bool,
420    /// Whether to show a background for inlay hints.
421    ///
422    /// If set to `true`, the background will use the `hint.background` color
423    /// from the current theme.
424    ///
425    /// Default: false
426    pub show_background: bool,
427    /// Whether or not to debounce inlay hints updates after buffer edits.
428    ///
429    /// Set to 0 to disable debouncing.
430    ///
431    /// Default: 700
432    pub edit_debounce_ms: u64,
433    /// Whether or not to debounce inlay hints updates after buffer scrolls.
434    ///
435    /// Set to 0 to disable debouncing.
436    ///
437    /// Default: 50
438    pub scroll_debounce_ms: u64,
439    /// Toggles inlay hints (hides or shows) when the user presses the modifiers specified.
440    /// If only a subset of the modifiers specified is pressed, hints are not toggled.
441    /// If no modifiers are specified, this is equivalent to `None`.
442    ///
443    /// Default: None
444    pub toggle_on_modifiers_press: Option<Modifiers>,
445}
446
447impl InlayHintSettings {
448    /// Returns the kinds of inlay hints that are enabled based on the settings.
449    pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
450        let mut kinds = HashSet::default();
451        if self.show_type_hints {
452            kinds.insert(Some(InlayHintKind::Type));
453        }
454        if self.show_parameter_hints {
455            kinds.insert(Some(InlayHintKind::Parameter));
456        }
457        if self.show_other_hints {
458            kinds.insert(None);
459        }
460        kinds
461    }
462}
463
464/// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot).
465#[derive(Clone, Debug, Default)]
466pub struct EditPredictionSettings {
467    /// The provider that supplies edit predictions.
468    pub provider: settings::EditPredictionProvider,
469    /// A list of globs representing files that edit predictions should be disabled for.
470    /// This list adds to a pre-existing, sensible default set of globs.
471    /// Any additional ones you add are combined with them.
472    pub disabled_globs: Vec<DisabledGlob>,
473    /// Configures how edit predictions are displayed in the buffer.
474    pub mode: settings::EditPredictionsMode,
475    /// Settings specific to GitHub Copilot.
476    pub copilot: CopilotSettings,
477    /// Settings specific to Codestral.
478    pub codestral: CodestralSettings,
479    /// Settings specific to Ollama.
480    pub ollama: Option<OpenAiCompatibleEditPredictionSettings>,
481    pub open_ai_compatible_api: Option<OpenAiCompatibleEditPredictionSettings>,
482    /// Controls whether training data collection is enabled.
483    ///
484    /// `Default` means the value stored in the legacy KV store is used as a fallback,
485    /// preserving existing users' choices without a migration.
486    pub allow_data_collection: EditPredictionDataCollectionChoice,
487}
488
489impl EditPredictionSettings {
490    /// Returns whether edit predictions are enabled for the given path.
491    pub fn enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
492        !self.disabled_globs.iter().any(|glob| {
493            if glob.is_absolute {
494                file.as_local()
495                    .is_some_and(|local| glob.matcher.is_match(local.abs_path(cx)))
496            } else {
497                glob.matcher.is_match(file.path().as_std_path())
498            }
499        })
500    }
501}
502
503#[derive(Clone, Debug)]
504pub struct DisabledGlob {
505    matcher: GlobMatcher,
506    is_absolute: bool,
507}
508
509#[derive(Clone, Debug, Default)]
510pub struct CopilotSettings {
511    /// HTTP/HTTPS proxy to use for Copilot.
512    pub proxy: Option<String>,
513    /// Disable certificate verification for proxy (not recommended).
514    pub proxy_no_verify: Option<bool>,
515    /// Enterprise URI for Copilot.
516    pub enterprise_uri: Option<String>,
517    /// Whether the Copilot Next Edit Suggestions feature is enabled.
518    pub enable_next_edit_suggestions: Option<bool>,
519}
520
521#[derive(Clone, Debug, Default)]
522pub struct CodestralSettings {
523    /// Model to use for completions.
524    pub model: Option<String>,
525    /// Maximum tokens to generate.
526    pub max_tokens: Option<u32>,
527    /// Custom API URL to use for Codestral.
528    pub api_url: Option<String>,
529}
530
531#[derive(Clone, Debug, Default)]
532pub struct OpenAiCompatibleEditPredictionSettings {
533    /// Model to use for completions.
534    pub model: String,
535    /// Maximum tokens to generate.
536    pub max_output_tokens: u32,
537    /// Custom API URL to use for Ollama.
538    pub api_url: Arc<str>,
539    /// The prompt format to use for completions. When `None`, the format
540    /// will be derived from the model name at request time.
541    pub prompt_format: EditPredictionPromptFormat,
542}
543
544#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
545pub enum EditPredictionPromptFormat {
546    #[default]
547    Infer,
548    Zeta(ZetaVersion),
549    CodeLlama,
550    StarCoder,
551    DeepseekCoder,
552    Qwen,
553    CodeGemma,
554    Codestral,
555    Glm,
556}
557
558#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
559pub enum ZetaVersion {
560    Zeta1,
561    Zeta2,
562    #[default] // NOTE: make latest version default when adding
563    Zeta2_1,
564}
565
566impl From<EditPredictionPromptFormatContent> for EditPredictionPromptFormat {
567    fn from(value: EditPredictionPromptFormatContent) -> Self {
568        match value {
569            EditPredictionPromptFormatContent::Infer => Self::Infer,
570            EditPredictionPromptFormatContent::Zeta => Self::Zeta(ZetaVersion::Zeta1),
571            EditPredictionPromptFormatContent::Zeta2 => Self::Zeta(ZetaVersion::Zeta2),
572            EditPredictionPromptFormatContent::Zeta2_1 => Self::Zeta(ZetaVersion::Zeta2_1),
573            EditPredictionPromptFormatContent::CodeLlama => Self::CodeLlama,
574            EditPredictionPromptFormatContent::StarCoder => Self::StarCoder,
575            EditPredictionPromptFormatContent::DeepseekCoder => Self::DeepseekCoder,
576            EditPredictionPromptFormatContent::Qwen => Self::Qwen,
577            EditPredictionPromptFormatContent::CodeGemma => Self::CodeGemma,
578            EditPredictionPromptFormatContent::Codestral => Self::Codestral,
579            EditPredictionPromptFormatContent::Glm => Self::Glm,
580        }
581    }
582}
583
584impl AllLanguageSettings {
585    /// Returns the [`LanguageSettings`] for the language with the specified name.
586    pub fn language<'a>(
587        &'a self,
588        location: Option<SettingsLocation<'a>>,
589        language_name: Option<&LanguageName>,
590        cx: &'a App,
591    ) -> Cow<'a, LanguageSettings> {
592        let settings = language_name
593            .and_then(|name| self.languages.get(name))
594            .unwrap_or(&self.defaults);
595
596        let editorconfig_properties = location.and_then(|location| {
597            cx.global::<SettingsStore>()
598                .editorconfig_store
599                .read(cx)
600                .properties(location.worktree_id, location.path)
601        });
602        if let Some(editorconfig_properties) = editorconfig_properties {
603            let mut settings = settings.clone();
604            merge_with_editorconfig(&mut settings, &editorconfig_properties);
605            Cow::Owned(settings)
606        } else {
607            Cow::Borrowed(settings)
608        }
609    }
610
611    /// Returns whether edit predictions are enabled for the given path.
612    pub fn edit_predictions_enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
613        self.edit_predictions.enabled_for_file(file, cx)
614    }
615
616    /// Returns whether edit predictions are enabled for the given language and path.
617    pub fn show_edit_predictions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
618        self.language(None, language.map(|l| l.name()).as_ref(), cx)
619            .show_edit_predictions
620    }
621
622    /// Returns the edit predictions preview mode for the given language and path.
623    pub fn edit_predictions_mode(&self) -> EditPredictionsMode {
624        self.edit_predictions.mode
625    }
626}
627
628fn merge_with_modeline(settings: &mut LanguageSettings, modeline: &ModelineSettings) {
629    let show_whitespaces = modeline.show_trailing_whitespace.and_then(|v| {
630        if v {
631            Some(ShowWhitespaceSetting::Trailing)
632        } else {
633            None
634        }
635    });
636
637    settings
638        .tab_size
639        .merge_from_option(modeline.tab_size.as_ref());
640    settings
641        .hard_tabs
642        .merge_from_option(modeline.hard_tabs.as_ref());
643    settings
644        .preferred_line_length
645        .merge_from_option(modeline.preferred_line_length.map(u32::from).as_ref());
646    let auto_indent_mode = modeline.auto_indent.map(|enabled| {
647        if enabled {
648            AutoIndentMode::SyntaxAware
649        } else {
650            AutoIndentMode::None
651        }
652    });
653    settings
654        .auto_indent
655        .merge_from_option(auto_indent_mode.as_ref());
656    settings
657        .show_whitespaces
658        .merge_from_option(show_whitespaces.as_ref());
659    settings
660        .ensure_final_newline_on_save
661        .merge_from_option(modeline.ensure_final_newline.as_ref());
662}
663
664fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
665    let preferred_line_length = cfg.get::<MaxLineLen>().ok().and_then(|v| match v {
666        MaxLineLen::Value(u) => Some(u as u32),
667        MaxLineLen::Off => None,
668    });
669    let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
670        IndentSize::Value(u) => NonZeroU32::new(u as u32),
671        IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
672            TabWidth::Value(u) => NonZeroU32::new(u as u32),
673        }),
674    });
675    let hard_tabs = cfg
676        .get::<IndentStyle>()
677        .map(|v| v.eq(&IndentStyle::Tabs))
678        .ok();
679    let ensure_final_newline_on_save = cfg
680        .get::<FinalNewline>()
681        .map(|v| match v {
682            FinalNewline::Value(b) => b,
683        })
684        .ok();
685    let remove_trailing_whitespace_on_save = cfg
686        .get::<TrimTrailingWs>()
687        .map(|v| match v {
688            TrimTrailingWs::Value(b) => b,
689        })
690        .ok();
691    let line_ending = cfg.get::<EndOfLine>().ok().and_then(|v| match v {
692        EndOfLine::Lf => Some(LineEndingSetting::EnforceLf),
693        EndOfLine::CrLf => Some(LineEndingSetting::EnforceCrlf),
694        EndOfLine::Cr => None,
695    });
696
697    settings
698        .preferred_line_length
699        .merge_from_option(preferred_line_length.as_ref());
700    settings.tab_size.merge_from_option(tab_size.as_ref());
701    settings.hard_tabs.merge_from_option(hard_tabs.as_ref());
702    // Avoid re-enabling destructive whitespace trimming when Zed settings have
703    // disabled it, e.g. for Markdown hard breaks.
704    if !matches!(remove_trailing_whitespace_on_save, Some(true))
705        || settings.remove_trailing_whitespace_on_save
706    {
707        settings
708            .remove_trailing_whitespace_on_save
709            .merge_from_option(remove_trailing_whitespace_on_save.as_ref());
710    }
711    settings
712        .ensure_final_newline_on_save
713        .merge_from_option(ensure_final_newline_on_save.as_ref());
714    settings.line_ending.merge_from_option(line_ending.as_ref());
715}
716
717impl settings::Settings for AllLanguageSettings {
718    fn from_settings(content: &settings::SettingsContent) -> Self {
719        let all_languages = &content.project.all_languages;
720
721        fn load_from_content(settings: LanguageSettingsContent) -> LanguageSettings {
722            let inlay_hints = settings.inlay_hints.unwrap();
723            let completions = settings.completions.unwrap();
724            let prettier = settings.prettier.unwrap();
725            let indent_guides = settings.indent_guides.unwrap();
726            let tasks = settings.tasks.unwrap();
727            let whitespace_map = settings.whitespace_map.unwrap();
728
729            LanguageSettings {
730                tab_size: settings.tab_size.unwrap(),
731                hard_tabs: settings.hard_tabs.unwrap(),
732                soft_wrap: settings.soft_wrap.unwrap(),
733                preferred_line_length: settings.preferred_line_length.unwrap(),
734                show_wrap_guides: settings.show_wrap_guides.unwrap(),
735                wrap_guides: settings.wrap_guides.unwrap(),
736                indent_guides: IndentGuideSettings {
737                    enabled: indent_guides.enabled.unwrap(),
738                    line_width: indent_guides.line_width.unwrap(),
739                    active_line_width: indent_guides.active_line_width.unwrap(),
740                    coloring: indent_guides.coloring.unwrap(),
741                    background_coloring: indent_guides.background_coloring.unwrap(),
742                },
743                format_on_save: settings.format_on_save.unwrap(),
744                remove_trailing_whitespace_on_save: settings
745                    .remove_trailing_whitespace_on_save
746                    .unwrap(),
747                ensure_final_newline_on_save: settings.ensure_final_newline_on_save.unwrap(),
748                line_ending: settings.line_ending.unwrap(),
749                formatter: settings.formatter.unwrap(),
750                prettier: PrettierSettings {
751                    allowed: prettier.allowed.unwrap(),
752                    parser: prettier.parser.filter(|parser| !parser.is_empty()),
753                    plugins: prettier.plugins.unwrap_or_default(),
754                    options: prettier.options.unwrap_or_default(),
755                },
756                jsx_tag_auto_close: settings.jsx_tag_auto_close.unwrap().enabled.unwrap(),
757                enable_language_server: settings.enable_language_server.unwrap(),
758                language_servers: settings.language_servers.unwrap(),
759                semantic_tokens: settings.semantic_tokens.unwrap(),
760                document_folding_ranges: settings.document_folding_ranges.unwrap(),
761                document_symbols: settings.document_symbols.unwrap(),
762                allow_rewrap: settings.allow_rewrap.unwrap(),
763                show_edit_predictions: settings.show_edit_predictions.unwrap(),
764                edit_predictions_disabled_in: settings.edit_predictions_disabled_in.unwrap(),
765                show_whitespaces: settings.show_whitespaces.unwrap(),
766                whitespace_map: WhitespaceMap {
767                    space: SharedString::new(whitespace_map.space.unwrap().to_string()),
768                    tab: SharedString::new(whitespace_map.tab.unwrap().to_string()),
769                },
770                extend_comment_on_newline: settings.extend_comment_on_newline.unwrap(),
771                extend_list_on_newline: settings.extend_list_on_newline.unwrap(),
772                indent_list_on_tab: settings.indent_list_on_tab.unwrap(),
773                inlay_hints: InlayHintSettings {
774                    enabled: inlay_hints.enabled.unwrap(),
775                    show_value_hints: inlay_hints.show_value_hints.unwrap(),
776                    show_type_hints: inlay_hints.show_type_hints.unwrap(),
777                    show_parameter_hints: inlay_hints.show_parameter_hints.unwrap(),
778                    show_other_hints: inlay_hints.show_other_hints.unwrap(),
779                    show_background: inlay_hints.show_background.unwrap(),
780                    edit_debounce_ms: inlay_hints.edit_debounce_ms.unwrap(),
781                    scroll_debounce_ms: inlay_hints.scroll_debounce_ms.unwrap(),
782                    toggle_on_modifiers_press: inlay_hints
783                        .toggle_on_modifiers_press
784                        .map(|m| m.into_gpui()),
785                },
786                use_autoclose: settings.use_autoclose.unwrap(),
787                use_auto_surround: settings.use_auto_surround.unwrap(),
788                use_on_type_format: settings.use_on_type_format.unwrap(),
789                auto_indent: settings.auto_indent.unwrap(),
790                auto_indent_on_paste: settings.auto_indent_on_paste.unwrap(),
791                always_treat_brackets_as_autoclosed: settings
792                    .always_treat_brackets_as_autoclosed
793                    .unwrap(),
794                code_actions_on_format: settings.code_actions_on_format.unwrap(),
795                linked_edits: settings.linked_edits.unwrap(),
796                tasks: LanguageTaskSettings {
797                    variables: tasks.variables.unwrap_or_default(),
798                    enabled: tasks.enabled.unwrap(),
799                    prefer_lsp: tasks.prefer_lsp.unwrap(),
800                },
801                show_completions_on_input: settings.show_completions_on_input.unwrap(),
802                show_completion_documentation: settings.show_completion_documentation.unwrap(),
803                colorize_brackets: settings.colorize_brackets.unwrap(),
804                completions: CompletionSettings {
805                    words: completions.words.unwrap(),
806                    words_min_length: completions.words_min_length.unwrap() as usize,
807                    lsp: completions.lsp.unwrap(),
808                    lsp_fetch_timeout_ms: completions.lsp_fetch_timeout_ms.unwrap(),
809                    lsp_insert_mode: completions.lsp_insert_mode.unwrap(),
810                },
811                debuggers: settings.debuggers.unwrap(),
812                word_diff_enabled: settings.word_diff_enabled.unwrap(),
813            }
814        }
815
816        let default_language_settings = load_from_content(all_languages.defaults.clone());
817
818        let mut languages = HashMap::default();
819        for (language_name, settings) in &all_languages.languages.0 {
820            let mut language_settings = all_languages.defaults.clone();
821            settings::merge_from::MergeFrom::merge_from(&mut language_settings, settings);
822            languages.insert(
823                LanguageName(language_name.clone().into()),
824                load_from_content(language_settings),
825            );
826        }
827
828        let edit_prediction_provider = all_languages
829            .edit_predictions
830            .as_ref()
831            .and_then(|ep| ep.provider);
832
833        let edit_predictions = all_languages.edit_predictions.clone().unwrap();
834        let edit_predictions_mode = edit_predictions.mode.unwrap();
835
836        let disabled_globs: HashSet<&String> = edit_predictions
837            .disabled_globs
838            .as_ref()
839            .unwrap()
840            .iter()
841            .collect();
842
843        let copilot = edit_predictions.copilot.unwrap();
844        let copilot_settings = CopilotSettings {
845            proxy: copilot.proxy,
846            proxy_no_verify: copilot.proxy_no_verify,
847            enterprise_uri: copilot.enterprise_uri,
848            enable_next_edit_suggestions: copilot.enable_next_edit_suggestions,
849        };
850
851        let codestral = edit_predictions.codestral.unwrap();
852        let codestral_settings = CodestralSettings {
853            model: codestral.model,
854            max_tokens: codestral.max_tokens,
855            api_url: codestral.api_url,
856        };
857
858        let ollama = edit_predictions.ollama.unwrap();
859        let ollama_settings = ollama
860            .model
861            .filter(|model| !model.0.is_empty())
862            .map(|model| OpenAiCompatibleEditPredictionSettings {
863                model: model.0,
864                max_output_tokens: ollama.max_output_tokens.unwrap(),
865                api_url: ollama.api_url.unwrap().into(),
866                prompt_format: ollama.prompt_format.unwrap().into(),
867            });
868        let openai_compatible_settings = edit_predictions.open_ai_compatible_api.unwrap();
869        let openai_compatible_settings = openai_compatible_settings
870            .model
871            .filter(|model| !model.is_empty())
872            .zip(
873                openai_compatible_settings
874                    .api_url
875                    .filter(|api_url| !api_url.is_empty()),
876            )
877            .map(|(model, api_url)| OpenAiCompatibleEditPredictionSettings {
878                model,
879                max_output_tokens: openai_compatible_settings.max_output_tokens.unwrap(),
880                api_url: api_url.into(),
881                prompt_format: openai_compatible_settings.prompt_format.unwrap().into(),
882            });
883
884        let mut file_types: FxHashMap<Arc<str>, (GlobSet, Vec<String>)> = FxHashMap::default();
885
886        for (language, patterns) in all_languages.file_types.iter().flatten() {
887            let mut builder = GlobSetBuilder::new();
888
889            for pattern in &patterns.0 {
890                builder.add(Glob::new(pattern).unwrap());
891            }
892
893            file_types.insert(
894                language.clone(),
895                (
896                    builder.build().unwrap(),
897                    patterns.0.iter().cloned().collect(),
898                ),
899            );
900        }
901
902        Self {
903            edit_predictions: EditPredictionSettings {
904                provider: if let Some(provider) = edit_prediction_provider {
905                    provider
906                } else {
907                    EditPredictionProvider::None
908                },
909                disabled_globs: disabled_globs
910                    .iter()
911                    .filter_map(|g| {
912                        let expanded_g = shellexpand::tilde(g).into_owned();
913                        Some(DisabledGlob {
914                            matcher: globset::Glob::new(&expanded_g).ok()?.compile_matcher(),
915                            is_absolute: Path::new(&expanded_g).is_absolute(),
916                        })
917                    })
918                    .collect(),
919                mode: edit_predictions_mode,
920                copilot: copilot_settings,
921                codestral: codestral_settings,
922                ollama: ollama_settings,
923                open_ai_compatible_api: openai_compatible_settings,
924                allow_data_collection: edit_predictions.allow_data_collection.unwrap_or_default(),
925            },
926            defaults: default_language_settings,
927            languages,
928            file_types,
929        }
930    }
931}
932
933#[derive(Default, Debug, Clone, PartialEq, Eq)]
934pub struct JsxTagAutoCloseSettings {
935    /// Enables or disables auto-closing of JSX tags.
936    pub enabled: bool,
937}
938
939#[cfg(test)]
940mod tests {
941    use super::*;
942    use gpui::TestAppContext;
943    use settings::{LocalSettingsKind, LocalSettingsPath, WorktreeId};
944    use util::rel_path::rel_path;
945
946    #[gpui::test]
947    fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
948        use crate::TestFile;
949        use std::path::PathBuf;
950
951        let cx = cx.app.borrow_mut();
952
953        let build_settings = |globs: &[&str]| -> EditPredictionSettings {
954            EditPredictionSettings {
955                disabled_globs: globs
956                    .iter()
957                    .map(|glob_str| {
958                        #[cfg(windows)]
959                        let glob_str = {
960                            let mut g = String::new();
961
962                            if glob_str.starts_with('/') {
963                                g.push_str("C:");
964                            }
965
966                            g.push_str(&glob_str.replace('/', "\\"));
967                            g
968                        };
969                        #[cfg(windows)]
970                        let glob_str = glob_str.as_str();
971                        let expanded_glob_str = shellexpand::tilde(glob_str).into_owned();
972                        DisabledGlob {
973                            matcher: globset::Glob::new(&expanded_glob_str)
974                                .unwrap()
975                                .compile_matcher(),
976                            is_absolute: Path::new(&expanded_glob_str).is_absolute(),
977                        }
978                    })
979                    .collect(),
980                ..Default::default()
981            }
982        };
983
984        const WORKTREE_NAME: &str = "project";
985        let make_test_file = |segments: &[&str]| -> Arc<dyn File> {
986            let path = segments.join("/");
987            let path = rel_path(&path);
988
989            Arc::new(TestFile {
990                path: path.into(),
991                root_name: WORKTREE_NAME.to_string(),
992                local_root: Some(PathBuf::from(if cfg!(windows) {
993                    "C:\\absolute\\"
994                } else {
995                    "/absolute/"
996                })),
997            })
998        };
999
1000        let test_file = make_test_file(&["src", "test", "file.rs"]);
1001
1002        // Test relative globs
1003        let settings = build_settings(&["*.rs"]);
1004        assert!(!settings.enabled_for_file(&test_file, &cx));
1005        let settings = build_settings(&["*.txt"]);
1006        assert!(settings.enabled_for_file(&test_file, &cx));
1007
1008        // Test absolute globs
1009        let settings = build_settings(&["/absolute/**/*.rs"]);
1010        assert!(!settings.enabled_for_file(&test_file, &cx));
1011        let settings = build_settings(&["/other/**/*.rs"]);
1012        assert!(settings.enabled_for_file(&test_file, &cx));
1013
1014        // Test exact path match relative
1015        let settings = build_settings(&["src/test/file.rs"]);
1016        assert!(!settings.enabled_for_file(&test_file, &cx));
1017        let settings = build_settings(&["src/test/otherfile.rs"]);
1018        assert!(settings.enabled_for_file(&test_file, &cx));
1019
1020        // Test exact path match absolute
1021        let settings = build_settings(&[&format!("/absolute/{}/src/test/file.rs", WORKTREE_NAME)]);
1022        assert!(!settings.enabled_for_file(&test_file, &cx));
1023        let settings = build_settings(&["/other/test/otherfile.rs"]);
1024        assert!(settings.enabled_for_file(&test_file, &cx));
1025
1026        // Test * glob
1027        let settings = build_settings(&["*"]);
1028        assert!(!settings.enabled_for_file(&test_file, &cx));
1029        let settings = build_settings(&["*.txt"]);
1030        assert!(settings.enabled_for_file(&test_file, &cx));
1031
1032        // Test **/* glob
1033        let settings = build_settings(&["**/*"]);
1034        assert!(!settings.enabled_for_file(&test_file, &cx));
1035        let settings = build_settings(&["other/**/*"]);
1036        assert!(settings.enabled_for_file(&test_file, &cx));
1037
1038        // Test directory/** glob
1039        let settings = build_settings(&["src/**"]);
1040        assert!(!settings.enabled_for_file(&test_file, &cx));
1041
1042        let test_file_root: Arc<dyn File> = Arc::new(TestFile {
1043            path: rel_path("file.rs").into(),
1044            root_name: WORKTREE_NAME.to_string(),
1045            local_root: Some(PathBuf::from("/absolute/")),
1046        });
1047        assert!(settings.enabled_for_file(&test_file_root, &cx));
1048
1049        let settings = build_settings(&["other/**"]);
1050        assert!(settings.enabled_for_file(&test_file, &cx));
1051
1052        // Test **/directory/* glob
1053        let settings = build_settings(&["**/test/*"]);
1054        assert!(!settings.enabled_for_file(&test_file, &cx));
1055        let settings = build_settings(&["**/other/*"]);
1056        assert!(settings.enabled_for_file(&test_file, &cx));
1057
1058        // Test multiple globs
1059        let settings = build_settings(&["*.rs", "*.txt", "src/**"]);
1060        assert!(!settings.enabled_for_file(&test_file, &cx));
1061        let settings = build_settings(&["*.txt", "*.md", "other/**"]);
1062        assert!(settings.enabled_for_file(&test_file, &cx));
1063
1064        // Test dot files
1065        let dot_file = make_test_file(&[".config", "settings.json"]);
1066        let settings = build_settings(&[".*/**"]);
1067        assert!(!settings.enabled_for_file(&dot_file, &cx));
1068
1069        let dot_env_file = make_test_file(&[".env"]);
1070        let settings = build_settings(&[".env"]);
1071        assert!(!settings.enabled_for_file(&dot_env_file, &cx));
1072
1073        // Test tilde expansion
1074        let home = shellexpand::tilde("~").into_owned();
1075        let home_file = Arc::new(TestFile {
1076            path: rel_path("test.rs").into(),
1077            root_name: "the-dir".to_string(),
1078            local_root: Some(PathBuf::from(home)),
1079        }) as Arc<dyn File>;
1080        let settings = build_settings(&["~/the-dir/test.rs"]);
1081        assert!(!settings.enabled_for_file(&home_file, &cx));
1082    }
1083
1084    #[test]
1085    fn test_resolve_language_servers() {
1086        fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {
1087            names
1088                .iter()
1089                .copied()
1090                .map(|name| LanguageServerName(name.to_string().into()))
1091                .collect::<Vec<_>>()
1092        }
1093
1094        let available_language_servers = language_server_names(&[
1095            "typescript-language-server",
1096            "biome",
1097            "deno",
1098            "eslint",
1099            "tailwind",
1100        ]);
1101
1102        // A value of just `["..."]` is the same as taking all of the available language servers.
1103        assert_eq!(
1104            LanguageSettings::resolve_language_servers(
1105                &[REST_OF_LANGUAGE_SERVERS.into()],
1106                &available_language_servers,
1107            ),
1108            available_language_servers
1109        );
1110
1111        // Referencing one of the available language servers will change its order.
1112        assert_eq!(
1113            LanguageSettings::resolve_language_servers(
1114                &[
1115                    "biome".into(),
1116                    REST_OF_LANGUAGE_SERVERS.into(),
1117                    "deno".into()
1118                ],
1119                &available_language_servers
1120            ),
1121            language_server_names(&[
1122                "biome",
1123                "typescript-language-server",
1124                "eslint",
1125                "tailwind",
1126                "deno",
1127            ])
1128        );
1129
1130        // Negating an available language server removes it from the list.
1131        assert_eq!(
1132            LanguageSettings::resolve_language_servers(
1133                &[
1134                    "deno".into(),
1135                    "!typescript-language-server".into(),
1136                    "!biome".into(),
1137                    REST_OF_LANGUAGE_SERVERS.into()
1138                ],
1139                &available_language_servers
1140            ),
1141            language_server_names(&["deno", "eslint", "tailwind"])
1142        );
1143
1144        // Adding a language server not in the list of available language servers adds it to the list.
1145        assert_eq!(
1146            LanguageSettings::resolve_language_servers(
1147                &[
1148                    "my-cool-language-server".into(),
1149                    REST_OF_LANGUAGE_SERVERS.into()
1150                ],
1151                &available_language_servers
1152            ),
1153            language_server_names(&[
1154                "my-cool-language-server",
1155                "typescript-language-server",
1156                "biome",
1157                "deno",
1158                "eslint",
1159                "tailwind",
1160            ])
1161        );
1162    }
1163
1164    #[gpui::test]
1165    fn test_language_servers_across_settings_files(cx: &mut TestAppContext) {
1166        cx.update(|cx| {
1167            let mut store = SettingsStore::new(cx, &settings::default_settings());
1168            store.register_setting::<AllLanguageSettings>();
1169
1170            let worktree_id = WorktreeId::from_usize(1);
1171            let root = LocalSettingsPath::InWorktree(rel_path("root").into());
1172            let subdir = LocalSettingsPath::InWorktree(rel_path("root/subdir").into());
1173            let root_location = Some(SettingsLocation {
1174                worktree_id,
1175                path: rel_path("root/a.ts"),
1176            });
1177            let subdir_location = Some(SettingsLocation {
1178                worktree_id,
1179                path: rel_path("root/subdir/b.ts"),
1180            });
1181
1182            assert_eq!(
1183                resolved_language_servers(&store, None, "TypeScript"),
1184                servers(&["vtsls", "eslint"]),
1185                "default settings should disable typescript-language-server for TypeScript"
1186            );
1187            assert_eq!(
1188                resolved_language_servers(&store, None, "Rust"),
1189                servers(&["typescript-language-server", "vtsls", "eslint"]),
1190            );
1191
1192            store
1193                .set_user_settings(r#"{"language_servers": ["!vtsls", "..."]}"#, cx)
1194                .unwrap();
1195            assert_eq!(
1196                resolved_language_servers(&store, None, "TypeScript"),
1197                servers(&["vtsls", "eslint"]),
1198                "the per-language list should fully replace the user's global list"
1199            );
1200            assert_eq!(
1201                resolved_language_servers(&store, None, "Rust"),
1202                servers(&["typescript-language-server", "eslint"]),
1203                "user's global disable should apply to languages without their own list"
1204            );
1205
1206            store
1207                .set_local_settings(
1208                    worktree_id,
1209                    root.clone(),
1210                    LocalSettingsKind::Settings,
1211                    Some(r#"{"language_servers": ["..."]}"#),
1212                    cx,
1213                )
1214                .unwrap();
1215            assert_eq!(
1216                resolved_language_servers(&store, root_location, "Rust"),
1217                servers(&["typescript-language-server", "vtsls", "eslint"]),
1218                "project settings enabling all servers should undo the user's global disable (#61524)"
1219            );
1220            assert_eq!(
1221                resolved_language_servers(&store, None, "Rust"),
1222                servers(&["typescript-language-server", "eslint"]),
1223                "user's global disable should still apply outside of the project"
1224            );
1225
1226            store
1227                .set_local_settings(
1228                    worktree_id,
1229                    root.clone(),
1230                    LocalSettingsKind::Settings,
1231                    Some(r#"{"language_servers": ["!eslint", "..."]}"#),
1232                    cx,
1233                )
1234                .unwrap();
1235            assert_eq!(
1236                resolved_language_servers(&store, root_location, "Rust"),
1237                servers(&["typescript-language-server", "vtsls"]),
1238                "project's global list should replace the user's global list"
1239            );
1240            assert_eq!(
1241                resolved_language_servers(&store, root_location, "TypeScript"),
1242                servers(&["vtsls", "eslint"]),
1243                "the per-language list should fully replace the project's global list"
1244            );
1245
1246            store
1247                .set_local_settings(
1248                    worktree_id,
1249                    subdir.clone(),
1250                    LocalSettingsKind::Settings,
1251                    Some(r#"{"language_servers": ["..."]}"#),
1252                    cx,
1253                )
1254                .unwrap();
1255            assert_eq!(
1256                resolved_language_servers(&store, subdir_location, "Rust"),
1257                servers(&["typescript-language-server", "vtsls", "eslint"]),
1258                "nested project settings should replace the outer global list"
1259            );
1260            assert_eq!(
1261                resolved_language_servers(&store, root_location, "Rust"),
1262                servers(&["typescript-language-server", "vtsls"]),
1263            );
1264            store
1265                .set_local_settings(worktree_id, subdir, LocalSettingsKind::Settings, None, cx)
1266                .unwrap();
1267
1268            store
1269                .set_local_settings(
1270                    worktree_id,
1271                    root.clone(),
1272                    LocalSettingsKind::Settings,
1273                    Some(
1274                        r#"{"languages": {"TypeScript": {"language_servers": ["vtsls", "..."]}}}"#,
1275                    ),
1276                    cx,
1277                )
1278                .unwrap();
1279            assert_eq!(
1280                resolved_language_servers(&store, root_location, "TypeScript"),
1281                servers(&["vtsls", "typescript-language-server", "eslint"]),
1282                "project's per-language configuration should override the user's global disable"
1283            );
1284            assert_eq!(
1285                resolved_language_servers(&store, root_location, "Rust"),
1286                servers(&["typescript-language-server", "eslint"]),
1287                "user's global disable should still apply to other languages"
1288            );
1289
1290            store
1291                .set_user_settings(
1292                    r#"{
1293                        "language_servers": ["!vtsls", "..."],
1294                        "languages": {"TypeScript": {"language_servers": ["vtsls", "..."]}}
1295                    }"#,
1296                    cx,
1297                )
1298                .unwrap();
1299            assert_eq!(
1300                resolved_language_servers(&store, None, "TypeScript"),
1301                servers(&["vtsls", "typescript-language-server", "eslint"]),
1302                "per-language configuration should win over a global disable from the same file"
1303            );
1304            assert_eq!(
1305                resolved_language_servers(&store, None, "Rust"),
1306                servers(&["typescript-language-server", "eslint"]),
1307            );
1308
1309            store
1310                .set_local_settings(
1311                    worktree_id,
1312                    root.clone(),
1313                    LocalSettingsKind::Settings,
1314                    Some(r#"{"language_servers": ["!vtsls", "..."]}"#),
1315                    cx,
1316                )
1317                .unwrap();
1318            assert_eq!(
1319                resolved_language_servers(&store, root_location, "TypeScript"),
1320                servers(&["vtsls", "typescript-language-server", "eslint"]),
1321                "user's per-language list should win over the project's global disable"
1322            );
1323            assert_eq!(
1324                resolved_language_servers(&store, root_location, "Rust"),
1325                servers(&["typescript-language-server", "eslint"]),
1326                "project's global disable should still apply to languages without their own list"
1327            );
1328
1329            store
1330                .set_user_settings(
1331                    r#"{"languages": {"JavaScript": {"language_servers": ["!vtsls", "..."]}}}"#,
1332                    cx,
1333                )
1334                .unwrap();
1335            store
1336                .set_local_settings(
1337                    worktree_id,
1338                    root,
1339                    LocalSettingsKind::Settings,
1340                    Some(r#"{"languages": {"JavaScript": {"language_servers": ["..."]}}}"#),
1341                    cx,
1342                )
1343                .unwrap();
1344            assert_eq!(
1345                resolved_language_servers(&store, root_location, "JavaScript"),
1346                servers(&["typescript-language-server", "vtsls", "eslint"]),
1347                "project's per-language list should re-enable a server disabled by the user (#61524)"
1348            );
1349            assert_eq!(
1350                resolved_language_servers(&store, None, "JavaScript"),
1351                servers(&["typescript-language-server", "eslint"]),
1352                "user's per-language disable should still apply outside of the project"
1353            );
1354        });
1355    }
1356
1357    #[gpui::test]
1358    fn test_language_servers_combined_restrictions(cx: &mut TestAppContext) {
1359        cx.update(|cx| {
1360            let mut store = SettingsStore::new(cx, &settings::default_settings());
1361            store.register_setting::<AllLanguageSettings>();
1362
1363            let worktree_id = WorktreeId::from_usize(1);
1364            let root = LocalSettingsPath::InWorktree(rel_path("root").into());
1365            let root_location = Some(SettingsLocation {
1366                worktree_id,
1367                path: rel_path("root/a.ts"),
1368            });
1369
1370            store
1371                .set_user_settings(r#"{"language_servers": ["!vtsls", "..."]}"#, cx)
1372                .unwrap();
1373            store
1374                .set_local_settings(
1375                    worktree_id,
1376                    root,
1377                    LocalSettingsKind::Settings,
1378                    Some(r#"{"language_servers": ["!eslint", "..."]}"#),
1379                    cx,
1380                )
1381                .unwrap();
1382            assert_eq!(
1383                resolved_language_servers(&store, root_location, "Rust"),
1384                servers(&["typescript-language-server", "vtsls"]),
1385                "global lists replace wholesale: disables from different files must not accumulate"
1386            );
1387            assert_eq!(
1388                resolved_language_servers(&store, None, "Rust"),
1389                servers(&["typescript-language-server", "eslint"]),
1390                "user's own disable should still apply outside of the project"
1391            );
1392            assert_eq!(
1393                resolved_language_servers(&store, root_location, "TypeScript"),
1394                servers(&["vtsls", "eslint"]),
1395                "a language with its own list should ignore global lists from every file"
1396            );
1397
1398            store
1399                .set_user_settings(r#"{"language_servers": ["...", "!vtsls"]}"#, cx)
1400                .unwrap();
1401            assert_eq!(
1402                resolved_language_servers(&store, None, "Rust"),
1403                servers(&["typescript-language-server", "eslint"]),
1404                "the position of a disabled entry relative to '...' should not matter"
1405            );
1406
1407            store
1408                .set_user_settings(r#"{"language_servers": ["eslint", "..."]}"#, cx)
1409                .unwrap();
1410            assert_eq!(
1411                resolved_language_servers(&store, None, "Rust"),
1412                servers(&["eslint", "typescript-language-server", "vtsls"]),
1413                "the position of '...' should determine the priority order of enabled servers"
1414            );
1415
1416            store
1417                .set_user_settings(r#"{"language_servers": ["eslint"]}"#, cx)
1418                .unwrap();
1419            assert_eq!(
1420                resolved_language_servers(&store, None, "Rust"),
1421                servers(&["eslint"]),
1422                "a global list without '...' should be exhaustive for languages without their own list"
1423            );
1424            assert_eq!(
1425                resolved_language_servers(&store, None, "TypeScript"),
1426                servers(&["vtsls", "eslint"]),
1427                "a global list, exhaustive or not, never applies to languages with their own list"
1428            );
1429
1430            store
1431                .set_user_settings(
1432                    r#"{
1433                        "language_servers": ["!eslint", "..."],
1434                        "languages": {"TypeScript": {"language_servers": ["vtsls", "..."]}}
1435                    }"#,
1436                    cx,
1437                )
1438                .unwrap();
1439            assert_eq!(
1440                resolved_language_servers(&store, None, "TypeScript"),
1441                servers(&["vtsls", "typescript-language-server", "eslint"]),
1442                "authoring a per-language list opts the language out of the global list entirely"
1443            );
1444            assert_eq!(
1445                resolved_language_servers(&store, None, "Rust"),
1446                servers(&["typescript-language-server", "vtsls"]),
1447            );
1448
1449            // Issue #60763: to disable a server that the shipped list explicitly
1450            // enables, the spelling is per-language, restating the shipped
1451            // exclusions.
1452            store
1453                .set_user_settings(
1454                    r#"{"languages": {"TypeScript": {"language_servers": ["!typescript-language-server", "!vtsls", "..."]}}}"#,
1455                    cx,
1456                )
1457                .unwrap();
1458            assert_eq!(
1459                resolved_language_servers(&store, None, "TypeScript"),
1460                servers(&["eslint"]),
1461                "a per-language disable should win over the shipped explicit enable"
1462            );
1463            store
1464                .set_user_settings(
1465                    r#"{"languages": {"TypeScript": {"language_servers": ["!vtsls", "..."]}}}"#,
1466                    cx,
1467                )
1468                .unwrap();
1469            assert_eq!(
1470                resolved_language_servers(&store, None, "TypeScript"),
1471                servers(&["typescript-language-server", "eslint"]),
1472                "a per-language list not restating the shipped exclusions should lift them"
1473            );
1474
1475            store
1476                .set_user_settings(
1477                    r#"{"languages": {"TypeScript": {"language_servers": ["eslint"]}}}"#,
1478                    cx,
1479                )
1480                .unwrap();
1481            assert_eq!(
1482                resolved_language_servers(&store, None, "TypeScript"),
1483                servers(&["eslint"]),
1484                "an exhaustive per-language list should pin exactly the listed servers"
1485            );
1486        });
1487    }
1488
1489    fn servers(names: &[&str]) -> Vec<LanguageServerName> {
1490        names
1491            .iter()
1492            .map(|name| LanguageServerName(name.to_string().into()))
1493            .collect::<Vec<_>>()
1494    }
1495
1496    fn resolved_language_servers(
1497        store: &SettingsStore,
1498        location: Option<SettingsLocation>,
1499        language: &str,
1500    ) -> Vec<LanguageServerName> {
1501        let all_settings = store.get::<AllLanguageSettings>(location);
1502        let language_settings = all_settings
1503            .languages
1504            .get(&LanguageName::new(language))
1505            .unwrap_or(&all_settings.defaults);
1506        language_settings.customized_language_servers(&servers(&[
1507            "typescript-language-server",
1508            "vtsls",
1509            "eslint",
1510        ]))
1511    }
1512}
1513
Served at tenant.openagents/omega Member data and write actions are omitted.