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 pub default_width: Option<f32>,
519 /// How entry statuses are displayed.
520 ///
521 /// Default: icon
522 pub status_style: Option<StatusStyle>,
523 /// How and when the scrollbar should be displayed.
524 ///
525 /// Default: inherits editor scrollbar settings
526 pub scrollbar: Option<ScrollbarSettings>,
527
528 /// What the default branch name should be when
529 /// `init.defaultBranch` is not set in git
530 ///
531 /// Default: main
532 pub fallback_branch_name: Option<String>,
533
534 /// Whether to sort entries in the panel by path
535 /// or by status (the default).
536 ///
537 /// Default: false
538 pub sort_by_path: Option<bool>,
539
540 /// Whether to collapse untracked files in the diff panel.
541 ///
542 /// Default: false
543 pub collapse_untracked_diff: Option<bool>,
544}
545
546#[derive(
547 Default,
548 Copy,
549 Clone,
550 Debug,
551 Serialize,
552 Deserialize,
553 JsonSchema,
554 MergeFrom,
555 PartialEq,
556 Eq,
557 strum::VariantArray,
558 strum::VariantNames,
559)]
560#[serde(rename_all = "snake_case")]
561pub enum StatusStyle {
562 #[default]
563 Icon,
564 LabelColor,
565}
566
567#[skip_serializing_none]
568#[derive(
569 Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq,
570)]
571pub struct ScrollbarSettings {
572 pub show: Option<ShowScrollbar>,
573}
574
575#[skip_serializing_none]
576#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
577pub struct NotificationPanelSettingsContent {
578 /// Whether to show the panel button in the status bar.
579 ///
580 /// Default: true
581 pub button: Option<bool>,
582 /// Where to dock the panel.
583 ///
584 /// Default: right
585 pub dock: Option<DockPosition>,
586 /// Default width of the panel in pixels.
587 ///
588 /// Default: 300
589 pub default_width: Option<f32>,
590}
591
592#[skip_serializing_none]
593#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
594pub struct PanelSettingsContent {
595 /// Whether to show the panel button in the status bar.
596 ///
597 /// Default: true
598 pub button: Option<bool>,
599 /// Where to dock the panel.
600 ///
601 /// Default: left
602 pub dock: Option<DockPosition>,
603 /// Default width of the panel in pixels.
604 ///
605 /// Default: 240
606 pub default_width: Option<f32>,
607}
608
609#[skip_serializing_none]
610#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
611pub struct MessageEditorSettings {
612 /// Whether to automatically replace emoji shortcodes with emoji characters.
613 /// For example: typing `:wave:` gets replaced with `👋`.
614 ///
615 /// Default: false
616 pub auto_replace_emoji_shortcode: Option<bool>,
617}
618
619#[skip_serializing_none]
620#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
621pub struct FileFinderSettingsContent {
622 /// Whether to show file icons in the file finder.
623 ///
624 /// Default: true
625 pub file_icons: Option<bool>,
626 /// Determines how much space the file finder can take up in relation to the available window width.
627 ///
628 /// Default: small
629 pub modal_max_width: Option<FileFinderWidthContent>,
630 /// Determines whether the file finder should skip focus for the active file in search results.
631 ///
632 /// Default: true
633 pub skip_focus_for_active_in_search: Option<bool>,
634 /// Determines whether to show the git status in the file finder
635 ///
636 /// Default: true
637 pub git_status: Option<bool>,
638 /// Whether to use gitignored files when searching.
639 /// Only the file Zed had indexed will be used, not necessary all the gitignored files.
640 ///
641 /// Default: Smart
642 pub include_ignored: Option<IncludeIgnoredContent>,
643}
644
645#[derive(
646 Debug,
647 PartialEq,
648 Eq,
649 Clone,
650 Copy,
651 Default,
652 Serialize,
653 Deserialize,
654 JsonSchema,
655 MergeFrom,
656 strum::VariantArray,
657 strum::VariantNames,
658)]
659#[serde(rename_all = "snake_case")]
660pub enum IncludeIgnoredContent {
661 /// Use all gitignored files
662 All,
663 /// Use only the files Zed had indexed
664 Indexed,
665 /// Be smart and search for ignored when called from a gitignored worktree
666 #[default]
667 Smart,
668}
669
670#[derive(
671 Debug,
672 PartialEq,
673 Eq,
674 Clone,
675 Copy,
676 Default,
677 Serialize,
678 Deserialize,
679 JsonSchema,
680 MergeFrom,
681 strum::VariantArray,
682 strum::VariantNames,
683)]
684#[serde(rename_all = "lowercase")]
685pub enum FileFinderWidthContent {
686 #[default]
687 Small,
688 Medium,
689 Large,
690 XLarge,
691 Full,
692}
693
694#[skip_serializing_none]
695#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Debug, JsonSchema, MergeFrom)]
696pub struct VimSettingsContent {
697 pub default_mode: Option<ModeContent>,
698 pub toggle_relative_line_numbers: Option<bool>,
699 pub use_system_clipboard: Option<UseSystemClipboard>,
700 pub use_smartcase_find: Option<bool>,
701 pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
702 pub highlight_on_yank_duration: Option<u64>,
703 pub cursor_shape: Option<CursorShapeSettings>,
704}
705
706#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Debug)]
707#[serde(rename_all = "snake_case")]
708pub enum ModeContent {
709 #[default]
710 Normal,
711 Insert,
712}
713
714/// Controls when to use system clipboard.
715#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
716#[serde(rename_all = "snake_case")]
717pub enum UseSystemClipboard {
718 /// Don't use system clipboard.
719 Never,
720 /// Use system clipboard.
721 Always,
722 /// Use system clipboard for yank operations.
723 OnYank,
724}
725
726/// The settings for cursor shape.
727#[skip_serializing_none]
728#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
729pub struct CursorShapeSettings {
730 /// Cursor shape for the normal mode.
731 ///
732 /// Default: block
733 pub normal: Option<CursorShape>,
734 /// Cursor shape for the replace mode.
735 ///
736 /// Default: underline
737 pub replace: Option<CursorShape>,
738 /// Cursor shape for the visual mode.
739 ///
740 /// Default: block
741 pub visual: Option<CursorShape>,
742 /// Cursor shape for the insert mode.
743 ///
744 /// The default value follows the primary cursor_shape.
745 pub insert: Option<CursorShape>,
746}
747
748/// Settings specific to journaling
749#[skip_serializing_none]
750#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
751pub struct JournalSettingsContent {
752 /// The path of the directory where journal entries are stored.
753 ///
754 /// Default: `~`
755 pub path: Option<String>,
756 /// What format to display the hours in.
757 ///
758 /// Default: hour12
759 pub hour_format: Option<HourFormat>,
760}
761
762#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
763#[serde(rename_all = "snake_case")]
764pub enum HourFormat {
765 #[default]
766 Hour12,
767 Hour24,
768}
769
770#[skip_serializing_none]
771#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
772pub struct OutlinePanelSettingsContent {
773 /// Whether to show the outline panel button in the status bar.
774 ///
775 /// Default: true
776 pub button: Option<bool>,
777 /// Customize default width (in pixels) taken by outline panel
778 ///
779 /// Default: 240
780 pub default_width: Option<f32>,
781 /// The position of outline panel
782 ///
783 /// Default: left
784 pub dock: Option<DockSide>,
785 /// Whether to show file icons in the outline panel.
786 ///
787 /// Default: true
788 pub file_icons: Option<bool>,
789 /// Whether to show folder icons or chevrons for directories in the outline panel.
790 ///
791 /// Default: true
792 pub folder_icons: Option<bool>,
793 /// Whether to show the git status in the outline panel.
794 ///
795 /// Default: true
796 pub git_status: Option<bool>,
797 /// Amount of indentation (in pixels) for nested items.
798 ///
799 /// Default: 20
800 pub indent_size: Option<f32>,
801 /// Whether to reveal it in the outline panel automatically,
802 /// when a corresponding project entry becomes active.
803 /// Gitignored entries are never auto revealed.
804 ///
805 /// Default: true
806 pub auto_reveal_entries: Option<bool>,
807 /// Whether to fold directories automatically
808 /// when directory has only one directory inside.
809 ///
810 /// Default: true
811 pub auto_fold_dirs: Option<bool>,
812 /// Settings related to indent guides in the outline panel.
813 pub indent_guides: Option<IndentGuidesSettingsContent>,
814 /// Scrollbar-related settings
815 pub scrollbar: Option<ScrollbarSettingsContent>,
816 /// Default depth to expand outline items in the current file.
817 /// The default depth to which outline entries are expanded on reveal.
818 /// - Set to 0 to collapse all items that have children
819 /// - Set to 1 or higher to collapse items at that depth or deeper
820 ///
821 /// Default: 100
822 pub expand_outlines_with_depth: Option<usize>,
823}
824
825#[derive(
826 Clone,
827 Copy,
828 Debug,
829 PartialEq,
830 Eq,
831 Serialize,
832 Deserialize,
833 JsonSchema,
834 MergeFrom,
835 strum::VariantArray,
836 strum::VariantNames,
837)]
838#[serde(rename_all = "snake_case")]
839pub enum DockSide {
840 Left,
841 Right,
842}
843
844#[derive(
845 Copy,
846 Clone,
847 Debug,
848 PartialEq,
849 Eq,
850 Deserialize,
851 Serialize,
852 JsonSchema,
853 MergeFrom,
854 strum::VariantArray,
855 strum::VariantNames,
856)]
857#[serde(rename_all = "snake_case")]
858pub enum ShowIndentGuides {
859 Always,
860 Never,
861}
862
863#[skip_serializing_none]
864#[derive(
865 Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default,
866)]
867pub struct IndentGuidesSettingsContent {
868 /// When to show the scrollbar in the outline panel.
869 pub show: Option<ShowIndentGuides>,
870}
871
872#[derive(Clone, Copy, Default, PartialEq, Debug, JsonSchema, MergeFrom, Deserialize, Serialize)]
873#[serde(rename_all = "snake_case")]
874pub enum LineIndicatorFormat {
875 Short,
876 #[default]
877 Long,
878}
879
880/// The settings for the image viewer.
881#[skip_serializing_none]
882#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Default, PartialEq)]
883pub struct ImageViewerSettingsContent {
884 /// The unit to use for displaying image file sizes.
885 ///
886 /// Default: "binary"
887 pub unit: Option<ImageFileSizeUnit>,
888}
889
890#[skip_serializing_none]
891#[derive(
892 Clone,
893 Copy,
894 Debug,
895 Serialize,
896 Deserialize,
897 JsonSchema,
898 MergeFrom,
899 Default,
900 PartialEq,
901 strum::VariantArray,
902 strum::VariantNames,
903)]
904#[serde(rename_all = "snake_case")]
905pub enum ImageFileSizeUnit {
906 /// Displays file size in binary units (e.g., KiB, MiB).
907 #[default]
908 Binary,
909 /// Displays file size in decimal units (e.g., KB, MB).
910 Decimal,
911}
912
913#[skip_serializing_none]
914#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
915pub struct RemoteSettingsContent {
916 pub ssh_connections: Option<Vec<SshConnection>>,
917 pub wsl_connections: Option<Vec<WslConnection>>,
918 pub read_ssh_config: Option<bool>,
919}
920
921#[skip_serializing_none]
922#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
923pub struct SshConnection {
924 pub host: SharedString,
925 pub username: Option<String>,
926 pub port: Option<u16>,
927 #[serde(default)]
928 pub args: Vec<String>,
929 #[serde(default)]
930 pub projects: collections::BTreeSet<SshProject>,
931 /// Name to use for this server in UI.
932 pub nickname: Option<String>,
933 // By default Zed will download the binary to the host directly.
934 // If this is set to true, Zed will download the binary to your local machine,
935 // and then upload it over the SSH connection. Useful if your SSH server has
936 // limited outbound internet access.
937 pub upload_binary_over_ssh: Option<bool>,
938
939 pub port_forwards: Option<Vec<SshPortForwardOption>>,
940}
941
942#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Debug)]
943pub struct WslConnection {
944 pub distro_name: SharedString,
945 pub user: Option<String>,
946 #[serde(default)]
947 pub projects: BTreeSet<SshProject>,
948}
949
950#[skip_serializing_none]
951#[derive(
952 Clone, Debug, Default, Serialize, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema,
953)]
954pub struct SshProject {
955 pub paths: Vec<String>,
956}
957
958#[skip_serializing_none]
959#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, MergeFrom)]
960pub struct SshPortForwardOption {
961 #[serde(skip_serializing_if = "Option::is_none")]
962 pub local_host: Option<String>,
963 pub local_port: u16,
964 #[serde(skip_serializing_if = "Option::is_none")]
965 pub remote_host: Option<String>,
966 pub remote_port: u16,
967}
968
969/// Settings for configuring REPL display and behavior.
970#[skip_serializing_none]
971#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
972pub struct ReplSettingsContent {
973 /// Maximum number of lines to keep in REPL's scrollback buffer.
974 /// Clamped with [4, 256] range.
975 ///
976 /// Default: 32
977 pub max_lines: Option<usize>,
978 /// Maximum number of columns to keep in REPL's scrollback buffer.
979 /// Clamped with [20, 512] range.
980 ///
981 /// Default: 128
982 pub max_columns: Option<usize>,
983}
984
985#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
986/// An ExtendingVec in the settings can only accumulate new values.
987///
988/// This is useful for things like private files where you only want
989/// to allow new values to be added.
990///
991/// Consider using a HashMap<String, bool> instead of this type
992/// (like auto_install_extensions) so that user settings files can both add
993/// and remove values from the set.
994pub struct ExtendingVec<T>(pub Vec<T>);
995
996impl<T> Into<Vec<T>> for ExtendingVec<T> {
997 fn into(self) -> Vec<T> {
998 self.0
999 }
1000}
1001impl<T> From<Vec<T>> for ExtendingVec<T> {
1002 fn from(vec: Vec<T>) -> Self {
1003 ExtendingVec(vec)
1004 }
1005}
1006
1007impl<T: Clone> merge_from::MergeFrom for ExtendingVec<T> {
1008 fn merge_from(&mut self, other: &Self) {
1009 self.0.extend_from_slice(other.0.as_slice());
1010 }
1011}
1012
1013/// A SaturatingBool in the settings can only ever be set to true,
1014/// later attempts to set it to false will be ignored.
1015///
1016/// Used by `disable_ai`.
1017#[derive(Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1018pub struct SaturatingBool(pub bool);
1019
1020impl From<bool> for SaturatingBool {
1021 fn from(value: bool) -> Self {
1022 SaturatingBool(value)
1023 }
1024}
1025
1026impl From<SaturatingBool> for bool {
1027 fn from(value: SaturatingBool) -> bool {
1028 value.0
1029 }
1030}
1031
1032impl merge_from::MergeFrom for SaturatingBool {
1033 fn merge_from(&mut self, other: &Self) {
1034 self.0 |= other.0
1035 }
1036}
1037
1038#[derive(
1039 Copy,
1040 Clone,
1041 Default,
1042 Debug,
1043 PartialEq,
1044 Eq,
1045 PartialOrd,
1046 Ord,
1047 Serialize,
1048 Deserialize,
1049 MergeFrom,
1050 JsonSchema,
1051 derive_more::FromStr,
1052)]
1053#[serde(transparent)]
1054pub struct DelayMs(pub u64);
1055
1056impl From<u64> for DelayMs {
1057 fn from(n: u64) -> Self {
1058 Self(n)
1059 }
1060}
1061
1062impl std::fmt::Display for DelayMs {
1063 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1064 write!(f, "{}ms", self.0)
1065 }
1066}
1067
1068/// A wrapper type that distinguishes between an explicitly set value (including null) and an unset value.
1069///
1070/// This is useful for configuration where you need to differentiate between:
1071/// - A field that is not present in the configuration file (`Maybe::Unset`)
1072/// - A field that is explicitly set to `null` (`Maybe::Set(None)`)
1073/// - A field that is explicitly set to a value (`Maybe::Set(Some(value))`)
1074///
1075/// # Examples
1076///
1077/// In JSON:
1078/// - `{}` (field missing) deserializes to `Maybe::Unset`
1079/// - `{"field": null}` deserializes to `Maybe::Set(None)`
1080/// - `{"field": "value"}` deserializes to `Maybe::Set(Some("value"))`
1081///
1082/// WARN: This type should not be wrapped in an option inside of settings, otherwise the default `serde_json` behavior
1083/// of treating `null` and missing as the `Option::None` will be used
1084#[derive(Debug, Clone, PartialEq, Eq, strum::EnumDiscriminants, Default)]
1085#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
1086pub enum Maybe<T> {
1087 /// An explicitly set value, which may be `None` (representing JSON `null`) or `Some(value)`.
1088 Set(Option<T>),
1089 /// A value that was not present in the configuration.
1090 #[default]
1091 Unset,
1092}
1093
1094impl<T: Clone> merge_from::MergeFrom for Maybe<T> {
1095 fn merge_from(&mut self, other: &Self) {
1096 if self.is_unset() {
1097 *self = other.clone();
1098 }
1099 }
1100}
1101
1102impl<T> From<Option<Option<T>>> for Maybe<T> {
1103 fn from(value: Option<Option<T>>) -> Self {
1104 match value {
1105 Some(value) => Maybe::Set(value),
1106 None => Maybe::Unset,
1107 }
1108 }
1109}
1110
1111impl<T> Maybe<T> {
1112 pub fn is_set(&self) -> bool {
1113 matches!(self, Maybe::Set(_))
1114 }
1115
1116 pub fn is_unset(&self) -> bool {
1117 matches!(self, Maybe::Unset)
1118 }
1119
1120 pub fn into_inner(self) -> Option<T> {
1121 match self {
1122 Maybe::Set(value) => value,
1123 Maybe::Unset => None,
1124 }
1125 }
1126
1127 pub fn as_ref(&self) -> Option<&Option<T>> {
1128 match self {
1129 Maybe::Set(value) => Some(value),
1130 Maybe::Unset => None,
1131 }
1132 }
1133}
1134
1135impl<T: serde::Serialize> serde::Serialize for Maybe<T> {
1136 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1137 where
1138 S: serde::Serializer,
1139 {
1140 match self {
1141 Maybe::Set(value) => value.serialize(serializer),
1142 Maybe::Unset => serializer.serialize_none(),
1143 }
1144 }
1145}
1146
1147impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Maybe<T> {
1148 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1149 where
1150 D: serde::Deserializer<'de>,
1151 {
1152 Option::<T>::deserialize(deserializer).map(Maybe::Set)
1153 }
1154}
1155
1156impl<T: JsonSchema> JsonSchema for Maybe<T> {
1157 fn schema_name() -> std::borrow::Cow<'static, str> {
1158 format!("Nullable<{}>", T::schema_name()).into()
1159 }
1160
1161 fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
1162 let mut schema = generator.subschema_for::<Option<T>>();
1163 // Add description explaining that null is an explicit value
1164 let description = if let Some(existing_desc) =
1165 schema.get("description").and_then(|desc| desc.as_str())
1166 {
1167 format!(
1168 "{}. Note: `null` is treated as an explicit value, different from omitting the field entirely.",
1169 existing_desc
1170 )
1171 } else {
1172 "This field supports explicit `null` values. Omitting the field is different from setting it to `null`.".to_string()
1173 };
1174
1175 schema.insert("description".to_string(), description.into());
1176
1177 schema
1178 }
1179}
1180
1181#[cfg(test)]
1182mod tests {
1183 use super::*;
1184 use serde_json;
1185
1186 #[test]
1187 fn test_maybe() {
1188 #[derive(Debug, PartialEq, Serialize, Deserialize)]
1189 struct TestStruct {
1190 #[serde(default)]
1191 #[serde(skip_serializing_if = "Maybe::is_unset")]
1192 field: Maybe<String>,
1193 }
1194
1195 #[derive(Debug, PartialEq, Serialize, Deserialize)]
1196 struct NumericTest {
1197 #[serde(default)]
1198 value: Maybe<i32>,
1199 }
1200
1201 let json = "{}";
1202 let result: TestStruct = serde_json::from_str(json).unwrap();
1203 assert!(result.field.is_unset());
1204 assert_eq!(result.field, Maybe::Unset);
1205
1206 let json = r#"{"field": null}"#;
1207 let result: TestStruct = serde_json::from_str(json).unwrap();
1208 assert!(result.field.is_set());
1209 assert_eq!(result.field, Maybe::Set(None));
1210
1211 let json = r#"{"field": "hello"}"#;
1212 let result: TestStruct = serde_json::from_str(json).unwrap();
1213 assert!(result.field.is_set());
1214 assert_eq!(result.field, Maybe::Set(Some("hello".to_string())));
1215
1216 let test = TestStruct {
1217 field: Maybe::Unset,
1218 };
1219 let json = serde_json::to_string(&test).unwrap();
1220 assert_eq!(json, "{}");
1221
1222 let test = TestStruct {
1223 field: Maybe::Set(None),
1224 };
1225 let json = serde_json::to_string(&test).unwrap();
1226 assert_eq!(json, r#"{"field":null}"#);
1227
1228 let test = TestStruct {
1229 field: Maybe::Set(Some("world".to_string())),
1230 };
1231 let json = serde_json::to_string(&test).unwrap();
1232 assert_eq!(json, r#"{"field":"world"}"#);
1233
1234 let default_maybe: Maybe<i32> = Maybe::default();
1235 assert!(default_maybe.is_unset());
1236
1237 let unset: Maybe<String> = Maybe::Unset;
1238 assert!(unset.is_unset());
1239 assert!(!unset.is_set());
1240
1241 let set_none: Maybe<String> = Maybe::Set(None);
1242 assert!(set_none.is_set());
1243 assert!(!set_none.is_unset());
1244
1245 let set_some: Maybe<String> = Maybe::Set(Some("value".to_string()));
1246 assert!(set_some.is_set());
1247 assert!(!set_some.is_unset());
1248
1249 let original = TestStruct {
1250 field: Maybe::Set(Some("test".to_string())),
1251 };
1252 let json = serde_json::to_string(&original).unwrap();
1253 let deserialized: TestStruct = serde_json::from_str(&json).unwrap();
1254 assert_eq!(original, deserialized);
1255
1256 let json = r#"{"value": 42}"#;
1257 let result: NumericTest = serde_json::from_str(json).unwrap();
1258 assert_eq!(result.value, Maybe::Set(Some(42)));
1259
1260 let json = r#"{"value": null}"#;
1261 let result: NumericTest = serde_json::from_str(json).unwrap();
1262 assert_eq!(result.value, Maybe::Set(None));
1263
1264 let json = "{}";
1265 let result: NumericTest = serde_json::from_str(json).unwrap();
1266 assert_eq!(result.value, Maybe::Unset);
1267
1268 // Test JsonSchema implementation
1269 use schemars::schema_for;
1270 let schema = schema_for!(Maybe<String>);
1271 let schema_json = serde_json::to_value(&schema).unwrap();
1272
1273 // Verify the description mentions that null is an explicit value
1274 let description = schema_json["description"].as_str().unwrap();
1275 assert!(
1276 description.contains("null") && description.contains("explicit"),
1277 "Schema description should mention that null is an explicit value. Got: {}",
1278 description
1279 );
1280 }
1281}