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 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 /// Toggles inlay hints (hides or shows) when the user presses the modifiers specified.
936 /// If only a subset of the modifiers specified is pressed, hints are not toggled.
937 /// If no modifiers are specified, this is equivalent to `None`.
938 ///
939 /// Default: None
940 #[serde(default)]
941 pub toggle_on_modifiers_press: Option<Modifiers>,
942}
943
944fn edit_debounce_ms() -> u64 {
945 700
946}
947
948fn scroll_debounce_ms() -> u64 {
949 50
950}
951
952/// The task settings for a particular language.
953#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema)]
954pub struct LanguageTaskConfig {
955 /// Extra task variables to set for a particular language.
956 pub variables: HashMap<String, String>,
957}
958
959impl InlayHintSettings {
960 /// Returns the kinds of inlay hints that are enabled based on the settings.
961 pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
962 let mut kinds = HashSet::default();
963 if self.show_type_hints {
964 kinds.insert(Some(InlayHintKind::Type));
965 }
966 if self.show_parameter_hints {
967 kinds.insert(Some(InlayHintKind::Parameter));
968 }
969 if self.show_other_hints {
970 kinds.insert(None);
971 }
972 kinds
973 }
974}
975
976impl AllLanguageSettings {
977 /// Returns the [`LanguageSettings`] for the language with the specified name.
978 pub fn language<'a>(
979 &'a self,
980 location: Option<SettingsLocation<'a>>,
981 language_name: Option<&LanguageName>,
982 cx: &'a App,
983 ) -> Cow<'a, LanguageSettings> {
984 let settings = language_name
985 .and_then(|name| self.languages.get(name))
986 .unwrap_or(&self.defaults);
987
988 let editorconfig_properties = location.and_then(|location| {
989 cx.global::<SettingsStore>()
990 .editorconfig_properties(location.worktree_id, location.path)
991 });
992 if let Some(editorconfig_properties) = editorconfig_properties {
993 let mut settings = settings.clone();
994 merge_with_editorconfig(&mut settings, &editorconfig_properties);
995 Cow::Owned(settings)
996 } else {
997 Cow::Borrowed(settings)
998 }
999 }
1000
1001 /// Returns whether edit predictions are enabled for the given path.
1002 pub fn edit_predictions_enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
1003 self.edit_predictions.enabled_for_file(file, cx)
1004 }
1005
1006 /// Returns whether edit predictions are enabled for the given language and path.
1007 pub fn show_edit_predictions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
1008 self.language(None, language.map(|l| l.name()).as_ref(), cx)
1009 .show_edit_predictions
1010 }
1011
1012 /// Returns the edit predictions preview mode for the given language and path.
1013 pub fn edit_predictions_mode(&self) -> EditPredictionsMode {
1014 self.edit_predictions.mode
1015 }
1016}
1017
1018fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
1019 let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
1020 IndentSize::Value(u) => NonZeroU32::new(u as u32),
1021 IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
1022 TabWidth::Value(u) => NonZeroU32::new(u as u32),
1023 }),
1024 });
1025 let hard_tabs = cfg
1026 .get::<IndentStyle>()
1027 .map(|v| v.eq(&IndentStyle::Tabs))
1028 .ok();
1029 let ensure_final_newline_on_save = cfg
1030 .get::<FinalNewline>()
1031 .map(|v| match v {
1032 FinalNewline::Value(b) => b,
1033 })
1034 .ok();
1035 let remove_trailing_whitespace_on_save = cfg
1036 .get::<TrimTrailingWs>()
1037 .map(|v| match v {
1038 TrimTrailingWs::Value(b) => b,
1039 })
1040 .ok();
1041 fn merge<T>(target: &mut T, value: Option<T>) {
1042 if let Some(value) = value {
1043 *target = value;
1044 }
1045 }
1046 merge(&mut settings.tab_size, tab_size);
1047 merge(&mut settings.hard_tabs, hard_tabs);
1048 merge(
1049 &mut settings.remove_trailing_whitespace_on_save,
1050 remove_trailing_whitespace_on_save,
1051 );
1052 merge(
1053 &mut settings.ensure_final_newline_on_save,
1054 ensure_final_newline_on_save,
1055 );
1056}
1057
1058/// The kind of an inlay hint.
1059#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1060pub enum InlayHintKind {
1061 /// An inlay hint for a type.
1062 Type,
1063 /// An inlay hint for a parameter.
1064 Parameter,
1065}
1066
1067impl InlayHintKind {
1068 /// Returns the [`InlayHintKind`] from the given name.
1069 ///
1070 /// Returns `None` if `name` does not match any of the expected
1071 /// string representations.
1072 pub fn from_name(name: &str) -> Option<Self> {
1073 match name {
1074 "type" => Some(InlayHintKind::Type),
1075 "parameter" => Some(InlayHintKind::Parameter),
1076 _ => None,
1077 }
1078 }
1079
1080 /// Returns the name of this [`InlayHintKind`].
1081 pub fn name(&self) -> &'static str {
1082 match self {
1083 InlayHintKind::Type => "type",
1084 InlayHintKind::Parameter => "parameter",
1085 }
1086 }
1087}
1088
1089impl settings::Settings for AllLanguageSettings {
1090 const KEY: Option<&'static str> = None;
1091
1092 type FileContent = AllLanguageSettingsContent;
1093
1094 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1095 let default_value = sources.default;
1096
1097 // A default is provided for all settings.
1098 let mut defaults: LanguageSettings =
1099 serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
1100
1101 let mut languages = HashMap::default();
1102 for (language_name, settings) in &default_value.languages {
1103 let mut language_settings = defaults.clone();
1104 merge_settings(&mut language_settings, settings);
1105 languages.insert(language_name.clone(), language_settings);
1106 }
1107
1108 let mut copilot_enabled = default_value.features.as_ref().and_then(|f| f.copilot);
1109 let mut edit_prediction_provider = default_value
1110 .features
1111 .as_ref()
1112 .and_then(|f| f.edit_prediction_provider);
1113 let mut edit_predictions_mode = default_value
1114 .edit_predictions
1115 .as_ref()
1116 .map(|edit_predictions| edit_predictions.mode)
1117 .ok_or_else(Self::missing_default)?;
1118
1119 let mut completion_globs: HashSet<&String> = default_value
1120 .edit_predictions
1121 .as_ref()
1122 .and_then(|c| c.disabled_globs.as_ref())
1123 .map(|globs| globs.iter().collect())
1124 .ok_or_else(Self::missing_default)?;
1125
1126 let mut copilot_settings = default_value
1127 .edit_predictions
1128 .as_ref()
1129 .map(|settings| settings.copilot.clone())
1130 .map(|copilot| CopilotSettings {
1131 proxy: copilot.proxy,
1132 proxy_no_verify: copilot.proxy_no_verify,
1133 })
1134 .unwrap_or_default();
1135
1136 let mut edit_predictions_enabled_in_assistant = default_value
1137 .edit_predictions
1138 .as_ref()
1139 .map(|settings| settings.enabled_in_assistant)
1140 .unwrap_or(true);
1141
1142 let mut file_types: HashMap<Arc<str>, GlobSet> = HashMap::default();
1143
1144 for (language, suffixes) in &default_value.file_types {
1145 let mut builder = GlobSetBuilder::new();
1146
1147 for suffix in suffixes {
1148 builder.add(Glob::new(suffix)?);
1149 }
1150
1151 file_types.insert(language.clone(), builder.build()?);
1152 }
1153
1154 for user_settings in sources.customizations() {
1155 if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
1156 copilot_enabled = Some(copilot);
1157 }
1158 if let Some(provider) = user_settings
1159 .features
1160 .as_ref()
1161 .and_then(|f| f.edit_prediction_provider)
1162 {
1163 edit_prediction_provider = Some(provider);
1164 }
1165
1166 if let Some(edit_predictions) = user_settings.edit_predictions.as_ref() {
1167 edit_predictions_mode = edit_predictions.mode;
1168 edit_predictions_enabled_in_assistant = edit_predictions.enabled_in_assistant;
1169
1170 if let Some(disabled_globs) = edit_predictions.disabled_globs.as_ref() {
1171 completion_globs.extend(disabled_globs.iter());
1172 }
1173 }
1174
1175 if let Some(proxy) = user_settings
1176 .edit_predictions
1177 .as_ref()
1178 .and_then(|settings| settings.copilot.proxy.clone())
1179 {
1180 copilot_settings.proxy = Some(proxy);
1181 }
1182
1183 if let Some(proxy_no_verify) = user_settings
1184 .edit_predictions
1185 .as_ref()
1186 .and_then(|settings| settings.copilot.proxy_no_verify)
1187 {
1188 copilot_settings.proxy_no_verify = Some(proxy_no_verify);
1189 }
1190
1191 // A user's global settings override the default global settings and
1192 // all default language-specific settings.
1193 merge_settings(&mut defaults, &user_settings.defaults);
1194 for language_settings in languages.values_mut() {
1195 merge_settings(language_settings, &user_settings.defaults);
1196 }
1197
1198 // A user's language-specific settings override default language-specific settings.
1199 for (language_name, user_language_settings) in &user_settings.languages {
1200 merge_settings(
1201 languages
1202 .entry(language_name.clone())
1203 .or_insert_with(|| defaults.clone()),
1204 user_language_settings,
1205 );
1206 }
1207
1208 for (language, suffixes) in &user_settings.file_types {
1209 let mut builder = GlobSetBuilder::new();
1210
1211 let default_value = default_value.file_types.get(&language.clone());
1212
1213 // Merge the default value with the user's value.
1214 if let Some(suffixes) = default_value {
1215 for suffix in suffixes {
1216 builder.add(Glob::new(suffix)?);
1217 }
1218 }
1219
1220 for suffix in suffixes {
1221 builder.add(Glob::new(suffix)?);
1222 }
1223
1224 file_types.insert(language.clone(), builder.build()?);
1225 }
1226 }
1227
1228 Ok(Self {
1229 edit_predictions: EditPredictionSettings {
1230 provider: if let Some(provider) = edit_prediction_provider {
1231 provider
1232 } else if copilot_enabled.unwrap_or(true) {
1233 EditPredictionProvider::Copilot
1234 } else {
1235 EditPredictionProvider::None
1236 },
1237 disabled_globs: completion_globs
1238 .iter()
1239 .filter_map(|g| {
1240 Some(DisabledGlob {
1241 matcher: globset::Glob::new(g).ok()?.compile_matcher(),
1242 is_absolute: Path::new(g).is_absolute(),
1243 })
1244 })
1245 .collect(),
1246 mode: edit_predictions_mode,
1247 copilot: copilot_settings,
1248 enabled_in_assistant: edit_predictions_enabled_in_assistant,
1249 },
1250 defaults,
1251 languages,
1252 file_types,
1253 })
1254 }
1255
1256 fn json_schema(
1257 generator: &mut schemars::gen::SchemaGenerator,
1258 params: &settings::SettingsJsonSchemaParams,
1259 _: &App,
1260 ) -> schemars::schema::RootSchema {
1261 let mut root_schema = generator.root_schema_for::<Self::FileContent>();
1262
1263 // Create a schema for a 'languages overrides' object, associating editor
1264 // settings with specific languages.
1265 assert!(root_schema
1266 .definitions
1267 .contains_key("LanguageSettingsContent"));
1268
1269 let languages_object_schema = SchemaObject {
1270 instance_type: Some(InstanceType::Object.into()),
1271 object: Some(Box::new(ObjectValidation {
1272 properties: params
1273 .language_names
1274 .iter()
1275 .map(|name| {
1276 (
1277 name.clone(),
1278 Schema::new_ref("#/definitions/LanguageSettingsContent".into()),
1279 )
1280 })
1281 .collect(),
1282 ..Default::default()
1283 })),
1284 ..Default::default()
1285 };
1286
1287 root_schema
1288 .definitions
1289 .extend([("Languages".into(), languages_object_schema.into())]);
1290
1291 add_references_to_properties(
1292 &mut root_schema,
1293 &[("languages", "#/definitions/Languages")],
1294 );
1295
1296 root_schema
1297 }
1298}
1299
1300fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
1301 fn merge<T>(target: &mut T, value: Option<T>) {
1302 if let Some(value) = value {
1303 *target = value;
1304 }
1305 }
1306
1307 merge(&mut settings.tab_size, src.tab_size);
1308 settings.tab_size = settings
1309 .tab_size
1310 .clamp(NonZeroU32::new(1).unwrap(), NonZeroU32::new(16).unwrap());
1311
1312 merge(&mut settings.hard_tabs, src.hard_tabs);
1313 merge(&mut settings.soft_wrap, src.soft_wrap);
1314 merge(&mut settings.use_autoclose, src.use_autoclose);
1315 merge(&mut settings.use_auto_surround, src.use_auto_surround);
1316 merge(&mut settings.use_on_type_format, src.use_on_type_format);
1317 merge(&mut settings.auto_indent_on_paste, src.auto_indent_on_paste);
1318 merge(
1319 &mut settings.always_treat_brackets_as_autoclosed,
1320 src.always_treat_brackets_as_autoclosed,
1321 );
1322 merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
1323 merge(&mut settings.wrap_guides, src.wrap_guides.clone());
1324 merge(&mut settings.indent_guides, src.indent_guides);
1325 merge(
1326 &mut settings.code_actions_on_format,
1327 src.code_actions_on_format.clone(),
1328 );
1329 merge(&mut settings.linked_edits, src.linked_edits);
1330 merge(&mut settings.tasks, src.tasks.clone());
1331
1332 merge(
1333 &mut settings.preferred_line_length,
1334 src.preferred_line_length,
1335 );
1336 merge(&mut settings.formatter, src.formatter.clone());
1337 merge(&mut settings.prettier, src.prettier.clone());
1338 merge(&mut settings.format_on_save, src.format_on_save.clone());
1339 merge(
1340 &mut settings.remove_trailing_whitespace_on_save,
1341 src.remove_trailing_whitespace_on_save,
1342 );
1343 merge(
1344 &mut settings.ensure_final_newline_on_save,
1345 src.ensure_final_newline_on_save,
1346 );
1347 merge(
1348 &mut settings.enable_language_server,
1349 src.enable_language_server,
1350 );
1351 merge(&mut settings.language_servers, src.language_servers.clone());
1352 merge(&mut settings.allow_rewrap, src.allow_rewrap);
1353 merge(
1354 &mut settings.show_edit_predictions,
1355 src.show_edit_predictions,
1356 );
1357 merge(
1358 &mut settings.edit_predictions_disabled_in,
1359 src.edit_predictions_disabled_in.clone(),
1360 );
1361 merge(&mut settings.show_whitespaces, src.show_whitespaces);
1362 merge(
1363 &mut settings.extend_comment_on_newline,
1364 src.extend_comment_on_newline,
1365 );
1366 merge(&mut settings.inlay_hints, src.inlay_hints);
1367 merge(
1368 &mut settings.show_completions_on_input,
1369 src.show_completions_on_input,
1370 );
1371 merge(
1372 &mut settings.show_completion_documentation,
1373 src.show_completion_documentation,
1374 );
1375}
1376
1377/// Allows to enable/disable formatting with Prettier
1378/// and configure default Prettier, used when no project-level Prettier installation is found.
1379/// Prettier formatting is disabled by default.
1380#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1381pub struct PrettierSettings {
1382 /// Enables or disables formatting with Prettier for a given language.
1383 #[serde(default)]
1384 pub allowed: bool,
1385
1386 /// Forces Prettier integration to use a specific parser name when formatting files with the language.
1387 #[serde(default)]
1388 pub parser: Option<String>,
1389
1390 /// Forces Prettier integration to use specific plugins when formatting files with the language.
1391 /// The default Prettier will be installed with these plugins.
1392 #[serde(default)]
1393 pub plugins: HashSet<String>,
1394
1395 /// Default Prettier options, in the format as in package.json section for Prettier.
1396 /// If project installs Prettier via its package.json, these options will be ignored.
1397 #[serde(flatten)]
1398 pub options: HashMap<String, serde_json::Value>,
1399}
1400
1401#[cfg(test)]
1402mod tests {
1403 use gpui::TestAppContext;
1404
1405 use super::*;
1406
1407 #[test]
1408 fn test_formatter_deserialization() {
1409 let raw_auto = "{\"formatter\": \"auto\"}";
1410 let settings: LanguageSettingsContent = serde_json::from_str(raw_auto).unwrap();
1411 assert_eq!(settings.formatter, Some(SelectedFormatter::Auto));
1412 let raw = "{\"formatter\": \"language_server\"}";
1413 let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1414 assert_eq!(
1415 settings.formatter,
1416 Some(SelectedFormatter::List(FormatterList(
1417 Formatter::LanguageServer { name: None }.into()
1418 )))
1419 );
1420 let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}]}";
1421 let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1422 assert_eq!(
1423 settings.formatter,
1424 Some(SelectedFormatter::List(FormatterList(
1425 vec![Formatter::LanguageServer { name: None }].into()
1426 )))
1427 );
1428 let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"prettier\"]}";
1429 let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1430 assert_eq!(
1431 settings.formatter,
1432 Some(SelectedFormatter::List(FormatterList(
1433 vec![
1434 Formatter::LanguageServer { name: None },
1435 Formatter::Prettier
1436 ]
1437 .into()
1438 )))
1439 );
1440 }
1441
1442 #[test]
1443 fn test_formatter_deserialization_invalid() {
1444 let raw_auto = "{\"formatter\": {}}";
1445 let result: Result<LanguageSettingsContent, _> = serde_json::from_str(raw_auto);
1446 assert!(result.is_err());
1447 }
1448
1449 #[gpui::test]
1450 fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
1451 use crate::TestFile;
1452 use std::path::PathBuf;
1453
1454 let cx = cx.app.borrow_mut();
1455
1456 let build_settings = |globs: &[&str]| -> EditPredictionSettings {
1457 EditPredictionSettings {
1458 disabled_globs: globs
1459 .iter()
1460 .map(|glob_str| {
1461 #[cfg(windows)]
1462 let glob_str = {
1463 let mut g = String::new();
1464
1465 if glob_str.starts_with('/') {
1466 g.push_str("C:");
1467 }
1468
1469 g.push_str(&glob_str.replace('/', "\\"));
1470 g
1471 };
1472 #[cfg(windows)]
1473 let glob_str = glob_str.as_str();
1474
1475 DisabledGlob {
1476 matcher: globset::Glob::new(glob_str).unwrap().compile_matcher(),
1477 is_absolute: Path::new(glob_str).is_absolute(),
1478 }
1479 })
1480 .collect(),
1481 ..Default::default()
1482 }
1483 };
1484
1485 const WORKTREE_NAME: &str = "project";
1486 let make_test_file = |segments: &[&str]| -> Arc<dyn File> {
1487 let mut path_buf = PathBuf::new();
1488 path_buf.extend(segments);
1489
1490 Arc::new(TestFile {
1491 path: path_buf.as_path().into(),
1492 root_name: WORKTREE_NAME.to_string(),
1493 local_root: Some(PathBuf::from(if cfg!(windows) {
1494 "C:\\absolute\\"
1495 } else {
1496 "/absolute/"
1497 })),
1498 })
1499 };
1500
1501 let test_file = make_test_file(&["src", "test", "file.rs"]);
1502
1503 // Test relative globs
1504 let settings = build_settings(&["*.rs"]);
1505 assert!(!settings.enabled_for_file(&test_file, &cx));
1506 let settings = build_settings(&["*.txt"]);
1507 assert!(settings.enabled_for_file(&test_file, &cx));
1508
1509 // Test absolute globs
1510 let settings = build_settings(&["/absolute/**/*.rs"]);
1511 assert!(!settings.enabled_for_file(&test_file, &cx));
1512 let settings = build_settings(&["/other/**/*.rs"]);
1513 assert!(settings.enabled_for_file(&test_file, &cx));
1514
1515 // Test exact path match relative
1516 let settings = build_settings(&["src/test/file.rs"]);
1517 assert!(!settings.enabled_for_file(&test_file, &cx));
1518 let settings = build_settings(&["src/test/otherfile.rs"]);
1519 assert!(settings.enabled_for_file(&test_file, &cx));
1520
1521 // Test exact path match absolute
1522 let settings = build_settings(&[&format!("/absolute/{}/src/test/file.rs", WORKTREE_NAME)]);
1523 assert!(!settings.enabled_for_file(&test_file, &cx));
1524 let settings = build_settings(&["/other/test/otherfile.rs"]);
1525 assert!(settings.enabled_for_file(&test_file, &cx));
1526
1527 // Test * glob
1528 let settings = build_settings(&["*"]);
1529 assert!(!settings.enabled_for_file(&test_file, &cx));
1530 let settings = build_settings(&["*.txt"]);
1531 assert!(settings.enabled_for_file(&test_file, &cx));
1532
1533 // Test **/* glob
1534 let settings = build_settings(&["**/*"]);
1535 assert!(!settings.enabled_for_file(&test_file, &cx));
1536 let settings = build_settings(&["other/**/*"]);
1537 assert!(settings.enabled_for_file(&test_file, &cx));
1538
1539 // Test directory/** glob
1540 let settings = build_settings(&["src/**"]);
1541 assert!(!settings.enabled_for_file(&test_file, &cx));
1542
1543 let test_file_root: Arc<dyn File> = Arc::new(TestFile {
1544 path: PathBuf::from("file.rs").as_path().into(),
1545 root_name: WORKTREE_NAME.to_string(),
1546 local_root: Some(PathBuf::from("/absolute/")),
1547 });
1548 assert!(settings.enabled_for_file(&test_file_root, &cx));
1549
1550 let settings = build_settings(&["other/**"]);
1551 assert!(settings.enabled_for_file(&test_file, &cx));
1552
1553 // Test **/directory/* glob
1554 let settings = build_settings(&["**/test/*"]);
1555 assert!(!settings.enabled_for_file(&test_file, &cx));
1556 let settings = build_settings(&["**/other/*"]);
1557 assert!(settings.enabled_for_file(&test_file, &cx));
1558
1559 // Test multiple globs
1560 let settings = build_settings(&["*.rs", "*.txt", "src/**"]);
1561 assert!(!settings.enabled_for_file(&test_file, &cx));
1562 let settings = build_settings(&["*.txt", "*.md", "other/**"]);
1563 assert!(settings.enabled_for_file(&test_file, &cx));
1564
1565 // Test dot files
1566 let dot_file = make_test_file(&[".config", "settings.json"]);
1567 let settings = build_settings(&[".*/**"]);
1568 assert!(!settings.enabled_for_file(&dot_file, &cx));
1569
1570 let dot_env_file = make_test_file(&[".env"]);
1571 let settings = build_settings(&[".env"]);
1572 assert!(!settings.enabled_for_file(&dot_env_file, &cx));
1573 }
1574
1575 #[test]
1576 pub fn test_resolve_language_servers() {
1577 fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {
1578 names
1579 .iter()
1580 .copied()
1581 .map(|name| LanguageServerName(name.to_string().into()))
1582 .collect::<Vec<_>>()
1583 }
1584
1585 let available_language_servers = language_server_names(&[
1586 "typescript-language-server",
1587 "biome",
1588 "deno",
1589 "eslint",
1590 "tailwind",
1591 ]);
1592
1593 // A value of just `["..."]` is the same as taking all of the available language servers.
1594 assert_eq!(
1595 LanguageSettings::resolve_language_servers(
1596 &[LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()],
1597 &available_language_servers,
1598 ),
1599 available_language_servers
1600 );
1601
1602 // Referencing one of the available language servers will change its order.
1603 assert_eq!(
1604 LanguageSettings::resolve_language_servers(
1605 &[
1606 "biome".into(),
1607 LanguageSettings::REST_OF_LANGUAGE_SERVERS.into(),
1608 "deno".into()
1609 ],
1610 &available_language_servers
1611 ),
1612 language_server_names(&[
1613 "biome",
1614 "typescript-language-server",
1615 "eslint",
1616 "tailwind",
1617 "deno",
1618 ])
1619 );
1620
1621 // Negating an available language server removes it from the list.
1622 assert_eq!(
1623 LanguageSettings::resolve_language_servers(
1624 &[
1625 "deno".into(),
1626 "!typescript-language-server".into(),
1627 "!biome".into(),
1628 LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1629 ],
1630 &available_language_servers
1631 ),
1632 language_server_names(&["deno", "eslint", "tailwind"])
1633 );
1634
1635 // Adding a language server not in the list of available language servers adds it to the list.
1636 assert_eq!(
1637 LanguageSettings::resolve_language_servers(
1638 &[
1639 "my-cool-language-server".into(),
1640 LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1641 ],
1642 &available_language_servers
1643 ),
1644 language_server_names(&[
1645 "my-cool-language-server",
1646 "typescript-language-server",
1647 "biome",
1648 "deno",
1649 "eslint",
1650 "tailwind",
1651 ])
1652 );
1653 }
1654}