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