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