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)]
985#[serde(untagged)]
986pub enum FormatterList {
987 Single(Formatter),
988 Vec(Vec<Formatter>),
989}
990
991impl AsRef<[Formatter]> for FormatterList {
992 fn as_ref(&self) -> &[Formatter] {
993 match &self {
994 Self::Single(single) => slice::from_ref(single),
995 Self::Vec(v) => v,
996 }
997 }
998}
999
1000/// Controls which formatter should be used when formatting code. If there are multiple formatters, they are executed in the order of declaration.
1001#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
1002#[serde(rename_all = "snake_case")]
1003pub enum Formatter {
1004 /// Format code using the current language server.
1005 LanguageServer { name: Option<String> },
1006 /// Format code using Zed's Prettier integration.
1007 Prettier,
1008 /// Format code using an external command.
1009 External {
1010 /// The external program to run.
1011 command: Arc<str>,
1012 /// The arguments to pass to the program.
1013 arguments: Option<Arc<[String]>>,
1014 },
1015 /// Files should be formatted using code actions executed by language servers.
1016 CodeActions(HashMap<String, bool>),
1017}
1018
1019/// The settings for indent guides.
1020#[derive(
1021 Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, SettingsUi,
1022)]
1023pub struct IndentGuideSettings {
1024 /// Whether to display indent guides in the editor.
1025 ///
1026 /// Default: true
1027 #[serde(default = "default_true")]
1028 pub enabled: bool,
1029 /// The width of the indent guides in pixels, between 1 and 10.
1030 ///
1031 /// Default: 1
1032 #[serde(default = "line_width")]
1033 pub line_width: u32,
1034 /// The width of the active indent guide in pixels, between 1 and 10.
1035 ///
1036 /// Default: 1
1037 #[serde(default = "active_line_width")]
1038 pub active_line_width: u32,
1039 /// Determines how indent guides are colored.
1040 ///
1041 /// Default: Fixed
1042 #[serde(default)]
1043 pub coloring: IndentGuideColoring,
1044 /// Determines how indent guide backgrounds are colored.
1045 ///
1046 /// Default: Disabled
1047 #[serde(default)]
1048 pub background_coloring: IndentGuideBackgroundColoring,
1049}
1050
1051fn line_width() -> u32 {
1052 1
1053}
1054
1055fn active_line_width() -> u32 {
1056 line_width()
1057}
1058
1059/// Determines how indent guides are colored.
1060#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1061#[serde(rename_all = "snake_case")]
1062pub enum IndentGuideColoring {
1063 /// Do not render any lines for indent guides.
1064 Disabled,
1065 /// Use the same color for all indentation levels.
1066 #[default]
1067 Fixed,
1068 /// Use a different color for each indentation level.
1069 IndentAware,
1070}
1071
1072/// Determines how indent guide backgrounds are colored.
1073#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1074#[serde(rename_all = "snake_case")]
1075pub enum IndentGuideBackgroundColoring {
1076 /// Do not render any background for indent guides.
1077 #[default]
1078 Disabled,
1079 /// Use a different color for each indentation level.
1080 IndentAware,
1081}
1082
1083/// The settings for inlay hints.
1084#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)]
1085pub struct InlayHintSettings {
1086 /// Global switch to toggle hints on and off.
1087 ///
1088 /// Default: false
1089 #[serde(default)]
1090 pub enabled: bool,
1091 /// Global switch to toggle inline values on and off when debugging.
1092 ///
1093 /// Default: true
1094 #[serde(default = "default_true")]
1095 pub show_value_hints: bool,
1096 /// Whether type hints should be shown.
1097 ///
1098 /// Default: true
1099 #[serde(default = "default_true")]
1100 pub show_type_hints: bool,
1101 /// Whether parameter hints should be shown.
1102 ///
1103 /// Default: true
1104 #[serde(default = "default_true")]
1105 pub show_parameter_hints: bool,
1106 /// Whether other hints should be shown.
1107 ///
1108 /// Default: true
1109 #[serde(default = "default_true")]
1110 pub show_other_hints: bool,
1111 /// Whether to show a background for inlay hints.
1112 ///
1113 /// If set to `true`, the background will use the `hint.background` color
1114 /// from the current theme.
1115 ///
1116 /// Default: false
1117 #[serde(default)]
1118 pub show_background: bool,
1119 /// Whether or not to debounce inlay hints updates after buffer edits.
1120 ///
1121 /// Set to 0 to disable debouncing.
1122 ///
1123 /// Default: 700
1124 #[serde(default = "edit_debounce_ms")]
1125 pub edit_debounce_ms: u64,
1126 /// Whether or not to debounce inlay hints updates after buffer scrolls.
1127 ///
1128 /// Set to 0 to disable debouncing.
1129 ///
1130 /// Default: 50
1131 #[serde(default = "scroll_debounce_ms")]
1132 pub scroll_debounce_ms: u64,
1133 /// Toggles inlay hints (hides or shows) when the user presses the modifiers specified.
1134 /// If only a subset of the modifiers specified is pressed, hints are not toggled.
1135 /// If no modifiers are specified, this is equivalent to `None`.
1136 ///
1137 /// Default: None
1138 #[serde(default)]
1139 pub toggle_on_modifiers_press: Option<Modifiers>,
1140}
1141
1142fn edit_debounce_ms() -> u64 {
1143 700
1144}
1145
1146fn scroll_debounce_ms() -> u64 {
1147 50
1148}
1149
1150/// The task settings for a particular language.
1151#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema, SettingsUi)]
1152pub struct LanguageTaskConfig {
1153 /// Extra task variables to set for a particular language.
1154 #[serde(default)]
1155 pub variables: HashMap<String, String>,
1156 #[serde(default = "default_true")]
1157 pub enabled: bool,
1158 /// Use LSP tasks over Zed language extension ones.
1159 /// If no LSP tasks are returned due to error/timeout or regular execution,
1160 /// Zed language extension tasks will be used instead.
1161 ///
1162 /// Other Zed tasks will still be shown:
1163 /// * Zed task from either of the task config file
1164 /// * Zed task from history (e.g. one-off task was spawned before)
1165 #[serde(default = "default_true")]
1166 pub prefer_lsp: bool,
1167}
1168
1169impl InlayHintSettings {
1170 /// Returns the kinds of inlay hints that are enabled based on the settings.
1171 pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
1172 let mut kinds = HashSet::default();
1173 if self.show_type_hints {
1174 kinds.insert(Some(InlayHintKind::Type));
1175 }
1176 if self.show_parameter_hints {
1177 kinds.insert(Some(InlayHintKind::Parameter));
1178 }
1179 if self.show_other_hints {
1180 kinds.insert(None);
1181 }
1182 kinds
1183 }
1184}
1185
1186impl AllLanguageSettings {
1187 /// Returns the [`LanguageSettings`] for the language with the specified name.
1188 pub fn language<'a>(
1189 &'a self,
1190 location: Option<SettingsLocation<'a>>,
1191 language_name: Option<&LanguageName>,
1192 cx: &'a App,
1193 ) -> Cow<'a, LanguageSettings> {
1194 let settings = language_name
1195 .and_then(|name| self.languages.get(name))
1196 .unwrap_or(&self.defaults);
1197
1198 let editorconfig_properties = location.and_then(|location| {
1199 cx.global::<SettingsStore>()
1200 .editorconfig_properties(location.worktree_id, location.path)
1201 });
1202 if let Some(editorconfig_properties) = editorconfig_properties {
1203 let mut settings = settings.clone();
1204 merge_with_editorconfig(&mut settings, &editorconfig_properties);
1205 Cow::Owned(settings)
1206 } else {
1207 Cow::Borrowed(settings)
1208 }
1209 }
1210
1211 /// Returns whether edit predictions are enabled for the given path.
1212 pub fn edit_predictions_enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
1213 self.edit_predictions.enabled_for_file(file, cx)
1214 }
1215
1216 /// Returns whether edit predictions are enabled for the given language and path.
1217 pub fn show_edit_predictions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
1218 self.language(None, language.map(|l| l.name()).as_ref(), cx)
1219 .show_edit_predictions
1220 }
1221
1222 /// Returns the edit predictions preview mode for the given language and path.
1223 pub fn edit_predictions_mode(&self) -> EditPredictionsMode {
1224 self.edit_predictions.mode
1225 }
1226}
1227
1228fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
1229 let preferred_line_length = cfg.get::<MaxLineLen>().ok().and_then(|v| match v {
1230 MaxLineLen::Value(u) => Some(u as u32),
1231 MaxLineLen::Off => None,
1232 });
1233 let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
1234 IndentSize::Value(u) => NonZeroU32::new(u as u32),
1235 IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
1236 TabWidth::Value(u) => NonZeroU32::new(u as u32),
1237 }),
1238 });
1239 let hard_tabs = cfg
1240 .get::<IndentStyle>()
1241 .map(|v| v.eq(&IndentStyle::Tabs))
1242 .ok();
1243 let ensure_final_newline_on_save = cfg
1244 .get::<FinalNewline>()
1245 .map(|v| match v {
1246 FinalNewline::Value(b) => b,
1247 })
1248 .ok();
1249 let remove_trailing_whitespace_on_save = cfg
1250 .get::<TrimTrailingWs>()
1251 .map(|v| match v {
1252 TrimTrailingWs::Value(b) => b,
1253 })
1254 .ok();
1255 fn merge<T>(target: &mut T, value: Option<T>) {
1256 if let Some(value) = value {
1257 *target = value;
1258 }
1259 }
1260 merge(&mut settings.preferred_line_length, preferred_line_length);
1261 merge(&mut settings.tab_size, tab_size);
1262 merge(&mut settings.hard_tabs, hard_tabs);
1263 merge(
1264 &mut settings.remove_trailing_whitespace_on_save,
1265 remove_trailing_whitespace_on_save,
1266 );
1267 merge(
1268 &mut settings.ensure_final_newline_on_save,
1269 ensure_final_newline_on_save,
1270 );
1271}
1272
1273/// The kind of an inlay hint.
1274#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1275pub enum InlayHintKind {
1276 /// An inlay hint for a type.
1277 Type,
1278 /// An inlay hint for a parameter.
1279 Parameter,
1280}
1281
1282impl InlayHintKind {
1283 /// Returns the [`InlayHintKind`] from the given name.
1284 ///
1285 /// Returns `None` if `name` does not match any of the expected
1286 /// string representations.
1287 pub fn from_name(name: &str) -> Option<Self> {
1288 match name {
1289 "type" => Some(InlayHintKind::Type),
1290 "parameter" => Some(InlayHintKind::Parameter),
1291 _ => None,
1292 }
1293 }
1294
1295 /// Returns the name of this [`InlayHintKind`].
1296 pub fn name(&self) -> &'static str {
1297 match self {
1298 InlayHintKind::Type => "type",
1299 InlayHintKind::Parameter => "parameter",
1300 }
1301 }
1302}
1303
1304impl settings::Settings for AllLanguageSettings {
1305 type FileContent = AllLanguageSettingsContent;
1306
1307 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1308 let default_value = sources.default;
1309
1310 // A default is provided for all settings.
1311 let mut defaults: LanguageSettings =
1312 serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
1313
1314 let mut languages = HashMap::default();
1315 for (language_name, settings) in &default_value.languages.0 {
1316 let mut language_settings = defaults.clone();
1317 merge_settings(&mut language_settings, settings);
1318 languages.insert(language_name.clone(), language_settings);
1319 }
1320
1321 let mut edit_prediction_provider = default_value
1322 .features
1323 .as_ref()
1324 .and_then(|f| f.edit_prediction_provider);
1325 let mut edit_predictions_mode = default_value
1326 .edit_predictions
1327 .as_ref()
1328 .map(|edit_predictions| edit_predictions.mode)
1329 .ok_or_else(Self::missing_default)?;
1330
1331 let mut completion_globs: HashSet<&String> = default_value
1332 .edit_predictions
1333 .as_ref()
1334 .and_then(|c| c.disabled_globs.as_ref())
1335 .map(|globs| globs.iter().collect())
1336 .ok_or_else(Self::missing_default)?;
1337
1338 let mut copilot_settings = default_value
1339 .edit_predictions
1340 .as_ref()
1341 .map(|settings| CopilotSettings {
1342 proxy: settings.copilot.proxy.clone(),
1343 proxy_no_verify: settings.copilot.proxy_no_verify,
1344 enterprise_uri: settings.copilot.enterprise_uri.clone(),
1345 })
1346 .unwrap_or_default();
1347
1348 let mut enabled_in_text_threads = default_value
1349 .edit_predictions
1350 .as_ref()
1351 .map(|settings| settings.enabled_in_text_threads)
1352 .unwrap_or(true);
1353
1354 let mut file_types: FxHashMap<Arc<str>, GlobSet> = FxHashMap::default();
1355
1356 for (language, patterns) in &default_value.file_types {
1357 let mut builder = GlobSetBuilder::new();
1358
1359 for pattern in patterns {
1360 builder.add(Glob::new(pattern)?);
1361 }
1362
1363 file_types.insert(language.clone(), builder.build()?);
1364 }
1365
1366 for user_settings in sources.customizations() {
1367 if let Some(provider) = user_settings
1368 .features
1369 .as_ref()
1370 .and_then(|f| f.edit_prediction_provider)
1371 {
1372 edit_prediction_provider = Some(provider);
1373 }
1374
1375 if let Some(edit_predictions) = user_settings.edit_predictions.as_ref() {
1376 edit_predictions_mode = edit_predictions.mode;
1377 enabled_in_text_threads = edit_predictions.enabled_in_text_threads;
1378
1379 if let Some(disabled_globs) = edit_predictions.disabled_globs.as_ref() {
1380 completion_globs.extend(disabled_globs.iter());
1381 }
1382 }
1383
1384 if let Some(proxy) = user_settings
1385 .edit_predictions
1386 .as_ref()
1387 .and_then(|settings| settings.copilot.proxy.clone())
1388 {
1389 copilot_settings.proxy = Some(proxy);
1390 }
1391
1392 if let Some(proxy_no_verify) = user_settings
1393 .edit_predictions
1394 .as_ref()
1395 .and_then(|settings| settings.copilot.proxy_no_verify)
1396 {
1397 copilot_settings.proxy_no_verify = Some(proxy_no_verify);
1398 }
1399
1400 if let Some(enterprise_uri) = user_settings
1401 .edit_predictions
1402 .as_ref()
1403 .and_then(|settings| settings.copilot.enterprise_uri.clone())
1404 {
1405 copilot_settings.enterprise_uri = Some(enterprise_uri);
1406 }
1407
1408 // A user's global settings override the default global settings and
1409 // all default language-specific settings.
1410 merge_settings(&mut defaults, &user_settings.defaults);
1411 for language_settings in languages.values_mut() {
1412 merge_settings(language_settings, &user_settings.defaults);
1413 }
1414
1415 // A user's language-specific settings override default language-specific settings.
1416 for (language_name, user_language_settings) in &user_settings.languages.0 {
1417 merge_settings(
1418 languages
1419 .entry(language_name.clone())
1420 .or_insert_with(|| defaults.clone()),
1421 user_language_settings,
1422 );
1423 }
1424
1425 for (language, patterns) in &user_settings.file_types {
1426 let mut builder = GlobSetBuilder::new();
1427
1428 let default_value = default_value.file_types.get(&language.clone());
1429
1430 // Merge the default value with the user's value.
1431 if let Some(patterns) = default_value {
1432 for pattern in patterns {
1433 builder.add(Glob::new(pattern)?);
1434 }
1435 }
1436
1437 for pattern in patterns {
1438 builder.add(Glob::new(pattern)?);
1439 }
1440
1441 file_types.insert(language.clone(), builder.build()?);
1442 }
1443 }
1444
1445 Ok(Self {
1446 edit_predictions: EditPredictionSettings {
1447 provider: if let Some(provider) = edit_prediction_provider {
1448 provider
1449 } else {
1450 EditPredictionProvider::None
1451 },
1452 disabled_globs: completion_globs
1453 .iter()
1454 .filter_map(|g| {
1455 let expanded_g = shellexpand::tilde(g).into_owned();
1456 Some(DisabledGlob {
1457 matcher: globset::Glob::new(&expanded_g).ok()?.compile_matcher(),
1458 is_absolute: Path::new(&expanded_g).is_absolute(),
1459 })
1460 })
1461 .collect(),
1462 mode: edit_predictions_mode,
1463 copilot: copilot_settings,
1464 enabled_in_text_threads,
1465 },
1466 defaults,
1467 languages,
1468 file_types,
1469 })
1470 }
1471
1472 fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
1473 let d = &mut current.defaults;
1474 if let Some(size) = vscode
1475 .read_value("editor.tabSize")
1476 .and_then(|v| v.as_u64())
1477 .and_then(|n| NonZeroU32::new(n as u32))
1478 {
1479 d.tab_size = Some(size);
1480 }
1481 if let Some(v) = vscode.read_bool("editor.insertSpaces") {
1482 d.hard_tabs = Some(!v);
1483 }
1484
1485 vscode.enum_setting("editor.wordWrap", &mut d.soft_wrap, |s| match s {
1486 "on" => Some(SoftWrap::EditorWidth),
1487 "wordWrapColumn" => Some(SoftWrap::PreferLine),
1488 "bounded" => Some(SoftWrap::Bounded),
1489 "off" => Some(SoftWrap::None),
1490 _ => None,
1491 });
1492 vscode.u32_setting("editor.wordWrapColumn", &mut d.preferred_line_length);
1493
1494 if let Some(arr) = vscode
1495 .read_value("editor.rulers")
1496 .and_then(|v| v.as_array())
1497 .map(|v| v.iter().map(|n| n.as_u64().map(|n| n as usize)).collect())
1498 {
1499 d.wrap_guides = arr;
1500 }
1501 if let Some(b) = vscode.read_bool("editor.guides.indentation") {
1502 if let Some(guide_settings) = d.indent_guides.as_mut() {
1503 guide_settings.enabled = b;
1504 } else {
1505 d.indent_guides = Some(IndentGuideSettings {
1506 enabled: b,
1507 ..Default::default()
1508 });
1509 }
1510 }
1511
1512 if let Some(b) = vscode.read_bool("editor.guides.formatOnSave") {
1513 d.format_on_save = Some(if b {
1514 FormatOnSave::On
1515 } else {
1516 FormatOnSave::Off
1517 });
1518 }
1519 vscode.bool_setting(
1520 "editor.trimAutoWhitespace",
1521 &mut d.remove_trailing_whitespace_on_save,
1522 );
1523 vscode.bool_setting(
1524 "files.insertFinalNewline",
1525 &mut d.ensure_final_newline_on_save,
1526 );
1527 vscode.bool_setting("editor.inlineSuggest.enabled", &mut d.show_edit_predictions);
1528 vscode.enum_setting("editor.renderWhitespace", &mut d.show_whitespaces, |s| {
1529 Some(match s {
1530 "boundary" => ShowWhitespaceSetting::Boundary,
1531 "trailing" => ShowWhitespaceSetting::Trailing,
1532 "selection" => ShowWhitespaceSetting::Selection,
1533 "all" => ShowWhitespaceSetting::All,
1534 _ => ShowWhitespaceSetting::None,
1535 })
1536 });
1537 vscode.enum_setting(
1538 "editor.autoSurround",
1539 &mut d.use_auto_surround,
1540 |s| match s {
1541 "languageDefined" | "quotes" | "brackets" => Some(true),
1542 "never" => Some(false),
1543 _ => None,
1544 },
1545 );
1546 vscode.bool_setting("editor.formatOnType", &mut d.use_on_type_format);
1547 vscode.bool_setting("editor.linkedEditing", &mut d.linked_edits);
1548 vscode.bool_setting("editor.formatOnPaste", &mut d.auto_indent_on_paste);
1549 vscode.bool_setting(
1550 "editor.suggestOnTriggerCharacters",
1551 &mut d.show_completions_on_input,
1552 );
1553 if let Some(b) = vscode.read_bool("editor.suggest.showWords") {
1554 let mode = if b {
1555 WordsCompletionMode::Enabled
1556 } else {
1557 WordsCompletionMode::Disabled
1558 };
1559 if let Some(completion_settings) = d.completions.as_mut() {
1560 completion_settings.words = mode;
1561 } else {
1562 d.completions = Some(CompletionSettings {
1563 words: mode,
1564 words_min_length: 3,
1565 lsp: true,
1566 lsp_fetch_timeout_ms: 0,
1567 lsp_insert_mode: LspInsertMode::ReplaceSuffix,
1568 });
1569 }
1570 }
1571 // TODO: pull ^ out into helper and reuse for per-language settings
1572
1573 // vscodes file association map is inverted from ours, so we flip the mapping before merging
1574 let mut associations: HashMap<Arc<str>, Vec<String>> = HashMap::default();
1575 if let Some(map) = vscode
1576 .read_value("files.associations")
1577 .and_then(|v| v.as_object())
1578 {
1579 for (k, v) in map {
1580 let Some(v) = v.as_str() else { continue };
1581 associations.entry(v.into()).or_default().push(k.clone());
1582 }
1583 }
1584
1585 // TODO: do we want to merge imported globs per filetype? for now we'll just replace
1586 current.file_types.extend(associations);
1587
1588 // cursor global ignore list applies to cursor-tab, so transfer it to edit_predictions.disabled_globs
1589 if let Some(disabled_globs) = vscode
1590 .read_value("cursor.general.globalCursorIgnoreList")
1591 .and_then(|v| v.as_array())
1592 {
1593 current
1594 .edit_predictions
1595 .get_or_insert_default()
1596 .disabled_globs
1597 .get_or_insert_default()
1598 .extend(
1599 disabled_globs
1600 .iter()
1601 .filter_map(|glob| glob.as_str())
1602 .map(|s| s.to_string()),
1603 );
1604 }
1605 }
1606}
1607
1608fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
1609 fn merge<T>(target: &mut T, value: Option<T>) {
1610 if let Some(value) = value {
1611 *target = value;
1612 }
1613 }
1614
1615 merge(&mut settings.tab_size, src.tab_size);
1616 settings.tab_size = settings
1617 .tab_size
1618 .clamp(NonZeroU32::new(1).unwrap(), NonZeroU32::new(16).unwrap());
1619
1620 merge(&mut settings.hard_tabs, src.hard_tabs);
1621 merge(&mut settings.soft_wrap, src.soft_wrap);
1622 merge(&mut settings.use_autoclose, src.use_autoclose);
1623 merge(&mut settings.use_auto_surround, src.use_auto_surround);
1624 merge(&mut settings.use_on_type_format, src.use_on_type_format);
1625 merge(&mut settings.auto_indent, src.auto_indent);
1626 merge(&mut settings.auto_indent_on_paste, src.auto_indent_on_paste);
1627 merge(
1628 &mut settings.always_treat_brackets_as_autoclosed,
1629 src.always_treat_brackets_as_autoclosed,
1630 );
1631 merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
1632 merge(&mut settings.wrap_guides, src.wrap_guides.clone());
1633 merge(&mut settings.indent_guides, src.indent_guides);
1634 merge(
1635 &mut settings.code_actions_on_format,
1636 src.code_actions_on_format.clone(),
1637 );
1638 merge(&mut settings.linked_edits, src.linked_edits);
1639 merge(&mut settings.tasks, src.tasks.clone());
1640
1641 merge(
1642 &mut settings.preferred_line_length,
1643 src.preferred_line_length,
1644 );
1645 merge(&mut settings.formatter, src.formatter.clone());
1646 merge(&mut settings.prettier, src.prettier.clone());
1647 merge(
1648 &mut settings.jsx_tag_auto_close,
1649 src.jsx_tag_auto_close.clone(),
1650 );
1651 merge(&mut settings.format_on_save, src.format_on_save.clone());
1652 merge(
1653 &mut settings.remove_trailing_whitespace_on_save,
1654 src.remove_trailing_whitespace_on_save,
1655 );
1656 merge(
1657 &mut settings.ensure_final_newline_on_save,
1658 src.ensure_final_newline_on_save,
1659 );
1660 merge(
1661 &mut settings.enable_language_server,
1662 src.enable_language_server,
1663 );
1664 merge(&mut settings.language_servers, src.language_servers.clone());
1665 merge(&mut settings.allow_rewrap, src.allow_rewrap);
1666 merge(
1667 &mut settings.show_edit_predictions,
1668 src.show_edit_predictions,
1669 );
1670 merge(
1671 &mut settings.edit_predictions_disabled_in,
1672 src.edit_predictions_disabled_in.clone(),
1673 );
1674 merge(&mut settings.show_whitespaces, src.show_whitespaces);
1675 merge(&mut settings.whitespace_map, src.whitespace_map.clone());
1676 merge(
1677 &mut settings.extend_comment_on_newline,
1678 src.extend_comment_on_newline,
1679 );
1680 merge(&mut settings.inlay_hints, src.inlay_hints);
1681 merge(
1682 &mut settings.show_completions_on_input,
1683 src.show_completions_on_input,
1684 );
1685 merge(
1686 &mut settings.show_completion_documentation,
1687 src.show_completion_documentation,
1688 );
1689 merge(&mut settings.completions, src.completions);
1690}
1691
1692/// Allows to enable/disable formatting with Prettier
1693/// and configure default Prettier, used when no project-level Prettier installation is found.
1694/// Prettier formatting is disabled by default.
1695#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, SettingsUi)]
1696pub struct PrettierSettings {
1697 /// Enables or disables formatting with Prettier for a given language.
1698 #[serde(default)]
1699 pub allowed: bool,
1700
1701 /// Forces Prettier integration to use a specific parser name when formatting files with the language.
1702 #[serde(default)]
1703 pub parser: Option<String>,
1704
1705 /// Forces Prettier integration to use specific plugins when formatting files with the language.
1706 /// The default Prettier will be installed with these plugins.
1707 #[serde(default)]
1708 #[settings_ui(skip)]
1709 pub plugins: HashSet<String>,
1710
1711 /// Default Prettier options, in the format as in package.json section for Prettier.
1712 /// If project installs Prettier via its package.json, these options will be ignored.
1713 #[serde(flatten)]
1714 #[settings_ui(skip)]
1715 pub options: HashMap<String, serde_json::Value>,
1716}
1717
1718#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, SettingsUi)]
1719pub struct JsxTagAutoCloseSettings {
1720 /// Enables or disables auto-closing of JSX tags.
1721 #[serde(default)]
1722 pub enabled: bool,
1723}
1724
1725#[cfg(test)]
1726mod tests {
1727 use gpui::TestAppContext;
1728
1729 use super::*;
1730
1731 #[test]
1732 fn test_formatter_deserialization() {
1733 let raw_auto = "{\"formatter\": \"auto\"}";
1734 let settings: LanguageSettingsContent = serde_json::from_str(raw_auto).unwrap();
1735 assert_eq!(settings.formatter, Some(SelectedFormatter::Auto));
1736 let raw = "{\"formatter\": \"language_server\"}";
1737 let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1738 assert_eq!(
1739 settings.formatter,
1740 Some(SelectedFormatter::List(FormatterList::Single(
1741 Formatter::LanguageServer { name: None }
1742 )))
1743 );
1744 let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}]}";
1745 let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1746 assert_eq!(
1747 settings.formatter,
1748 Some(SelectedFormatter::List(FormatterList::Vec(vec![
1749 Formatter::LanguageServer { name: None }
1750 ])))
1751 );
1752 let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"prettier\"]}";
1753 let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1754 assert_eq!(
1755 settings.formatter,
1756 Some(SelectedFormatter::List(FormatterList::Vec(vec![
1757 Formatter::LanguageServer { name: None },
1758 Formatter::Prettier
1759 ])))
1760 );
1761 }
1762
1763 #[test]
1764 fn test_formatter_deserialization_invalid() {
1765 let raw_auto = "{\"formatter\": {}}";
1766 let result: Result<LanguageSettingsContent, _> = serde_json::from_str(raw_auto);
1767 assert!(result.is_err());
1768 }
1769
1770 #[gpui::test]
1771 fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
1772 use crate::TestFile;
1773 use std::path::PathBuf;
1774
1775 let cx = cx.app.borrow_mut();
1776
1777 let build_settings = |globs: &[&str]| -> EditPredictionSettings {
1778 EditPredictionSettings {
1779 disabled_globs: globs
1780 .iter()
1781 .map(|glob_str| {
1782 #[cfg(windows)]
1783 let glob_str = {
1784 let mut g = String::new();
1785
1786 if glob_str.starts_with('/') {
1787 g.push_str("C:");
1788 }
1789
1790 g.push_str(&glob_str.replace('/', "\\"));
1791 g
1792 };
1793 #[cfg(windows)]
1794 let glob_str = glob_str.as_str();
1795 let expanded_glob_str = shellexpand::tilde(glob_str).into_owned();
1796 DisabledGlob {
1797 matcher: globset::Glob::new(&expanded_glob_str)
1798 .unwrap()
1799 .compile_matcher(),
1800 is_absolute: Path::new(&expanded_glob_str).is_absolute(),
1801 }
1802 })
1803 .collect(),
1804 ..Default::default()
1805 }
1806 };
1807
1808 const WORKTREE_NAME: &str = "project";
1809 let make_test_file = |segments: &[&str]| -> Arc<dyn File> {
1810 let mut path_buf = PathBuf::new();
1811 path_buf.extend(segments);
1812
1813 Arc::new(TestFile {
1814 path: path_buf.as_path().into(),
1815 root_name: WORKTREE_NAME.to_string(),
1816 local_root: Some(PathBuf::from(if cfg!(windows) {
1817 "C:\\absolute\\"
1818 } else {
1819 "/absolute/"
1820 })),
1821 })
1822 };
1823
1824 let test_file = make_test_file(&["src", "test", "file.rs"]);
1825
1826 // Test relative globs
1827 let settings = build_settings(&["*.rs"]);
1828 assert!(!settings.enabled_for_file(&test_file, &cx));
1829 let settings = build_settings(&["*.txt"]);
1830 assert!(settings.enabled_for_file(&test_file, &cx));
1831
1832 // Test absolute globs
1833 let settings = build_settings(&["/absolute/**/*.rs"]);
1834 assert!(!settings.enabled_for_file(&test_file, &cx));
1835 let settings = build_settings(&["/other/**/*.rs"]);
1836 assert!(settings.enabled_for_file(&test_file, &cx));
1837
1838 // Test exact path match relative
1839 let settings = build_settings(&["src/test/file.rs"]);
1840 assert!(!settings.enabled_for_file(&test_file, &cx));
1841 let settings = build_settings(&["src/test/otherfile.rs"]);
1842 assert!(settings.enabled_for_file(&test_file, &cx));
1843
1844 // Test exact path match absolute
1845 let settings = build_settings(&[&format!("/absolute/{}/src/test/file.rs", WORKTREE_NAME)]);
1846 assert!(!settings.enabled_for_file(&test_file, &cx));
1847 let settings = build_settings(&["/other/test/otherfile.rs"]);
1848 assert!(settings.enabled_for_file(&test_file, &cx));
1849
1850 // Test * glob
1851 let settings = build_settings(&["*"]);
1852 assert!(!settings.enabled_for_file(&test_file, &cx));
1853 let settings = build_settings(&["*.txt"]);
1854 assert!(settings.enabled_for_file(&test_file, &cx));
1855
1856 // Test **/* glob
1857 let settings = build_settings(&["**/*"]);
1858 assert!(!settings.enabled_for_file(&test_file, &cx));
1859 let settings = build_settings(&["other/**/*"]);
1860 assert!(settings.enabled_for_file(&test_file, &cx));
1861
1862 // Test directory/** glob
1863 let settings = build_settings(&["src/**"]);
1864 assert!(!settings.enabled_for_file(&test_file, &cx));
1865
1866 let test_file_root: Arc<dyn File> = Arc::new(TestFile {
1867 path: PathBuf::from("file.rs").as_path().into(),
1868 root_name: WORKTREE_NAME.to_string(),
1869 local_root: Some(PathBuf::from("/absolute/")),
1870 });
1871 assert!(settings.enabled_for_file(&test_file_root, &cx));
1872
1873 let settings = build_settings(&["other/**"]);
1874 assert!(settings.enabled_for_file(&test_file, &cx));
1875
1876 // Test **/directory/* glob
1877 let settings = build_settings(&["**/test/*"]);
1878 assert!(!settings.enabled_for_file(&test_file, &cx));
1879 let settings = build_settings(&["**/other/*"]);
1880 assert!(settings.enabled_for_file(&test_file, &cx));
1881
1882 // Test multiple globs
1883 let settings = build_settings(&["*.rs", "*.txt", "src/**"]);
1884 assert!(!settings.enabled_for_file(&test_file, &cx));
1885 let settings = build_settings(&["*.txt", "*.md", "other/**"]);
1886 assert!(settings.enabled_for_file(&test_file, &cx));
1887
1888 // Test dot files
1889 let dot_file = make_test_file(&[".config", "settings.json"]);
1890 let settings = build_settings(&[".*/**"]);
1891 assert!(!settings.enabled_for_file(&dot_file, &cx));
1892
1893 let dot_env_file = make_test_file(&[".env"]);
1894 let settings = build_settings(&[".env"]);
1895 assert!(!settings.enabled_for_file(&dot_env_file, &cx));
1896
1897 // Test tilde expansion
1898 let home = shellexpand::tilde("~").into_owned();
1899 let home_file = make_test_file(&[&home, "test.rs"]);
1900 let settings = build_settings(&["~/test.rs"]);
1901 assert!(!settings.enabled_for_file(&home_file, &cx));
1902 }
1903
1904 #[test]
1905 fn test_resolve_language_servers() {
1906 fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {
1907 names
1908 .iter()
1909 .copied()
1910 .map(|name| LanguageServerName(name.to_string().into()))
1911 .collect::<Vec<_>>()
1912 }
1913
1914 let available_language_servers = language_server_names(&[
1915 "typescript-language-server",
1916 "biome",
1917 "deno",
1918 "eslint",
1919 "tailwind",
1920 ]);
1921
1922 // A value of just `["..."]` is the same as taking all of the available language servers.
1923 assert_eq!(
1924 LanguageSettings::resolve_language_servers(
1925 &[LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()],
1926 &available_language_servers,
1927 ),
1928 available_language_servers
1929 );
1930
1931 // Referencing one of the available language servers will change its order.
1932 assert_eq!(
1933 LanguageSettings::resolve_language_servers(
1934 &[
1935 "biome".into(),
1936 LanguageSettings::REST_OF_LANGUAGE_SERVERS.into(),
1937 "deno".into()
1938 ],
1939 &available_language_servers
1940 ),
1941 language_server_names(&[
1942 "biome",
1943 "typescript-language-server",
1944 "eslint",
1945 "tailwind",
1946 "deno",
1947 ])
1948 );
1949
1950 // Negating an available language server removes it from the list.
1951 assert_eq!(
1952 LanguageSettings::resolve_language_servers(
1953 &[
1954 "deno".into(),
1955 "!typescript-language-server".into(),
1956 "!biome".into(),
1957 LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1958 ],
1959 &available_language_servers
1960 ),
1961 language_server_names(&["deno", "eslint", "tailwind"])
1962 );
1963
1964 // Adding a language server not in the list of available language servers adds it to the list.
1965 assert_eq!(
1966 LanguageSettings::resolve_language_servers(
1967 &[
1968 "my-cool-language-server".into(),
1969 LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1970 ],
1971 &available_language_servers
1972 ),
1973 language_server_names(&[
1974 "my-cool-language-server",
1975 "typescript-language-server",
1976 "biome",
1977 "deno",
1978 "eslint",
1979 "tailwind",
1980 ])
1981 );
1982 }
1983}