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#[skip_serializing_none]
 264#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
 265pub struct TitleBarSettingsContent {
 266    /// Whether to show the branch icon beside branch switcher in the title bar.
 267    ///
 268    /// Default: false
 269    pub show_branch_icon: Option<bool>,
 270    /// Whether to show onboarding banners in the title bar.
 271    ///
 272    /// Default: true
 273    pub show_onboarding_banner: Option<bool>,
 274    /// Whether to show user avatar in the title bar.
 275    ///
 276    /// Default: true
 277    pub show_user_picture: Option<bool>,
 278    /// Whether to show the branch name button in the titlebar.
 279    ///
 280    /// Default: true
 281    pub show_branch_name: Option<bool>,
 282    /// Whether to show the project host and name in the titlebar.
 283    ///
 284    /// Default: true
 285    pub show_project_items: Option<bool>,
 286    /// Whether to show the sign in button in the title bar.
 287    ///
 288    /// Default: true
 289    pub show_sign_in: Option<bool>,
 290    /// Whether to show the menus in the title bar.
 291    ///
 292    /// Default: false
 293    pub show_menus: Option<bool>,
 294}
 295
 296/// Configuration of audio in Zed.
 297#[skip_serializing_none]
 298#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
 299pub struct AudioSettingsContent {
 300    /// Opt into the new audio system.
 301    ///
 302    /// You need to rejoin a call for this setting to apply
 303    #[serde(rename = "experimental.rodio_audio")]
 304    pub rodio_audio: Option<bool>, // default is false
 305    /// Requires 'rodio_audio: true'
 306    ///
 307    /// Automatically increase or decrease you microphone's volume. This affects how
 308    /// loud you sound to others.
 309    ///
 310    /// Recommended: off (default)
 311    /// Microphones are too quite in zed, until everyone is on experimental
 312    /// audio and has auto speaker volume on this will make you very loud
 313    /// compared to other speakers.
 314    #[serde(rename = "experimental.auto_microphone_volume")]
 315    pub auto_microphone_volume: Option<bool>,
 316    /// Requires 'rodio_audio: true'
 317    ///
 318    /// Automatically increate or decrease the volume of other call members.
 319    /// This only affects how things sound for you.
 320    #[serde(rename = "experimental.auto_speaker_volume")]
 321    pub auto_speaker_volume: Option<bool>,
 322    /// Requires 'rodio_audio: true'
 323    ///
 324    /// Remove background noises. Works great for typing, cars, dogs, AC. Does
 325    /// not work well on music.
 326    #[serde(rename = "experimental.denoise")]
 327    pub denoise: Option<bool>,
 328    /// Requires 'rodio_audio: true'
 329    ///
 330    /// Use audio parameters compatible with the previous versions of
 331    /// experimental audio and non-experimental audio. When this is false you
 332    /// will sound strange to anyone not on the latest experimental audio. In
 333    /// the future we will migrate by setting this to false
 334    ///
 335    /// You need to rejoin a call for this setting to apply
 336    #[serde(rename = "experimental.legacy_audio_compatible")]
 337    pub legacy_audio_compatible: Option<bool>,
 338}
 339
 340/// Control what info is collected by Zed.
 341#[skip_serializing_none]
 342#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Debug, MergeFrom)]
 343pub struct TelemetrySettingsContent {
 344    /// Send debug info like crash reports.
 345    ///
 346    /// Default: true
 347    pub diagnostics: Option<bool>,
 348    /// Send anonymized usage data like what languages you're using Zed with.
 349    ///
 350    /// Default: true
 351    pub metrics: Option<bool>,
 352}
 353
 354impl Default for TelemetrySettingsContent {
 355    fn default() -> Self {
 356        Self {
 357            diagnostics: Some(true),
 358            metrics: Some(true),
 359        }
 360    }
 361}
 362
 363#[skip_serializing_none]
 364#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Clone, MergeFrom)]
 365pub struct DebuggerSettingsContent {
 366    /// Determines the stepping granularity.
 367    ///
 368    /// Default: line
 369    pub stepping_granularity: Option<SteppingGranularity>,
 370    /// Whether the breakpoints should be reused across Zed sessions.
 371    ///
 372    /// Default: true
 373    pub save_breakpoints: Option<bool>,
 374    /// Whether to show the debug button in the status bar.
 375    ///
 376    /// Default: true
 377    pub button: Option<bool>,
 378    /// Time in milliseconds until timeout error when connecting to a TCP debug adapter
 379    ///
 380    /// Default: 2000ms
 381    pub timeout: Option<u64>,
 382    /// Whether to log messages between active debug adapters and Zed
 383    ///
 384    /// Default: true
 385    pub log_dap_communications: Option<bool>,
 386    /// Whether to format dap messages in when adding them to debug adapter logger
 387    ///
 388    /// Default: true
 389    pub format_dap_log_messages: Option<bool>,
 390    /// The dock position of the debug panel
 391    ///
 392    /// Default: Bottom
 393    pub dock: Option<DockPosition>,
 394}
 395
 396/// The granularity of one 'step' in the stepping requests `next`, `stepIn`, `stepOut`, and `stepBack`.
 397#[derive(
 398    PartialEq,
 399    Eq,
 400    Debug,
 401    Hash,
 402    Clone,
 403    Copy,
 404    Deserialize,
 405    Serialize,
 406    JsonSchema,
 407    MergeFrom,
 408    strum::VariantArray,
 409    strum::VariantNames,
 410)]
 411#[serde(rename_all = "snake_case")]
 412pub enum SteppingGranularity {
 413    /// The step should allow the program to run until the current statement has finished executing.
 414    /// The meaning of a statement is determined by the adapter and it may be considered equivalent to a line.
 415    /// For example 'for(int i = 0; i < 10; i++)' could be considered to have 3 statements 'int i = 0', 'i < 10', and 'i++'.
 416    Statement,
 417    /// The step should allow the program to run until the current source line has executed.
 418    Line,
 419    /// The step should allow one instruction to execute (e.g. one x86 instruction).
 420    Instruction,
 421}
 422
 423#[derive(
 424    Copy,
 425    Clone,
 426    Debug,
 427    Serialize,
 428    Deserialize,
 429    JsonSchema,
 430    MergeFrom,
 431    PartialEq,
 432    Eq,
 433    strum::VariantArray,
 434    strum::VariantNames,
 435)]
 436#[serde(rename_all = "snake_case")]
 437pub enum DockPosition {
 438    Left,
 439    Bottom,
 440    Right,
 441}
 442
 443/// Settings for slash commands.
 444#[skip_serializing_none]
 445#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, MergeFrom, PartialEq, Eq)]
 446pub struct SlashCommandSettings {
 447    /// Settings for the `/cargo-workspace` slash command.
 448    pub cargo_workspace: Option<CargoWorkspaceCommandSettings>,
 449}
 450
 451/// Settings for the `/cargo-workspace` slash command.
 452#[skip_serializing_none]
 453#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, MergeFrom, PartialEq, Eq)]
 454pub struct CargoWorkspaceCommandSettings {
 455    /// Whether `/cargo-workspace` is enabled.
 456    pub enabled: Option<bool>,
 457}
 458
 459/// Configuration of voice calls in Zed.
 460#[skip_serializing_none]
 461#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
 462pub struct CallSettingsContent {
 463    /// Whether the microphone should be muted when joining a channel or a call.
 464    ///
 465    /// Default: false
 466    pub mute_on_join: Option<bool>,
 467
 468    /// Whether your current project should be shared when joining an empty channel.
 469    ///
 470    /// Default: false
 471    pub share_on_join: Option<bool>,
 472}
 473
 474#[skip_serializing_none]
 475#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
 476pub struct GitPanelSettingsContent {
 477    /// Whether to show the panel button in the status bar.
 478    ///
 479    /// Default: true
 480    pub button: Option<bool>,
 481    /// Where to dock the panel.
 482    ///
 483    /// Default: left
 484    pub dock: Option<DockPosition>,
 485    /// Default width of the panel in pixels.
 486    ///
 487    /// Default: 360
 488    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
 489    pub default_width: Option<f32>,
 490    /// How entry statuses are displayed.
 491    ///
 492    /// Default: icon
 493    pub status_style: Option<StatusStyle>,
 494    /// How and when the scrollbar should be displayed.
 495    ///
 496    /// Default: inherits editor scrollbar settings
 497    pub scrollbar: Option<ScrollbarSettings>,
 498
 499    /// What the default branch name should be when
 500    /// `init.defaultBranch` is not set in git
 501    ///
 502    /// Default: main
 503    pub fallback_branch_name: Option<String>,
 504
 505    /// Whether to sort entries in the panel by path
 506    /// or by status (the default).
 507    ///
 508    /// Default: false
 509    pub sort_by_path: Option<bool>,
 510
 511    /// Whether to collapse untracked files in the diff panel.
 512    ///
 513    /// Default: false
 514    pub collapse_untracked_diff: Option<bool>,
 515}
 516
 517#[derive(
 518    Default,
 519    Copy,
 520    Clone,
 521    Debug,
 522    Serialize,
 523    Deserialize,
 524    JsonSchema,
 525    MergeFrom,
 526    PartialEq,
 527    Eq,
 528    strum::VariantArray,
 529    strum::VariantNames,
 530)]
 531#[serde(rename_all = "snake_case")]
 532pub enum StatusStyle {
 533    #[default]
 534    Icon,
 535    LabelColor,
 536}
 537
 538#[skip_serializing_none]
 539#[derive(
 540    Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq,
 541)]
 542pub struct ScrollbarSettings {
 543    pub show: Option<ShowScrollbar>,
 544}
 545
 546#[skip_serializing_none]
 547#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
 548pub struct NotificationPanelSettingsContent {
 549    /// Whether to show the panel button in the status bar.
 550    ///
 551    /// Default: true
 552    pub button: Option<bool>,
 553    /// Where to dock the panel.
 554    ///
 555    /// Default: right
 556    pub dock: Option<DockPosition>,
 557    /// Default width of the panel in pixels.
 558    ///
 559    /// Default: 300
 560    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
 561    pub default_width: Option<f32>,
 562}
 563
 564#[skip_serializing_none]
 565#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
 566pub struct PanelSettingsContent {
 567    /// Whether to show the panel button in the status bar.
 568    ///
 569    /// Default: true
 570    pub button: Option<bool>,
 571    /// Where to dock the panel.
 572    ///
 573    /// Default: left
 574    pub dock: Option<DockPosition>,
 575    /// Default width of the panel in pixels.
 576    ///
 577    /// Default: 240
 578    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
 579    pub default_width: Option<f32>,
 580}
 581
 582#[skip_serializing_none]
 583#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
 584pub struct MessageEditorSettings {
 585    /// Whether to automatically replace emoji shortcodes with emoji characters.
 586    /// For example: typing `:wave:` gets replaced with `👋`.
 587    ///
 588    /// Default: false
 589    pub auto_replace_emoji_shortcode: Option<bool>,
 590}
 591
 592#[skip_serializing_none]
 593#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
 594pub struct FileFinderSettingsContent {
 595    /// Whether to show file icons in the file finder.
 596    ///
 597    /// Default: true
 598    pub file_icons: Option<bool>,
 599    /// Determines how much space the file finder can take up in relation to the available window width.
 600    ///
 601    /// Default: small
 602    pub modal_max_width: Option<FileFinderWidthContent>,
 603    /// Determines whether the file finder should skip focus for the active file in search results.
 604    ///
 605    /// Default: true
 606    pub skip_focus_for_active_in_search: Option<bool>,
 607    /// Determines whether to show the git status in the file finder
 608    ///
 609    /// Default: true
 610    pub git_status: Option<bool>,
 611    /// Whether to use gitignored files when searching.
 612    /// Only the file Zed had indexed will be used, not necessary all the gitignored files.
 613    ///
 614    /// Default: Smart
 615    pub include_ignored: Option<IncludeIgnoredContent>,
 616}
 617
 618#[derive(
 619    Debug,
 620    PartialEq,
 621    Eq,
 622    Clone,
 623    Copy,
 624    Default,
 625    Serialize,
 626    Deserialize,
 627    JsonSchema,
 628    MergeFrom,
 629    strum::VariantArray,
 630    strum::VariantNames,
 631)]
 632#[serde(rename_all = "snake_case")]
 633pub enum IncludeIgnoredContent {
 634    /// Use all gitignored files
 635    All,
 636    /// Use only the files Zed had indexed
 637    Indexed,
 638    /// Be smart and search for ignored when called from a gitignored worktree
 639    #[default]
 640    Smart,
 641}
 642
 643#[derive(
 644    Debug,
 645    PartialEq,
 646    Eq,
 647    Clone,
 648    Copy,
 649    Default,
 650    Serialize,
 651    Deserialize,
 652    JsonSchema,
 653    MergeFrom,
 654    strum::VariantArray,
 655    strum::VariantNames,
 656)]
 657#[serde(rename_all = "lowercase")]
 658pub enum FileFinderWidthContent {
 659    #[default]
 660    Small,
 661    Medium,
 662    Large,
 663    XLarge,
 664    Full,
 665}
 666
 667#[skip_serializing_none]
 668#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Debug, JsonSchema, MergeFrom)]
 669pub struct VimSettingsContent {
 670    pub default_mode: Option<ModeContent>,
 671    pub toggle_relative_line_numbers: Option<bool>,
 672    pub use_system_clipboard: Option<UseSystemClipboard>,
 673    pub use_smartcase_find: Option<bool>,
 674    pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
 675    pub highlight_on_yank_duration: Option<u64>,
 676    pub cursor_shape: Option<CursorShapeSettings>,
 677}
 678
 679#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Debug)]
 680#[serde(rename_all = "snake_case")]
 681pub enum ModeContent {
 682    #[default]
 683    Normal,
 684    Insert,
 685}
 686
 687/// Controls when to use system clipboard.
 688#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
 689#[serde(rename_all = "snake_case")]
 690pub enum UseSystemClipboard {
 691    /// Don't use system clipboard.
 692    Never,
 693    /// Use system clipboard.
 694    Always,
 695    /// Use system clipboard for yank operations.
 696    OnYank,
 697}
 698
 699/// The settings for cursor shape.
 700#[skip_serializing_none]
 701#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
 702pub struct CursorShapeSettings {
 703    /// Cursor shape for the normal mode.
 704    ///
 705    /// Default: block
 706    pub normal: Option<CursorShape>,
 707    /// Cursor shape for the replace mode.
 708    ///
 709    /// Default: underline
 710    pub replace: Option<CursorShape>,
 711    /// Cursor shape for the visual mode.
 712    ///
 713    /// Default: block
 714    pub visual: Option<CursorShape>,
 715    /// Cursor shape for the insert mode.
 716    ///
 717    /// The default value follows the primary cursor_shape.
 718    pub insert: Option<CursorShape>,
 719}
 720
 721/// Settings specific to journaling
 722#[skip_serializing_none]
 723#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
 724pub struct JournalSettingsContent {
 725    /// The path of the directory where journal entries are stored.
 726    ///
 727    /// Default: `~`
 728    pub path: Option<String>,
 729    /// What format to display the hours in.
 730    ///
 731    /// Default: hour12
 732    pub hour_format: Option<HourFormat>,
 733}
 734
 735#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
 736#[serde(rename_all = "snake_case")]
 737pub enum HourFormat {
 738    #[default]
 739    Hour12,
 740    Hour24,
 741}
 742
 743#[skip_serializing_none]
 744#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
 745pub struct OutlinePanelSettingsContent {
 746    /// Whether to show the outline panel button in the status bar.
 747    ///
 748    /// Default: true
 749    pub button: Option<bool>,
 750    /// Customize default width (in pixels) taken by outline panel
 751    ///
 752    /// Default: 240
 753    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
 754    pub default_width: Option<f32>,
 755    /// The position of outline panel
 756    ///
 757    /// Default: left
 758    pub dock: Option<DockSide>,
 759    /// Whether to show file icons in the outline panel.
 760    ///
 761    /// Default: true
 762    pub file_icons: Option<bool>,
 763    /// Whether to show folder icons or chevrons for directories in the outline panel.
 764    ///
 765    /// Default: true
 766    pub folder_icons: Option<bool>,
 767    /// Whether to show the git status in the outline panel.
 768    ///
 769    /// Default: true
 770    pub git_status: Option<bool>,
 771    /// Amount of indentation (in pixels) for nested items.
 772    ///
 773    /// Default: 20
 774    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
 775    pub indent_size: Option<f32>,
 776    /// Whether to reveal it in the outline panel automatically,
 777    /// when a corresponding project entry becomes active.
 778    /// Gitignored entries are never auto revealed.
 779    ///
 780    /// Default: true
 781    pub auto_reveal_entries: Option<bool>,
 782    /// Whether to fold directories automatically
 783    /// when directory has only one directory inside.
 784    ///
 785    /// Default: true
 786    pub auto_fold_dirs: Option<bool>,
 787    /// Settings related to indent guides in the outline panel.
 788    pub indent_guides: Option<IndentGuidesSettingsContent>,
 789    /// Scrollbar-related settings
 790    pub scrollbar: Option<ScrollbarSettingsContent>,
 791    /// Default depth to expand outline items in the current file.
 792    /// The default depth to which outline entries are expanded on reveal.
 793    /// - Set to 0 to collapse all items that have children
 794    /// - Set to 1 or higher to collapse items at that depth or deeper
 795    ///
 796    /// Default: 100
 797    pub expand_outlines_with_depth: Option<usize>,
 798}
 799
 800#[derive(
 801    Clone,
 802    Copy,
 803    Debug,
 804    PartialEq,
 805    Eq,
 806    Serialize,
 807    Deserialize,
 808    JsonSchema,
 809    MergeFrom,
 810    strum::VariantArray,
 811    strum::VariantNames,
 812)]
 813#[serde(rename_all = "snake_case")]
 814pub enum DockSide {
 815    Left,
 816    Right,
 817}
 818
 819#[derive(
 820    Copy,
 821    Clone,
 822    Debug,
 823    PartialEq,
 824    Eq,
 825    Deserialize,
 826    Serialize,
 827    JsonSchema,
 828    MergeFrom,
 829    strum::VariantArray,
 830    strum::VariantNames,
 831)]
 832#[serde(rename_all = "snake_case")]
 833pub enum ShowIndentGuides {
 834    Always,
 835    Never,
 836}
 837
 838#[skip_serializing_none]
 839#[derive(
 840    Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default,
 841)]
 842pub struct IndentGuidesSettingsContent {
 843    /// When to show the scrollbar in the outline panel.
 844    pub show: Option<ShowIndentGuides>,
 845}
 846
 847#[derive(Clone, Copy, Default, PartialEq, Debug, JsonSchema, MergeFrom, Deserialize, Serialize)]
 848#[serde(rename_all = "snake_case")]
 849pub enum LineIndicatorFormat {
 850    Short,
 851    #[default]
 852    Long,
 853}
 854
 855/// The settings for the image viewer.
 856#[skip_serializing_none]
 857#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Default, PartialEq)]
 858pub struct ImageViewerSettingsContent {
 859    /// The unit to use for displaying image file sizes.
 860    ///
 861    /// Default: "binary"
 862    pub unit: Option<ImageFileSizeUnit>,
 863}
 864
 865#[skip_serializing_none]
 866#[derive(
 867    Clone,
 868    Copy,
 869    Debug,
 870    Serialize,
 871    Deserialize,
 872    JsonSchema,
 873    MergeFrom,
 874    Default,
 875    PartialEq,
 876    strum::VariantArray,
 877    strum::VariantNames,
 878)]
 879#[serde(rename_all = "snake_case")]
 880pub enum ImageFileSizeUnit {
 881    /// Displays file size in binary units (e.g., KiB, MiB).
 882    #[default]
 883    Binary,
 884    /// Displays file size in decimal units (e.g., KB, MB).
 885    Decimal,
 886}
 887
 888#[skip_serializing_none]
 889#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
 890pub struct RemoteSettingsContent {
 891    pub ssh_connections: Option<Vec<SshConnection>>,
 892    pub wsl_connections: Option<Vec<WslConnection>>,
 893    pub read_ssh_config: Option<bool>,
 894}
 895
 896#[skip_serializing_none]
 897#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
 898pub struct SshConnection {
 899    pub host: SharedString,
 900    pub username: Option<String>,
 901    pub port: Option<u16>,
 902    #[serde(default)]
 903    pub args: Vec<String>,
 904    #[serde(default)]
 905    pub projects: collections::BTreeSet<SshProject>,
 906    /// Name to use for this server in UI.
 907    pub nickname: Option<String>,
 908    // By default Zed will download the binary to the host directly.
 909    // If this is set to true, Zed will download the binary to your local machine,
 910    // and then upload it over the SSH connection. Useful if your SSH server has
 911    // limited outbound internet access.
 912    pub upload_binary_over_ssh: Option<bool>,
 913
 914    pub port_forwards: Option<Vec<SshPortForwardOption>>,
 915}
 916
 917#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Debug)]
 918pub struct WslConnection {
 919    pub distro_name: SharedString,
 920    pub user: Option<String>,
 921    #[serde(default)]
 922    pub projects: BTreeSet<SshProject>,
 923}
 924
 925#[skip_serializing_none]
 926#[derive(
 927    Clone, Debug, Default, Serialize, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema,
 928)]
 929pub struct SshProject {
 930    pub paths: Vec<String>,
 931}
 932
 933#[skip_serializing_none]
 934#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, MergeFrom)]
 935pub struct SshPortForwardOption {
 936    #[serde(skip_serializing_if = "Option::is_none")]
 937    pub local_host: Option<String>,
 938    pub local_port: u16,
 939    #[serde(skip_serializing_if = "Option::is_none")]
 940    pub remote_host: Option<String>,
 941    pub remote_port: u16,
 942}
 943
 944/// Settings for configuring REPL display and behavior.
 945#[skip_serializing_none]
 946#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
 947pub struct ReplSettingsContent {
 948    /// Maximum number of lines to keep in REPL's scrollback buffer.
 949    /// Clamped with [4, 256] range.
 950    ///
 951    /// Default: 32
 952    pub max_lines: Option<usize>,
 953    /// Maximum number of columns to keep in REPL's scrollback buffer.
 954    /// Clamped with [20, 512] range.
 955    ///
 956    /// Default: 128
 957    pub max_columns: Option<usize>,
 958}
 959
 960#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
 961/// An ExtendingVec in the settings can only accumulate new values.
 962///
 963/// This is useful for things like private files where you only want
 964/// to allow new values to be added.
 965///
 966/// Consider using a HashMap<String, bool> instead of this type
 967/// (like auto_install_extensions) so that user settings files can both add
 968/// and remove values from the set.
 969pub struct ExtendingVec<T>(pub Vec<T>);
 970
 971impl<T> Into<Vec<T>> for ExtendingVec<T> {
 972    fn into(self) -> Vec<T> {
 973        self.0
 974    }
 975}
 976impl<T> From<Vec<T>> for ExtendingVec<T> {
 977    fn from(vec: Vec<T>) -> Self {
 978        ExtendingVec(vec)
 979    }
 980}
 981
 982impl<T: Clone> merge_from::MergeFrom for ExtendingVec<T> {
 983    fn merge_from(&mut self, other: &Self) {
 984        self.0.extend_from_slice(other.0.as_slice());
 985    }
 986}
 987
 988/// A SaturatingBool in the settings can only ever be set to true,
 989/// later attempts to set it to false will be ignored.
 990///
 991/// Used by `disable_ai`.
 992#[derive(Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
 993pub struct SaturatingBool(pub bool);
 994
 995impl From<bool> for SaturatingBool {
 996    fn from(value: bool) -> Self {
 997        SaturatingBool(value)
 998    }
 999}
1000
1001impl From<SaturatingBool> for bool {
1002    fn from(value: SaturatingBool) -> bool {
1003        value.0
1004    }
1005}
1006
1007impl merge_from::MergeFrom for SaturatingBool {
1008    fn merge_from(&mut self, other: &Self) {
1009        self.0 |= other.0
1010    }
1011}
1012
1013#[derive(
1014    Copy,
1015    Clone,
1016    Default,
1017    Debug,
1018    PartialEq,
1019    Eq,
1020    PartialOrd,
1021    Ord,
1022    Serialize,
1023    Deserialize,
1024    MergeFrom,
1025    JsonSchema,
1026    derive_more::FromStr,
1027)]
1028#[serde(transparent)]
1029pub struct DelayMs(pub u64);
1030
1031impl From<u64> for DelayMs {
1032    fn from(n: u64) -> Self {
1033        Self(n)
1034    }
1035}
1036
1037impl std::fmt::Display for DelayMs {
1038    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1039        write!(f, "{}ms", self.0)
1040    }
1041}
1042
1043/// A wrapper type that distinguishes between an explicitly set value (including null) and an unset value.
1044///
1045/// This is useful for configuration where you need to differentiate between:
1046/// - A field that is not present in the configuration file (`Maybe::Unset`)
1047/// - A field that is explicitly set to `null` (`Maybe::Set(None)`)
1048/// - A field that is explicitly set to a value (`Maybe::Set(Some(value))`)
1049///
1050/// # Examples
1051///
1052/// In JSON:
1053/// - `{}` (field missing) deserializes to `Maybe::Unset`
1054/// - `{"field": null}` deserializes to `Maybe::Set(None)`
1055/// - `{"field": "value"}` deserializes to `Maybe::Set(Some("value"))`
1056///
1057/// WARN: This type should not be wrapped in an option inside of settings, otherwise the default `serde_json` behavior
1058/// of treating `null` and missing as the `Option::None` will be used
1059#[derive(Debug, Clone, PartialEq, Eq, strum::EnumDiscriminants, Default)]
1060#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
1061pub enum Maybe<T> {
1062    /// An explicitly set value, which may be `None` (representing JSON `null`) or `Some(value)`.
1063    Set(Option<T>),
1064    /// A value that was not present in the configuration.
1065    #[default]
1066    Unset,
1067}
1068
1069impl<T: Clone> merge_from::MergeFrom for Maybe<T> {
1070    fn merge_from(&mut self, other: &Self) {
1071        if self.is_unset() {
1072            *self = other.clone();
1073        }
1074    }
1075}
1076
1077impl<T> From<Option<Option<T>>> for Maybe<T> {
1078    fn from(value: Option<Option<T>>) -> Self {
1079        match value {
1080            Some(value) => Maybe::Set(value),
1081            None => Maybe::Unset,
1082        }
1083    }
1084}
1085
1086impl<T> Maybe<T> {
1087    pub fn is_set(&self) -> bool {
1088        matches!(self, Maybe::Set(_))
1089    }
1090
1091    pub fn is_unset(&self) -> bool {
1092        matches!(self, Maybe::Unset)
1093    }
1094
1095    pub fn into_inner(self) -> Option<T> {
1096        match self {
1097            Maybe::Set(value) => value,
1098            Maybe::Unset => None,
1099        }
1100    }
1101
1102    pub fn as_ref(&self) -> Option<&Option<T>> {
1103        match self {
1104            Maybe::Set(value) => Some(value),
1105            Maybe::Unset => None,
1106        }
1107    }
1108}
1109
1110impl<T: serde::Serialize> serde::Serialize for Maybe<T> {
1111    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1112    where
1113        S: serde::Serializer,
1114    {
1115        match self {
1116            Maybe::Set(value) => value.serialize(serializer),
1117            Maybe::Unset => serializer.serialize_none(),
1118        }
1119    }
1120}
1121
1122impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Maybe<T> {
1123    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1124    where
1125        D: serde::Deserializer<'de>,
1126    {
1127        Option::<T>::deserialize(deserializer).map(Maybe::Set)
1128    }
1129}
1130
1131impl<T: JsonSchema> JsonSchema for Maybe<T> {
1132    fn schema_name() -> std::borrow::Cow<'static, str> {
1133        format!("Nullable<{}>", T::schema_name()).into()
1134    }
1135
1136    fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
1137        let mut schema = generator.subschema_for::<Option<T>>();
1138        // Add description explaining that null is an explicit value
1139        let description = if let Some(existing_desc) =
1140            schema.get("description").and_then(|desc| desc.as_str())
1141        {
1142            format!(
1143                "{}. Note: `null` is treated as an explicit value, different from omitting the field entirely.",
1144                existing_desc
1145            )
1146        } else {
1147            "This field supports explicit `null` values. Omitting the field is different from setting it to `null`.".to_string()
1148        };
1149
1150        schema.insert("description".to_string(), description.into());
1151
1152        schema
1153    }
1154}
1155
1156#[cfg(test)]
1157mod tests {
1158    use super::*;
1159    use serde_json;
1160
1161    #[test]
1162    fn test_maybe() {
1163        #[derive(Debug, PartialEq, Serialize, Deserialize)]
1164        struct TestStruct {
1165            #[serde(default)]
1166            #[serde(skip_serializing_if = "Maybe::is_unset")]
1167            field: Maybe<String>,
1168        }
1169
1170        #[derive(Debug, PartialEq, Serialize, Deserialize)]
1171        struct NumericTest {
1172            #[serde(default)]
1173            value: Maybe<i32>,
1174        }
1175
1176        let json = "{}";
1177        let result: TestStruct = serde_json::from_str(json).unwrap();
1178        assert!(result.field.is_unset());
1179        assert_eq!(result.field, Maybe::Unset);
1180
1181        let json = r#"{"field": null}"#;
1182        let result: TestStruct = serde_json::from_str(json).unwrap();
1183        assert!(result.field.is_set());
1184        assert_eq!(result.field, Maybe::Set(None));
1185
1186        let json = r#"{"field": "hello"}"#;
1187        let result: TestStruct = serde_json::from_str(json).unwrap();
1188        assert!(result.field.is_set());
1189        assert_eq!(result.field, Maybe::Set(Some("hello".to_string())));
1190
1191        let test = TestStruct {
1192            field: Maybe::Unset,
1193        };
1194        let json = serde_json::to_string(&test).unwrap();
1195        assert_eq!(json, "{}");
1196
1197        let test = TestStruct {
1198            field: Maybe::Set(None),
1199        };
1200        let json = serde_json::to_string(&test).unwrap();
1201        assert_eq!(json, r#"{"field":null}"#);
1202
1203        let test = TestStruct {
1204            field: Maybe::Set(Some("world".to_string())),
1205        };
1206        let json = serde_json::to_string(&test).unwrap();
1207        assert_eq!(json, r#"{"field":"world"}"#);
1208
1209        let default_maybe: Maybe<i32> = Maybe::default();
1210        assert!(default_maybe.is_unset());
1211
1212        let unset: Maybe<String> = Maybe::Unset;
1213        assert!(unset.is_unset());
1214        assert!(!unset.is_set());
1215
1216        let set_none: Maybe<String> = Maybe::Set(None);
1217        assert!(set_none.is_set());
1218        assert!(!set_none.is_unset());
1219
1220        let set_some: Maybe<String> = Maybe::Set(Some("value".to_string()));
1221        assert!(set_some.is_set());
1222        assert!(!set_some.is_unset());
1223
1224        let original = TestStruct {
1225            field: Maybe::Set(Some("test".to_string())),
1226        };
1227        let json = serde_json::to_string(&original).unwrap();
1228        let deserialized: TestStruct = serde_json::from_str(&json).unwrap();
1229        assert_eq!(original, deserialized);
1230
1231        let json = r#"{"value": 42}"#;
1232        let result: NumericTest = serde_json::from_str(json).unwrap();
1233        assert_eq!(result.value, Maybe::Set(Some(42)));
1234
1235        let json = r#"{"value": null}"#;
1236        let result: NumericTest = serde_json::from_str(json).unwrap();
1237        assert_eq!(result.value, Maybe::Set(None));
1238
1239        let json = "{}";
1240        let result: NumericTest = serde_json::from_str(json).unwrap();
1241        assert_eq!(result.value, Maybe::Unset);
1242
1243        // Test JsonSchema implementation
1244        use schemars::schema_for;
1245        let schema = schema_for!(Maybe<String>);
1246        let schema_json = serde_json::to_value(&schema).unwrap();
1247
1248        // Verify the description mentions that null is an explicit value
1249        let description = schema_json["description"].as_str().unwrap();
1250        assert!(
1251            description.contains("null") && description.contains("explicit"),
1252            "Schema description should mention that null is an explicit value. Got: {}",
1253            description
1254        );
1255    }
1256}