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