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