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