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