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}