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