1mod agent;
2mod editor;
3mod extension;
4mod language;
5mod language_model;
6mod project;
7mod terminal;
8mod theme;
9mod workspace;
10
11pub use agent::*;
12pub use editor::*;
13pub use extension::*;
14pub use language::*;
15pub use language_model::*;
16pub use project::*;
17pub use terminal::*;
18pub use theme::*;
19pub use workspace::*;
20
21use collections::{HashMap, IndexMap};
22use gpui::{App, SharedString};
23use release_channel::ReleaseChannel;
24use schemars::JsonSchema;
25use serde::{Deserialize, Serialize};
26use serde_with::skip_serializing_none;
27use settings_macros::MergeFrom;
28use std::collections::BTreeSet;
29use std::env;
30use std::sync::Arc;
31pub use util::serde::default_true;
32
33use crate::{ActiveSettingsProfileName, merge_from};
34
35#[skip_serializing_none]
36#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
37pub struct SettingsContent {
38 #[serde(flatten)]
39 pub project: ProjectSettingsContent,
40
41 #[serde(flatten)]
42 pub theme: Box<ThemeSettingsContent>,
43
44 #[serde(flatten)]
45 pub extension: ExtensionSettingsContent,
46
47 #[serde(flatten)]
48 pub workspace: WorkspaceSettingsContent,
49
50 #[serde(flatten)]
51 pub editor: EditorSettingsContent,
52
53 #[serde(flatten)]
54 pub remote: RemoteSettingsContent,
55
56 /// Settings related to the file finder.
57 pub file_finder: Option<FileFinderSettingsContent>,
58
59 pub git_panel: Option<GitPanelSettingsContent>,
60
61 pub tabs: Option<ItemSettingsContent>,
62 pub tab_bar: Option<TabBarSettingsContent>,
63 pub status_bar: Option<StatusBarSettingsContent>,
64
65 pub preview_tabs: Option<PreviewTabsSettingsContent>,
66
67 pub agent: Option<AgentSettingsContent>,
68 pub agent_servers: Option<AllAgentServersSettings>,
69
70 /// Configuration of audio in Zed.
71 pub audio: Option<AudioSettingsContent>,
72
73 /// Whether or not to automatically check for updates.
74 ///
75 /// Default: true
76 pub auto_update: Option<bool>,
77
78 /// This base keymap settings adjusts the default keybindings in Zed to be similar
79 /// to other common code editors. By default, Zed's keymap closely follows VSCode's
80 /// keymap, with minor adjustments, this corresponds to the "VSCode" setting.
81 ///
82 /// Default: VSCode
83 pub base_keymap: Option<BaseKeymapContent>,
84
85 /// Configuration for the collab panel visual settings.
86 pub collaboration_panel: Option<PanelSettingsContent>,
87
88 pub debugger: Option<DebuggerSettingsContent>,
89
90 /// Configuration for Diagnostics-related features.
91 pub diagnostics: Option<DiagnosticsSettingsContent>,
92
93 /// Configuration for Git-related features
94 pub git: Option<GitSettings>,
95
96 /// Common language server settings.
97 pub global_lsp_settings: Option<GlobalLspSettingsContent>,
98
99 /// The settings for the image viewer.
100 pub image_viewer: Option<ImageViewerSettingsContent>,
101
102 pub repl: Option<ReplSettingsContent>,
103
104 /// Whether or not to enable Helix mode.
105 ///
106 /// Default: false
107 pub helix_mode: Option<bool>,
108
109 pub journal: Option<JournalSettingsContent>,
110
111 /// A map of log scopes to the desired log level.
112 /// Useful for filtering out noisy logs or enabling more verbose logging.
113 ///
114 /// Example: {"log": {"client": "warn"}}
115 pub log: Option<HashMap<String, String>>,
116
117 pub line_indicator_format: Option<LineIndicatorFormat>,
118
119 pub language_models: Option<AllLanguageModelSettingsContent>,
120
121 pub outline_panel: Option<OutlinePanelSettingsContent>,
122
123 pub project_panel: Option<ProjectPanelSettingsContent>,
124
125 /// Configuration for the Message Editor
126 pub message_editor: Option<MessageEditorSettings>,
127
128 /// Configuration for Node-related features
129 pub node: Option<NodeBinarySettings>,
130
131 /// Configuration for the Notification Panel
132 pub notification_panel: Option<NotificationPanelSettingsContent>,
133
134 pub proxy: Option<String>,
135
136 /// The URL of the Zed server to connect to.
137 pub server_url: Option<String>,
138
139 /// Configuration for session-related features
140 pub session: Option<SessionSettingsContent>,
141 /// Control what info is collected by Zed.
142 pub telemetry: Option<TelemetrySettingsContent>,
143
144 /// Configuration of the terminal in Zed.
145 pub terminal: Option<TerminalSettingsContent>,
146
147 pub title_bar: Option<TitleBarSettingsContent>,
148
149 /// Whether or not to enable Vim mode.
150 ///
151 /// Default: false
152 pub vim_mode: Option<bool>,
153
154 // Settings related to calls in Zed
155 pub calls: Option<CallSettingsContent>,
156
157 /// Whether to disable all AI features in Zed.
158 ///
159 /// Default: false
160 pub disable_ai: Option<SaturatingBool>,
161
162 /// Settings related to Vim mode in Zed.
163 pub vim: Option<VimSettingsContent>,
164}
165
166impl SettingsContent {
167 pub fn languages_mut(&mut self) -> &mut HashMap<SharedString, LanguageSettingsContent> {
168 &mut self.project.all_languages.languages.0
169 }
170}
171
172#[skip_serializing_none]
173#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
174pub struct UserSettingsContent {
175 #[serde(flatten)]
176 pub content: Box<SettingsContent>,
177
178 pub dev: Option<Box<SettingsContent>>,
179 pub nightly: Option<Box<SettingsContent>>,
180 pub preview: Option<Box<SettingsContent>>,
181 pub stable: Option<Box<SettingsContent>>,
182
183 pub macos: Option<Box<SettingsContent>>,
184 pub windows: Option<Box<SettingsContent>>,
185 pub linux: Option<Box<SettingsContent>>,
186
187 #[serde(default)]
188 pub profiles: IndexMap<String, SettingsContent>,
189}
190
191pub struct ExtensionsSettingsContent {
192 pub all_languages: AllLanguageSettingsContent,
193}
194
195impl UserSettingsContent {
196 pub fn for_release_channel(&self) -> Option<&SettingsContent> {
197 match *release_channel::RELEASE_CHANNEL {
198 ReleaseChannel::Dev => self.dev.as_deref(),
199 ReleaseChannel::Nightly => self.nightly.as_deref(),
200 ReleaseChannel::Preview => self.preview.as_deref(),
201 ReleaseChannel::Stable => self.stable.as_deref(),
202 }
203 }
204
205 pub fn for_os(&self) -> Option<&SettingsContent> {
206 match env::consts::OS {
207 "macos" => self.macos.as_deref(),
208 "linux" => self.linux.as_deref(),
209 "windows" => self.windows.as_deref(),
210 _ => None,
211 }
212 }
213
214 pub fn for_profile(&self, cx: &App) -> Option<&SettingsContent> {
215 let Some(active_profile) = cx.try_global::<ActiveSettingsProfileName>() else {
216 return None;
217 };
218 self.profiles.get(&active_profile.0)
219 }
220}
221
222/// Base key bindings scheme. Base keymaps can be overridden with user keymaps.
223///
224/// Default: VSCode
225#[derive(
226 Copy,
227 Clone,
228 Debug,
229 Serialize,
230 Deserialize,
231 JsonSchema,
232 MergeFrom,
233 PartialEq,
234 Eq,
235 Default,
236 strum::VariantArray,
237)]
238pub enum BaseKeymapContent {
239 #[default]
240 VSCode,
241 JetBrains,
242 SublimeText,
243 Atom,
244 TextMate,
245 Emacs,
246 Cursor,
247 None,
248}
249
250impl strum::VariantNames for BaseKeymapContent {
251 const VARIANTS: &'static [&'static str] = &[
252 "VSCode",
253 "JetBrains",
254 "Sublime Text",
255 "Atom",
256 "TextMate",
257 "Emacs",
258 "Cursor",
259 "None",
260 ];
261}
262
263/// Position of window control buttons on Linux.
264///
265/// Valid values: "left" (macOS style) or "right" (Windows/Linux style)
266#[derive(
267 Copy,
268 Clone,
269 Debug,
270 Serialize,
271 Deserialize,
272 JsonSchema,
273 MergeFrom,
274 PartialEq,
275 Eq,
276 Default,
277 strum::VariantArray,
278 strum::VariantNames,
279)]
280#[serde(rename_all = "snake_case")]
281pub enum WindowControlsPosition {
282 /// Window controls on the left side (macOS style)
283 Left,
284 /// Window controls on the right side (Windows style)
285 #[default]
286 Right,
287}
288
289#[skip_serializing_none]
290#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
291pub struct TitleBarSettingsContent {
292 /// Whether to show the branch icon beside branch switcher in the title bar.
293 ///
294 /// Default: false
295 pub show_branch_icon: Option<bool>,
296 /// Whether to show onboarding banners in the title bar.
297 ///
298 /// Default: true
299 pub show_onboarding_banner: Option<bool>,
300 /// Whether to show user avatar in the title bar.
301 ///
302 /// Default: true
303 pub show_user_picture: Option<bool>,
304 /// Whether to show the branch name button in the titlebar.
305 ///
306 /// Default: true
307 pub show_branch_name: Option<bool>,
308 /// Whether to show the project host and name in the titlebar.
309 ///
310 /// Default: true
311 pub show_project_items: Option<bool>,
312 /// Whether to show the sign in button in the title bar.
313 ///
314 /// Default: true
315 pub show_sign_in: Option<bool>,
316 /// Whether to show the menus in the title bar.
317 ///
318 /// Default: false
319 pub show_menus: Option<bool>,
320 /// Position of window control buttons (minimize, maximize, close) on Linux.
321 ///
322 /// Default: right
323 pub window_controls_position: Option<WindowControlsPosition>,
324}
325
326/// Configuration of audio in Zed.
327#[skip_serializing_none]
328#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
329pub struct AudioSettingsContent {
330 /// Opt into the new audio system.
331 ///
332 /// You need to rejoin a call for this setting to apply
333 #[serde(rename = "experimental.rodio_audio")]
334 pub rodio_audio: Option<bool>, // default is false
335 /// Requires 'rodio_audio: true'
336 ///
337 /// Automatically increase or decrease you microphone's volume. This affects how
338 /// loud you sound to others.
339 ///
340 /// Recommended: off (default)
341 /// Microphones are too quite in zed, until everyone is on experimental
342 /// audio and has auto speaker volume on this will make you very loud
343 /// compared to other speakers.
344 #[serde(rename = "experimental.auto_microphone_volume")]
345 pub auto_microphone_volume: Option<bool>,
346 /// Requires 'rodio_audio: true'
347 ///
348 /// Automatically increate or decrease the volume of other call members.
349 /// This only affects how things sound for you.
350 #[serde(rename = "experimental.auto_speaker_volume")]
351 pub auto_speaker_volume: Option<bool>,
352 /// Requires 'rodio_audio: true'
353 ///
354 /// Remove background noises. Works great for typing, cars, dogs, AC. Does
355 /// not work well on music.
356 #[serde(rename = "experimental.denoise")]
357 pub denoise: Option<bool>,
358 /// Requires 'rodio_audio: true'
359 ///
360 /// Use audio parameters compatible with the previous versions of
361 /// experimental audio and non-experimental audio. When this is false you
362 /// will sound strange to anyone not on the latest experimental audio. In
363 /// the future we will migrate by setting this to false
364 ///
365 /// You need to rejoin a call for this setting to apply
366 #[serde(rename = "experimental.legacy_audio_compatible")]
367 pub legacy_audio_compatible: Option<bool>,
368}
369
370/// Control what info is collected by Zed.
371#[skip_serializing_none]
372#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Debug, MergeFrom)]
373pub struct TelemetrySettingsContent {
374 /// Send debug info like crash reports.
375 ///
376 /// Default: true
377 pub diagnostics: Option<bool>,
378 /// Send anonymized usage data like what languages you're using Zed with.
379 ///
380 /// Default: true
381 pub metrics: Option<bool>,
382}
383
384impl Default for TelemetrySettingsContent {
385 fn default() -> Self {
386 Self {
387 diagnostics: Some(true),
388 metrics: Some(true),
389 }
390 }
391}
392
393#[skip_serializing_none]
394#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Clone, MergeFrom)]
395pub struct DebuggerSettingsContent {
396 /// Determines the stepping granularity.
397 ///
398 /// Default: line
399 pub stepping_granularity: Option<SteppingGranularity>,
400 /// Whether the breakpoints should be reused across Zed sessions.
401 ///
402 /// Default: true
403 pub save_breakpoints: Option<bool>,
404 /// Whether to show the debug button in the status bar.
405 ///
406 /// Default: true
407 pub button: Option<bool>,
408 /// Time in milliseconds until timeout error when connecting to a TCP debug adapter
409 ///
410 /// Default: 2000ms
411 pub timeout: Option<u64>,
412 /// Whether to log messages between active debug adapters and Zed
413 ///
414 /// Default: true
415 pub log_dap_communications: Option<bool>,
416 /// Whether to format dap messages in when adding them to debug adapter logger
417 ///
418 /// Default: true
419 pub format_dap_log_messages: Option<bool>,
420 /// The dock position of the debug panel
421 ///
422 /// Default: Bottom
423 pub dock: Option<DockPosition>,
424}
425
426/// The granularity of one 'step' in the stepping requests `next`, `stepIn`, `stepOut`, and `stepBack`.
427#[derive(
428 PartialEq,
429 Eq,
430 Debug,
431 Hash,
432 Clone,
433 Copy,
434 Deserialize,
435 Serialize,
436 JsonSchema,
437 MergeFrom,
438 strum::VariantArray,
439 strum::VariantNames,
440)]
441#[serde(rename_all = "snake_case")]
442pub enum SteppingGranularity {
443 /// The step should allow the program to run until the current statement has finished executing.
444 /// The meaning of a statement is determined by the adapter and it may be considered equivalent to a line.
445 /// For example 'for(int i = 0; i < 10; i++)' could be considered to have 3 statements 'int i = 0', 'i < 10', and 'i++'.
446 Statement,
447 /// The step should allow the program to run until the current source line has executed.
448 Line,
449 /// The step should allow one instruction to execute (e.g. one x86 instruction).
450 Instruction,
451}
452
453#[derive(
454 Copy,
455 Clone,
456 Debug,
457 Serialize,
458 Deserialize,
459 JsonSchema,
460 MergeFrom,
461 PartialEq,
462 Eq,
463 strum::VariantArray,
464 strum::VariantNames,
465)]
466#[serde(rename_all = "snake_case")]
467pub enum DockPosition {
468 Left,
469 Bottom,
470 Right,
471}
472
473/// Settings for slash commands.
474#[skip_serializing_none]
475#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, MergeFrom, PartialEq, Eq)]
476pub struct SlashCommandSettings {
477 /// Settings for the `/cargo-workspace` slash command.
478 pub cargo_workspace: Option<CargoWorkspaceCommandSettings>,
479}
480
481/// Settings for the `/cargo-workspace` slash command.
482#[skip_serializing_none]
483#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, MergeFrom, PartialEq, Eq)]
484pub struct CargoWorkspaceCommandSettings {
485 /// Whether `/cargo-workspace` is enabled.
486 pub enabled: Option<bool>,
487}
488
489/// Configuration of voice calls in Zed.
490#[skip_serializing_none]
491#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
492pub struct CallSettingsContent {
493 /// Whether the microphone should be muted when joining a channel or a call.
494 ///
495 /// Default: false
496 pub mute_on_join: Option<bool>,
497
498 /// Whether your current project should be shared when joining an empty channel.
499 ///
500 /// Default: false
501 pub share_on_join: Option<bool>,
502}
503
504#[skip_serializing_none]
505#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
506pub struct GitPanelSettingsContent {
507 /// Whether to show the panel button in the status bar.
508 ///
509 /// Default: true
510 pub button: Option<bool>,
511 /// Where to dock the panel.
512 ///
513 /// Default: left
514 pub dock: Option<DockPosition>,
515 /// Default width of the panel in pixels.
516 ///
517 /// Default: 360
518 #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
519 pub default_width: Option<f32>,
520 /// How entry statuses are displayed.
521 ///
522 /// Default: icon
523 pub status_style: Option<StatusStyle>,
524 /// How and when the scrollbar should be displayed.
525 ///
526 /// Default: inherits editor scrollbar settings
527 pub scrollbar: Option<ScrollbarSettings>,
528
529 /// What the default branch name should be when
530 /// `init.defaultBranch` is not set in git
531 ///
532 /// Default: main
533 pub fallback_branch_name: Option<String>,
534
535 /// Whether to sort entries in the panel by path
536 /// or by status (the default).
537 ///
538 /// Default: false
539 pub sort_by_path: Option<bool>,
540
541 /// Whether to collapse untracked files in the diff panel.
542 ///
543 /// Default: false
544 pub collapse_untracked_diff: Option<bool>,
545}
546
547#[derive(
548 Default,
549 Copy,
550 Clone,
551 Debug,
552 Serialize,
553 Deserialize,
554 JsonSchema,
555 MergeFrom,
556 PartialEq,
557 Eq,
558 strum::VariantArray,
559 strum::VariantNames,
560)]
561#[serde(rename_all = "snake_case")]
562pub enum StatusStyle {
563 #[default]
564 Icon,
565 LabelColor,
566}
567
568#[skip_serializing_none]
569#[derive(
570 Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq,
571)]
572pub struct ScrollbarSettings {
573 pub show: Option<ShowScrollbar>,
574}
575
576#[skip_serializing_none]
577#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
578pub struct NotificationPanelSettingsContent {
579 /// Whether to show the panel button in the status bar.
580 ///
581 /// Default: true
582 pub button: Option<bool>,
583 /// Where to dock the panel.
584 ///
585 /// Default: right
586 pub dock: Option<DockPosition>,
587 /// Default width of the panel in pixels.
588 ///
589 /// Default: 300
590 #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
591 pub default_width: Option<f32>,
592}
593
594#[skip_serializing_none]
595#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
596pub struct PanelSettingsContent {
597 /// Whether to show the panel button in the status bar.
598 ///
599 /// Default: true
600 pub button: Option<bool>,
601 /// Where to dock the panel.
602 ///
603 /// Default: left
604 pub dock: Option<DockPosition>,
605 /// Default width of the panel in pixels.
606 ///
607 /// Default: 240
608 #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
609 pub default_width: Option<f32>,
610}
611
612#[skip_serializing_none]
613#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
614pub struct MessageEditorSettings {
615 /// Whether to automatically replace emoji shortcodes with emoji characters.
616 /// For example: typing `:wave:` gets replaced with `👋`.
617 ///
618 /// Default: false
619 pub auto_replace_emoji_shortcode: Option<bool>,
620}
621
622#[skip_serializing_none]
623#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
624pub struct FileFinderSettingsContent {
625 /// Whether to show file icons in the file finder.
626 ///
627 /// Default: true
628 pub file_icons: Option<bool>,
629 /// Determines how much space the file finder can take up in relation to the available window width.
630 ///
631 /// Default: small
632 pub modal_max_width: Option<FileFinderWidthContent>,
633 /// Determines whether the file finder should skip focus for the active file in search results.
634 ///
635 /// Default: true
636 pub skip_focus_for_active_in_search: Option<bool>,
637 /// Determines whether to show the git status in the file finder
638 ///
639 /// Default: true
640 pub git_status: Option<bool>,
641 /// Whether to use gitignored files when searching.
642 /// Only the file Zed had indexed will be used, not necessary all the gitignored files.
643 ///
644 /// Default: Smart
645 pub include_ignored: Option<IncludeIgnoredContent>,
646}
647
648#[derive(
649 Debug,
650 PartialEq,
651 Eq,
652 Clone,
653 Copy,
654 Default,
655 Serialize,
656 Deserialize,
657 JsonSchema,
658 MergeFrom,
659 strum::VariantArray,
660 strum::VariantNames,
661)]
662#[serde(rename_all = "snake_case")]
663pub enum IncludeIgnoredContent {
664 /// Use all gitignored files
665 All,
666 /// Use only the files Zed had indexed
667 Indexed,
668 /// Be smart and search for ignored when called from a gitignored worktree
669 #[default]
670 Smart,
671}
672
673#[derive(
674 Debug,
675 PartialEq,
676 Eq,
677 Clone,
678 Copy,
679 Default,
680 Serialize,
681 Deserialize,
682 JsonSchema,
683 MergeFrom,
684 strum::VariantArray,
685 strum::VariantNames,
686)]
687#[serde(rename_all = "lowercase")]
688pub enum FileFinderWidthContent {
689 #[default]
690 Small,
691 Medium,
692 Large,
693 XLarge,
694 Full,
695}
696
697#[skip_serializing_none]
698#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Debug, JsonSchema, MergeFrom)]
699pub struct VimSettingsContent {
700 pub default_mode: Option<ModeContent>,
701 pub toggle_relative_line_numbers: Option<bool>,
702 pub use_system_clipboard: Option<UseSystemClipboard>,
703 pub use_smartcase_find: Option<bool>,
704 pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
705 pub highlight_on_yank_duration: Option<u64>,
706 pub cursor_shape: Option<CursorShapeSettings>,
707}
708
709#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Debug)]
710#[serde(rename_all = "snake_case")]
711pub enum ModeContent {
712 #[default]
713 Normal,
714 Insert,
715}
716
717/// Controls when to use system clipboard.
718#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
719#[serde(rename_all = "snake_case")]
720pub enum UseSystemClipboard {
721 /// Don't use system clipboard.
722 Never,
723 /// Use system clipboard.
724 Always,
725 /// Use system clipboard for yank operations.
726 OnYank,
727}
728
729/// The settings for cursor shape.
730#[skip_serializing_none]
731#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
732pub struct CursorShapeSettings {
733 /// Cursor shape for the normal mode.
734 ///
735 /// Default: block
736 pub normal: Option<CursorShape>,
737 /// Cursor shape for the replace mode.
738 ///
739 /// Default: underline
740 pub replace: Option<CursorShape>,
741 /// Cursor shape for the visual mode.
742 ///
743 /// Default: block
744 pub visual: Option<CursorShape>,
745 /// Cursor shape for the insert mode.
746 ///
747 /// The default value follows the primary cursor_shape.
748 pub insert: Option<CursorShape>,
749}
750
751/// Settings specific to journaling
752#[skip_serializing_none]
753#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
754pub struct JournalSettingsContent {
755 /// The path of the directory where journal entries are stored.
756 ///
757 /// Default: `~`
758 pub path: Option<String>,
759 /// What format to display the hours in.
760 ///
761 /// Default: hour12
762 pub hour_format: Option<HourFormat>,
763}
764
765#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
766#[serde(rename_all = "snake_case")]
767pub enum HourFormat {
768 #[default]
769 Hour12,
770 Hour24,
771}
772
773#[skip_serializing_none]
774#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
775pub struct OutlinePanelSettingsContent {
776 /// Whether to show the outline panel button in the status bar.
777 ///
778 /// Default: true
779 pub button: Option<bool>,
780 /// Customize default width (in pixels) taken by outline panel
781 ///
782 /// Default: 240
783 #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
784 pub default_width: Option<f32>,
785 /// The position of outline panel
786 ///
787 /// Default: left
788 pub dock: Option<DockSide>,
789 /// Whether to show file icons in the outline panel.
790 ///
791 /// Default: true
792 pub file_icons: Option<bool>,
793 /// Whether to show folder icons or chevrons for directories in the outline panel.
794 ///
795 /// Default: true
796 pub folder_icons: Option<bool>,
797 /// Whether to show the git status in the outline panel.
798 ///
799 /// Default: true
800 pub git_status: Option<bool>,
801 /// Amount of indentation (in pixels) for nested items.
802 ///
803 /// Default: 20
804 #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
805 pub indent_size: Option<f32>,
806 /// Whether to reveal it in the outline panel automatically,
807 /// when a corresponding project entry becomes active.
808 /// Gitignored entries are never auto revealed.
809 ///
810 /// Default: true
811 pub auto_reveal_entries: Option<bool>,
812 /// Whether to fold directories automatically
813 /// when directory has only one directory inside.
814 ///
815 /// Default: true
816 pub auto_fold_dirs: Option<bool>,
817 /// Settings related to indent guides in the outline panel.
818 pub indent_guides: Option<IndentGuidesSettingsContent>,
819 /// Scrollbar-related settings
820 pub scrollbar: Option<ScrollbarSettingsContent>,
821 /// Default depth to expand outline items in the current file.
822 /// The default depth to which outline entries are expanded on reveal.
823 /// - Set to 0 to collapse all items that have children
824 /// - Set to 1 or higher to collapse items at that depth or deeper
825 ///
826 /// Default: 100
827 pub expand_outlines_with_depth: Option<usize>,
828}
829
830#[derive(
831 Clone,
832 Copy,
833 Debug,
834 PartialEq,
835 Eq,
836 Serialize,
837 Deserialize,
838 JsonSchema,
839 MergeFrom,
840 strum::VariantArray,
841 strum::VariantNames,
842)]
843#[serde(rename_all = "snake_case")]
844pub enum DockSide {
845 Left,
846 Right,
847}
848
849#[derive(
850 Copy,
851 Clone,
852 Debug,
853 PartialEq,
854 Eq,
855 Deserialize,
856 Serialize,
857 JsonSchema,
858 MergeFrom,
859 strum::VariantArray,
860 strum::VariantNames,
861)]
862#[serde(rename_all = "snake_case")]
863pub enum ShowIndentGuides {
864 Always,
865 Never,
866}
867
868#[skip_serializing_none]
869#[derive(
870 Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default,
871)]
872pub struct IndentGuidesSettingsContent {
873 /// When to show the scrollbar in the outline panel.
874 pub show: Option<ShowIndentGuides>,
875}
876
877#[derive(Clone, Copy, Default, PartialEq, Debug, JsonSchema, MergeFrom, Deserialize, Serialize)]
878#[serde(rename_all = "snake_case")]
879pub enum LineIndicatorFormat {
880 Short,
881 #[default]
882 Long,
883}
884
885/// The settings for the image viewer.
886#[skip_serializing_none]
887#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Default, PartialEq)]
888pub struct ImageViewerSettingsContent {
889 /// The unit to use for displaying image file sizes.
890 ///
891 /// Default: "binary"
892 pub unit: Option<ImageFileSizeUnit>,
893}
894
895#[skip_serializing_none]
896#[derive(
897 Clone,
898 Copy,
899 Debug,
900 Serialize,
901 Deserialize,
902 JsonSchema,
903 MergeFrom,
904 Default,
905 PartialEq,
906 strum::VariantArray,
907 strum::VariantNames,
908)]
909#[serde(rename_all = "snake_case")]
910pub enum ImageFileSizeUnit {
911 /// Displays file size in binary units (e.g., KiB, MiB).
912 #[default]
913 Binary,
914 /// Displays file size in decimal units (e.g., KB, MB).
915 Decimal,
916}
917
918#[skip_serializing_none]
919#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
920pub struct RemoteSettingsContent {
921 pub ssh_connections: Option<Vec<SshConnection>>,
922 pub wsl_connections: Option<Vec<WslConnection>>,
923 pub read_ssh_config: Option<bool>,
924}
925
926#[skip_serializing_none]
927#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
928pub struct SshConnection {
929 pub host: SharedString,
930 pub username: Option<String>,
931 pub port: Option<u16>,
932 #[serde(default)]
933 pub args: Vec<String>,
934 #[serde(default)]
935 pub projects: collections::BTreeSet<SshProject>,
936 /// Name to use for this server in UI.
937 pub nickname: Option<String>,
938 // By default Zed will download the binary to the host directly.
939 // If this is set to true, Zed will download the binary to your local machine,
940 // and then upload it over the SSH connection. Useful if your SSH server has
941 // limited outbound internet access.
942 pub upload_binary_over_ssh: Option<bool>,
943
944 pub port_forwards: Option<Vec<SshPortForwardOption>>,
945}
946
947#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Debug)]
948pub struct WslConnection {
949 pub distro_name: SharedString,
950 pub user: Option<String>,
951 #[serde(default)]
952 pub projects: BTreeSet<SshProject>,
953}
954
955#[skip_serializing_none]
956#[derive(
957 Clone, Debug, Default, Serialize, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema,
958)]
959pub struct SshProject {
960 pub paths: Vec<String>,
961}
962
963#[skip_serializing_none]
964#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, MergeFrom)]
965pub struct SshPortForwardOption {
966 #[serde(skip_serializing_if = "Option::is_none")]
967 pub local_host: Option<String>,
968 pub local_port: u16,
969 #[serde(skip_serializing_if = "Option::is_none")]
970 pub remote_host: Option<String>,
971 pub remote_port: u16,
972}
973
974/// Settings for configuring REPL display and behavior.
975#[skip_serializing_none]
976#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
977pub struct ReplSettingsContent {
978 /// Maximum number of lines to keep in REPL's scrollback buffer.
979 /// Clamped with [4, 256] range.
980 ///
981 /// Default: 32
982 pub max_lines: Option<usize>,
983 /// Maximum number of columns to keep in REPL's scrollback buffer.
984 /// Clamped with [20, 512] range.
985 ///
986 /// Default: 128
987 pub max_columns: Option<usize>,
988}
989
990#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
991/// An ExtendingVec in the settings can only accumulate new values.
992///
993/// This is useful for things like private files where you only want
994/// to allow new values to be added.
995///
996/// Consider using a HashMap<String, bool> instead of this type
997/// (like auto_install_extensions) so that user settings files can both add
998/// and remove values from the set.
999pub struct ExtendingVec<T>(pub Vec<T>);
1000
1001impl<T> Into<Vec<T>> for ExtendingVec<T> {
1002 fn into(self) -> Vec<T> {
1003 self.0
1004 }
1005}
1006impl<T> From<Vec<T>> for ExtendingVec<T> {
1007 fn from(vec: Vec<T>) -> Self {
1008 ExtendingVec(vec)
1009 }
1010}
1011
1012impl<T: Clone> merge_from::MergeFrom for ExtendingVec<T> {
1013 fn merge_from(&mut self, other: &Self) {
1014 self.0.extend_from_slice(other.0.as_slice());
1015 }
1016}
1017
1018/// A SaturatingBool in the settings can only ever be set to true,
1019/// later attempts to set it to false will be ignored.
1020///
1021/// Used by `disable_ai`.
1022#[derive(Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1023pub struct SaturatingBool(pub bool);
1024
1025impl From<bool> for SaturatingBool {
1026 fn from(value: bool) -> Self {
1027 SaturatingBool(value)
1028 }
1029}
1030
1031impl From<SaturatingBool> for bool {
1032 fn from(value: SaturatingBool) -> bool {
1033 value.0
1034 }
1035}
1036
1037impl merge_from::MergeFrom for SaturatingBool {
1038 fn merge_from(&mut self, other: &Self) {
1039 self.0 |= other.0
1040 }
1041}
1042
1043#[derive(
1044 Copy,
1045 Clone,
1046 Default,
1047 Debug,
1048 PartialEq,
1049 Eq,
1050 PartialOrd,
1051 Ord,
1052 Serialize,
1053 Deserialize,
1054 MergeFrom,
1055 JsonSchema,
1056 derive_more::FromStr,
1057)]
1058#[serde(transparent)]
1059pub struct DelayMs(pub u64);
1060
1061impl From<u64> for DelayMs {
1062 fn from(n: u64) -> Self {
1063 Self(n)
1064 }
1065}
1066
1067impl std::fmt::Display for DelayMs {
1068 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1069 write!(f, "{}ms", self.0)
1070 }
1071}
1072
1073/// A wrapper type that distinguishes between an explicitly set value (including null) and an unset value.
1074///
1075/// This is useful for configuration where you need to differentiate between:
1076/// - A field that is not present in the configuration file (`Maybe::Unset`)
1077/// - A field that is explicitly set to `null` (`Maybe::Set(None)`)
1078/// - A field that is explicitly set to a value (`Maybe::Set(Some(value))`)
1079///
1080/// # Examples
1081///
1082/// In JSON:
1083/// - `{}` (field missing) deserializes to `Maybe::Unset`
1084/// - `{"field": null}` deserializes to `Maybe::Set(None)`
1085/// - `{"field": "value"}` deserializes to `Maybe::Set(Some("value"))`
1086///
1087/// WARN: This type should not be wrapped in an option inside of settings, otherwise the default `serde_json` behavior
1088/// of treating `null` and missing as the `Option::None` will be used
1089#[derive(Debug, Clone, PartialEq, Eq, strum::EnumDiscriminants, Default)]
1090#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
1091pub enum Maybe<T> {
1092 /// An explicitly set value, which may be `None` (representing JSON `null`) or `Some(value)`.
1093 Set(Option<T>),
1094 /// A value that was not present in the configuration.
1095 #[default]
1096 Unset,
1097}
1098
1099impl<T: Clone> merge_from::MergeFrom for Maybe<T> {
1100 fn merge_from(&mut self, other: &Self) {
1101 if self.is_unset() {
1102 *self = other.clone();
1103 }
1104 }
1105}
1106
1107impl<T> From<Option<Option<T>>> for Maybe<T> {
1108 fn from(value: Option<Option<T>>) -> Self {
1109 match value {
1110 Some(value) => Maybe::Set(value),
1111 None => Maybe::Unset,
1112 }
1113 }
1114}
1115
1116impl<T> Maybe<T> {
1117 pub fn is_set(&self) -> bool {
1118 matches!(self, Maybe::Set(_))
1119 }
1120
1121 pub fn is_unset(&self) -> bool {
1122 matches!(self, Maybe::Unset)
1123 }
1124
1125 pub fn into_inner(self) -> Option<T> {
1126 match self {
1127 Maybe::Set(value) => value,
1128 Maybe::Unset => None,
1129 }
1130 }
1131
1132 pub fn as_ref(&self) -> Option<&Option<T>> {
1133 match self {
1134 Maybe::Set(value) => Some(value),
1135 Maybe::Unset => None,
1136 }
1137 }
1138}
1139
1140impl<T: serde::Serialize> serde::Serialize for Maybe<T> {
1141 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1142 where
1143 S: serde::Serializer,
1144 {
1145 match self {
1146 Maybe::Set(value) => value.serialize(serializer),
1147 Maybe::Unset => serializer.serialize_none(),
1148 }
1149 }
1150}
1151
1152impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Maybe<T> {
1153 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1154 where
1155 D: serde::Deserializer<'de>,
1156 {
1157 Option::<T>::deserialize(deserializer).map(Maybe::Set)
1158 }
1159}
1160
1161impl<T: JsonSchema> JsonSchema for Maybe<T> {
1162 fn schema_name() -> std::borrow::Cow<'static, str> {
1163 format!("Nullable<{}>", T::schema_name()).into()
1164 }
1165
1166 fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
1167 let mut schema = generator.subschema_for::<Option<T>>();
1168 // Add description explaining that null is an explicit value
1169 let description = if let Some(existing_desc) =
1170 schema.get("description").and_then(|desc| desc.as_str())
1171 {
1172 format!(
1173 "{}. Note: `null` is treated as an explicit value, different from omitting the field entirely.",
1174 existing_desc
1175 )
1176 } else {
1177 "This field supports explicit `null` values. Omitting the field is different from setting it to `null`.".to_string()
1178 };
1179
1180 schema.insert("description".to_string(), description.into());
1181
1182 schema
1183 }
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188 use super::*;
1189 use serde_json;
1190
1191 #[test]
1192 fn test_maybe() {
1193 #[derive(Debug, PartialEq, Serialize, Deserialize)]
1194 struct TestStruct {
1195 #[serde(default)]
1196 #[serde(skip_serializing_if = "Maybe::is_unset")]
1197 field: Maybe<String>,
1198 }
1199
1200 #[derive(Debug, PartialEq, Serialize, Deserialize)]
1201 struct NumericTest {
1202 #[serde(default)]
1203 value: Maybe<i32>,
1204 }
1205
1206 let json = "{}";
1207 let result: TestStruct = serde_json::from_str(json).unwrap();
1208 assert!(result.field.is_unset());
1209 assert_eq!(result.field, Maybe::Unset);
1210
1211 let json = r#"{"field": null}"#;
1212 let result: TestStruct = serde_json::from_str(json).unwrap();
1213 assert!(result.field.is_set());
1214 assert_eq!(result.field, Maybe::Set(None));
1215
1216 let json = r#"{"field": "hello"}"#;
1217 let result: TestStruct = serde_json::from_str(json).unwrap();
1218 assert!(result.field.is_set());
1219 assert_eq!(result.field, Maybe::Set(Some("hello".to_string())));
1220
1221 let test = TestStruct {
1222 field: Maybe::Unset,
1223 };
1224 let json = serde_json::to_string(&test).unwrap();
1225 assert_eq!(json, "{}");
1226
1227 let test = TestStruct {
1228 field: Maybe::Set(None),
1229 };
1230 let json = serde_json::to_string(&test).unwrap();
1231 assert_eq!(json, r#"{"field":null}"#);
1232
1233 let test = TestStruct {
1234 field: Maybe::Set(Some("world".to_string())),
1235 };
1236 let json = serde_json::to_string(&test).unwrap();
1237 assert_eq!(json, r#"{"field":"world"}"#);
1238
1239 let default_maybe: Maybe<i32> = Maybe::default();
1240 assert!(default_maybe.is_unset());
1241
1242 let unset: Maybe<String> = Maybe::Unset;
1243 assert!(unset.is_unset());
1244 assert!(!unset.is_set());
1245
1246 let set_none: Maybe<String> = Maybe::Set(None);
1247 assert!(set_none.is_set());
1248 assert!(!set_none.is_unset());
1249
1250 let set_some: Maybe<String> = Maybe::Set(Some("value".to_string()));
1251 assert!(set_some.is_set());
1252 assert!(!set_some.is_unset());
1253
1254 let original = TestStruct {
1255 field: Maybe::Set(Some("test".to_string())),
1256 };
1257 let json = serde_json::to_string(&original).unwrap();
1258 let deserialized: TestStruct = serde_json::from_str(&json).unwrap();
1259 assert_eq!(original, deserialized);
1260
1261 let json = r#"{"value": 42}"#;
1262 let result: NumericTest = serde_json::from_str(json).unwrap();
1263 assert_eq!(result.value, Maybe::Set(Some(42)));
1264
1265 let json = r#"{"value": null}"#;
1266 let result: NumericTest = serde_json::from_str(json).unwrap();
1267 assert_eq!(result.value, Maybe::Set(None));
1268
1269 let json = "{}";
1270 let result: NumericTest = serde_json::from_str(json).unwrap();
1271 assert_eq!(result.value, Maybe::Unset);
1272
1273 // Test JsonSchema implementation
1274 use schemars::schema_for;
1275 let schema = schema_for!(Maybe<String>);
1276 let schema_json = serde_json::to_value(&schema).unwrap();
1277
1278 // Verify the description mentions that null is an explicit value
1279 let description = schema_json["description"].as_str().unwrap();
1280 assert!(
1281 description.contains("null") && description.contains("explicit"),
1282 "Schema description should mention that null is an explicit value. Got: {}",
1283 description
1284 );
1285 }
1286}