settings_content.rs

   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}