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