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