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