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