page_data.rs

   1use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt as _};
   2use gpui::{Action as _, App};
   3use itertools::Itertools as _;
   4use settings::{
   5    AudioInputDeviceName, AudioOutputDeviceName, LanguageSettingsContent, SemanticTokens,
   6    SettingsContent,
   7};
   8use std::sync::{Arc, OnceLock};
   9use strum::{EnumMessage, IntoDiscriminant as _, VariantArray};
  10use ui::IntoElement;
  11
  12use crate::{
  13    ActionLink, DynamicItem, PROJECT, SettingField, SettingItem, SettingsFieldMetadata,
  14    SettingsPage, SettingsPageItem, SubPageLink, USER, active_language, all_language_names,
  15    pages::{
  16        open_audio_test_window, render_edit_prediction_setup_page,
  17        render_tool_permissions_setup_page,
  18    },
  19};
  20
  21const DEFAULT_STRING: String = String::new();
  22/// A default empty string reference. Useful in `pick` functions for cases either in dynamic item fields, or when dealing with `settings::Maybe`
  23/// to avoid the "NO DEFAULT" case.
  24const DEFAULT_EMPTY_STRING: Option<&String> = Some(&DEFAULT_STRING);
  25
  26const DEFAULT_AUDIO_OUTPUT: AudioOutputDeviceName = AudioOutputDeviceName(None);
  27const DEFAULT_EMPTY_AUDIO_OUTPUT: Option<&AudioOutputDeviceName> = Some(&DEFAULT_AUDIO_OUTPUT);
  28const DEFAULT_AUDIO_INPUT: AudioInputDeviceName = AudioInputDeviceName(None);
  29const DEFAULT_EMPTY_AUDIO_INPUT: Option<&AudioInputDeviceName> = Some(&DEFAULT_AUDIO_INPUT);
  30
  31macro_rules! concat_sections {
  32    (@vec, $($arr:expr),+ $(,)?) => {{
  33        let total_len = 0_usize $(+ $arr.len())+;
  34        let mut out = Vec::with_capacity(total_len);
  35
  36        $(
  37            out.extend($arr);
  38        )+
  39
  40        out
  41    }};
  42
  43    ($($arr:expr),+ $(,)?) => {{
  44        let total_len = 0_usize $(+ $arr.len())+;
  45
  46        let mut out: Box<[std::mem::MaybeUninit<_>]> = Box::new_uninit_slice(total_len);
  47
  48        let mut index = 0usize;
  49        $(
  50            let array = $arr;
  51            for item in array {
  52                out[index].write(item);
  53                index += 1;
  54            }
  55        )+
  56
  57        debug_assert_eq!(index, total_len);
  58
  59        // SAFETY: we wrote exactly `total_len` elements.
  60        unsafe { out.assume_init() }
  61    }};
  62}
  63
  64pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
  65    vec![
  66        general_page(),
  67        appearance_page(),
  68        keymap_page(),
  69        editor_page(),
  70        languages_and_tools_page(cx),
  71        search_and_files_page(),
  72        window_and_layout_page(),
  73        panels_page(),
  74        debugger_page(),
  75        terminal_page(),
  76        version_control_page(),
  77        collaboration_page(),
  78        ai_page(cx),
  79        network_page(),
  80    ]
  81}
  82
  83fn general_page() -> SettingsPage {
  84    fn general_settings_section() -> [SettingsPageItem; 8] {
  85        [
  86            SettingsPageItem::SectionHeader("General Settings"),
  87            SettingsPageItem::SettingItem(SettingItem {
  88                files: PROJECT,
  89                title: "Project Name",
  90                description: "The displayed name of this project. If left empty, the root directory name will be displayed.",
  91                field: Box::new(SettingField {
  92                    json_path: Some("project_name"),
  93                    pick: |settings_content| {
  94                        settings_content
  95                            .project
  96                            .worktree
  97                            .project_name
  98                            .as_ref()
  99                            .or(DEFAULT_EMPTY_STRING)
 100                    },
 101                    write: |settings_content, value| {
 102                        settings_content.project.worktree.project_name =
 103                            value.filter(|name| !name.is_empty());
 104                    },
 105                }),
 106                metadata: Some(Box::new(SettingsFieldMetadata {
 107                    placeholder: Some("Project Name"),
 108                    ..Default::default()
 109                })),
 110            }),
 111            SettingsPageItem::SettingItem(SettingItem {
 112                title: "When Closing With No Tabs",
 113                description: "What to do when using the 'close active item' action with no tabs.",
 114                field: Box::new(SettingField {
 115                    json_path: Some("when_closing_with_no_tabs"),
 116                    pick: |settings_content| {
 117                        settings_content
 118                            .workspace
 119                            .when_closing_with_no_tabs
 120                            .as_ref()
 121                    },
 122                    write: |settings_content, value| {
 123                        settings_content.workspace.when_closing_with_no_tabs = value;
 124                    },
 125                }),
 126                metadata: None,
 127                files: USER,
 128            }),
 129            SettingsPageItem::SettingItem(SettingItem {
 130                title: "On Last Window Closed",
 131                description: "What to do when the last window is closed.",
 132                field: Box::new(SettingField {
 133                    json_path: Some("on_last_window_closed"),
 134                    pick: |settings_content| {
 135                        settings_content.workspace.on_last_window_closed.as_ref()
 136                    },
 137                    write: |settings_content, value| {
 138                        settings_content.workspace.on_last_window_closed = value;
 139                    },
 140                }),
 141                metadata: None,
 142                files: USER,
 143            }),
 144            SettingsPageItem::SettingItem(SettingItem {
 145                title: "Use System Path Prompts",
 146                description: "Use native OS dialogs for 'Open' and 'Save As'.",
 147                field: Box::new(SettingField {
 148                    json_path: Some("use_system_path_prompts"),
 149                    pick: |settings_content| {
 150                        settings_content.workspace.use_system_path_prompts.as_ref()
 151                    },
 152                    write: |settings_content, value| {
 153                        settings_content.workspace.use_system_path_prompts = value;
 154                    },
 155                }),
 156                metadata: None,
 157                files: USER,
 158            }),
 159            SettingsPageItem::SettingItem(SettingItem {
 160                title: "Use System Prompts",
 161                description: "Use native OS dialogs for confirmations.",
 162                field: Box::new(SettingField {
 163                    json_path: Some("use_system_prompts"),
 164                    pick: |settings_content| settings_content.workspace.use_system_prompts.as_ref(),
 165                    write: |settings_content, value| {
 166                        settings_content.workspace.use_system_prompts = value;
 167                    },
 168                }),
 169                metadata: None,
 170                files: USER,
 171            }),
 172            SettingsPageItem::SettingItem(SettingItem {
 173                title: "Redact Private Values",
 174                description: "Hide the values of variables in private files.",
 175                field: Box::new(SettingField {
 176                    json_path: Some("redact_private_values"),
 177                    pick: |settings_content| settings_content.editor.redact_private_values.as_ref(),
 178                    write: |settings_content, value| {
 179                        settings_content.editor.redact_private_values = value;
 180                    },
 181                }),
 182                metadata: None,
 183                files: USER,
 184            }),
 185            SettingsPageItem::SettingItem(SettingItem {
 186                title: "Private Files",
 187                description: "Globs to match against file paths to determine if a file is private.",
 188                field: Box::new(
 189                    SettingField {
 190                        json_path: Some("worktree.private_files"),
 191                        pick: |settings_content| {
 192                            settings_content.project.worktree.private_files.as_ref()
 193                        },
 194                        write: |settings_content, value| {
 195                            settings_content.project.worktree.private_files = value;
 196                        },
 197                    }
 198                    .unimplemented(),
 199                ),
 200                metadata: None,
 201                files: USER,
 202            }),
 203        ]
 204    }
 205    fn security_section() -> [SettingsPageItem; 2] {
 206        [
 207            SettingsPageItem::SectionHeader("Security"),
 208            SettingsPageItem::SettingItem(SettingItem {
 209                title: "Trust All Projects By Default",
 210                description: "When opening Zed, avoid Restricted Mode by auto-trusting all projects, enabling use of all features without having to give permission to each new project.",
 211                field: Box::new(SettingField {
 212                    json_path: Some("session.trust_all_projects"),
 213                    pick: |settings_content| {
 214                        settings_content
 215                            .session
 216                            .as_ref()
 217                            .and_then(|session| session.trust_all_worktrees.as_ref())
 218                    },
 219                    write: |settings_content, value| {
 220                        settings_content
 221                            .session
 222                            .get_or_insert_default()
 223                            .trust_all_worktrees = value;
 224                    },
 225                }),
 226                metadata: None,
 227                files: USER,
 228            }),
 229        ]
 230    }
 231
 232    fn workspace_restoration_section() -> [SettingsPageItem; 3] {
 233        [
 234            SettingsPageItem::SectionHeader("Workspace Restoration"),
 235            SettingsPageItem::SettingItem(SettingItem {
 236                title: "Restore Unsaved Buffers",
 237                description: "Whether or not to restore unsaved buffers on restart.",
 238                field: Box::new(SettingField {
 239                    json_path: Some("session.restore_unsaved_buffers"),
 240                    pick: |settings_content| {
 241                        settings_content
 242                            .session
 243                            .as_ref()
 244                            .and_then(|session| session.restore_unsaved_buffers.as_ref())
 245                    },
 246                    write: |settings_content, value| {
 247                        settings_content
 248                            .session
 249                            .get_or_insert_default()
 250                            .restore_unsaved_buffers = value;
 251                    },
 252                }),
 253                metadata: None,
 254                files: USER,
 255            }),
 256            SettingsPageItem::SettingItem(SettingItem {
 257                title: "Restore On Startup",
 258                description: "What to restore from the previous session when opening Zed.",
 259                field: Box::new(SettingField {
 260                    json_path: Some("restore_on_startup"),
 261                    pick: |settings_content| settings_content.workspace.restore_on_startup.as_ref(),
 262                    write: |settings_content, value| {
 263                        settings_content.workspace.restore_on_startup = value;
 264                    },
 265                }),
 266                metadata: None,
 267                files: USER,
 268            }),
 269        ]
 270    }
 271
 272    fn scoped_settings_section() -> [SettingsPageItem; 3] {
 273        [
 274            SettingsPageItem::SectionHeader("Scoped Settings"),
 275            SettingsPageItem::SettingItem(SettingItem {
 276                files: USER,
 277                title: "Preview Channel",
 278                description: "Which settings should be activated only in Preview build of Zed.",
 279                field: Box::new(
 280                    SettingField {
 281                        json_path: Some("preview_channel_settings"),
 282                        pick: |settings_content| Some(settings_content),
 283                        write: |_settings_content, _value| {},
 284                    }
 285                    .unimplemented(),
 286                ),
 287                metadata: None,
 288            }),
 289            SettingsPageItem::SettingItem(SettingItem {
 290                files: USER,
 291                title: "Settings Profiles",
 292                description: "Any number of settings profiles that are temporarily applied on top of your existing user settings.",
 293                field: Box::new(
 294                    SettingField {
 295                        json_path: Some("settings_profiles"),
 296                        pick: |settings_content| Some(settings_content),
 297                        write: |_settings_content, _value| {},
 298                    }
 299                    .unimplemented(),
 300                ),
 301                metadata: None,
 302            }),
 303        ]
 304    }
 305
 306    fn privacy_section() -> [SettingsPageItem; 3] {
 307        [
 308            SettingsPageItem::SectionHeader("Privacy"),
 309            SettingsPageItem::SettingItem(SettingItem {
 310                title: "Telemetry Diagnostics",
 311                description: "Send debug information like crash reports.",
 312                field: Box::new(SettingField {
 313                    json_path: Some("telemetry.diagnostics"),
 314                    pick: |settings_content| {
 315                        settings_content
 316                            .telemetry
 317                            .as_ref()
 318                            .and_then(|telemetry| telemetry.diagnostics.as_ref())
 319                    },
 320                    write: |settings_content, value| {
 321                        settings_content
 322                            .telemetry
 323                            .get_or_insert_default()
 324                            .diagnostics = value;
 325                    },
 326                }),
 327                metadata: None,
 328                files: USER,
 329            }),
 330            SettingsPageItem::SettingItem(SettingItem {
 331                title: "Telemetry Metrics",
 332                description: "Send anonymized usage data like what languages you're using Zed with.",
 333                field: Box::new(SettingField {
 334                    json_path: Some("telemetry.metrics"),
 335                    pick: |settings_content| {
 336                        settings_content
 337                            .telemetry
 338                            .as_ref()
 339                            .and_then(|telemetry| telemetry.metrics.as_ref())
 340                    },
 341                    write: |settings_content, value| {
 342                        settings_content.telemetry.get_or_insert_default().metrics = value;
 343                    },
 344                }),
 345                metadata: None,
 346                files: USER,
 347            }),
 348        ]
 349    }
 350
 351    fn auto_update_section() -> [SettingsPageItem; 2] {
 352        [
 353            SettingsPageItem::SectionHeader("Auto Update"),
 354            SettingsPageItem::SettingItem(SettingItem {
 355                title: "Auto Update",
 356                description: "Whether or not to automatically check for updates.",
 357                field: Box::new(SettingField {
 358                    json_path: Some("auto_update"),
 359                    pick: |settings_content| settings_content.auto_update.as_ref(),
 360                    write: |settings_content, value| {
 361                        settings_content.auto_update = value;
 362                    },
 363                }),
 364                metadata: None,
 365                files: USER,
 366            }),
 367        ]
 368    }
 369
 370    SettingsPage {
 371        title: "General",
 372        items: concat_sections!(
 373            general_settings_section(),
 374            security_section(),
 375            workspace_restoration_section(),
 376            scoped_settings_section(),
 377            privacy_section(),
 378            auto_update_section(),
 379        ),
 380    }
 381}
 382
 383fn appearance_page() -> SettingsPage {
 384    fn theme_section() -> [SettingsPageItem; 3] {
 385        [
 386            SettingsPageItem::SectionHeader("Theme"),
 387            SettingsPageItem::DynamicItem(DynamicItem {
 388                discriminant: SettingItem {
 389                    files: USER,
 390                    title: "Theme Mode",
 391                    description: "Choose a static, fixed theme or dynamically select themes based on appearance and light/dark modes.",
 392                    field: Box::new(SettingField {
 393                        json_path: Some("theme$"),
 394                        pick: |settings_content| {
 395                            Some(&dynamic_variants::<settings::ThemeSelection>()[
 396                                settings_content
 397                                    .theme
 398                                    .theme
 399                                    .as_ref()?
 400                                    .discriminant() as usize])
 401                        },
 402                        write: |settings_content, value| {
 403                            let Some(value) = value else {
 404                                settings_content.theme.theme = None;
 405                                return;
 406                            };
 407                            let settings_value = settings_content.theme.theme.get_or_insert_default();
 408                            *settings_value = match value {
 409                                settings::ThemeSelectionDiscriminants::Static => {
 410                                    let name = match settings_value {
 411                                        settings::ThemeSelection::Static(_) => return,
 412                                        settings::ThemeSelection::Dynamic { mode, light, dark } => {
 413                                            match mode {
 414                                                theme_settings::ThemeAppearanceMode::Light => light.clone(),
 415                                                theme_settings::ThemeAppearanceMode::Dark => dark.clone(),
 416                                                theme_settings::ThemeAppearanceMode::System => dark.clone(), // no cx, can't determine correct choice
 417                                            }
 418                                        },
 419                                    };
 420                                    settings::ThemeSelection::Static(name)
 421                                },
 422                                settings::ThemeSelectionDiscriminants::Dynamic => {
 423                                    let static_name = match settings_value {
 424                                        settings::ThemeSelection::Static(theme_name) => theme_name.clone(),
 425                                        settings::ThemeSelection::Dynamic {..} => return,
 426                                    };
 427
 428                                    settings::ThemeSelection::Dynamic {
 429                                        mode: settings::ThemeAppearanceMode::System,
 430                                        light: static_name.clone(),
 431                                        dark: static_name,
 432                                    }
 433                                },
 434                            };
 435                        },
 436                    }),
 437                    metadata: None,
 438                },
 439                pick_discriminant: |settings_content| {
 440                    Some(settings_content.theme.theme.as_ref()?.discriminant() as usize)
 441                },
 442                fields: dynamic_variants::<settings::ThemeSelection>().into_iter().map(|variant| {
 443                    match variant {
 444                        settings::ThemeSelectionDiscriminants::Static => vec![
 445                            SettingItem {
 446                                files: USER,
 447                                title: "Theme Name",
 448                                description: "The name of your selected theme.",
 449                                field: Box::new(SettingField {
 450                                    json_path: Some("theme"),
 451                                    pick: |settings_content| {
 452                                        match settings_content.theme.theme.as_ref() {
 453                                            Some(settings::ThemeSelection::Static(name)) => Some(name),
 454                                            _ => None
 455                                        }
 456                                    },
 457                                    write: |settings_content, value| {
 458                                        let Some(value) = value else {
 459                                            return;
 460                                        };
 461                                        match settings_content
 462                                            .theme
 463                                            .theme.get_or_insert_default() {
 464                                                settings::ThemeSelection::Static(theme_name) => *theme_name = value,
 465                                                _ => return
 466                                            }
 467                                    },
 468                                }),
 469                                metadata: None,
 470                            }
 471                        ],
 472                        settings::ThemeSelectionDiscriminants::Dynamic => vec![
 473                            SettingItem {
 474                                files: USER,
 475                                title: "Mode",
 476                                description: "Choose whether to use the selected light or dark theme or to follow your OS appearance configuration.",
 477                                field: Box::new(SettingField {
 478                                    json_path: Some("theme.mode"),
 479                                    pick: |settings_content| {
 480                                        match settings_content.theme.theme.as_ref() {
 481                                            Some(settings::ThemeSelection::Dynamic { mode, ..}) => Some(mode),
 482                                            _ => None
 483                                        }
 484                                    },
 485                                    write: |settings_content, value| {
 486                                        let Some(value) = value else {
 487                                            return;
 488                                        };
 489                                        match settings_content
 490                                            .theme
 491                                            .theme.get_or_insert_default() {
 492                                                settings::ThemeSelection::Dynamic{ mode, ..} => *mode = value,
 493                                                _ => return
 494                                            }
 495                                    },
 496                                }),
 497                                metadata: None,
 498                            },
 499                            SettingItem {
 500                                files: USER,
 501                                title: "Light Theme",
 502                                description: "The theme to use when mode is set to light, or when mode is set to system and it is in light mode.",
 503                                field: Box::new(SettingField {
 504                                    json_path: Some("theme.light"),
 505                                    pick: |settings_content| {
 506                                        match settings_content.theme.theme.as_ref() {
 507                                            Some(settings::ThemeSelection::Dynamic { light, ..}) => Some(light),
 508                                            _ => None
 509                                        }
 510                                    },
 511                                    write: |settings_content, value| {
 512                                        let Some(value) = value else {
 513                                            return;
 514                                        };
 515                                        match settings_content
 516                                            .theme
 517                                            .theme.get_or_insert_default() {
 518                                                settings::ThemeSelection::Dynamic{ light, ..} => *light = value,
 519                                                _ => return
 520                                            }
 521                                    },
 522                                }),
 523                                metadata: None,
 524                            },
 525                            SettingItem {
 526                                files: USER,
 527                                title: "Dark Theme",
 528                                description: "The theme to use when mode is set to dark, or when mode is set to system and it is in dark mode.",
 529                                field: Box::new(SettingField {
 530                                    json_path: Some("theme.dark"),
 531                                    pick: |settings_content| {
 532                                        match settings_content.theme.theme.as_ref() {
 533                                            Some(settings::ThemeSelection::Dynamic { dark, ..}) => Some(dark),
 534                                            _ => None
 535                                        }
 536                                    },
 537                                    write: |settings_content, value| {
 538                                        let Some(value) = value else {
 539                                            return;
 540                                        };
 541                                        match settings_content
 542                                            .theme
 543                                            .theme.get_or_insert_default() {
 544                                                settings::ThemeSelection::Dynamic{ dark, ..} => *dark = value,
 545                                                _ => return
 546                                            }
 547                                    },
 548                                }),
 549                                metadata: None,
 550                            }
 551                        ],
 552                    }
 553                }).collect(),
 554            }),
 555            SettingsPageItem::DynamicItem(DynamicItem {
 556                discriminant: SettingItem {
 557                    files: USER,
 558                    title: "Icon Theme",
 559                    description: "The custom set of icons Zed will associate with files and directories.",
 560                    field: Box::new(SettingField {
 561                        json_path: Some("icon_theme$"),
 562                        pick: |settings_content| {
 563                            Some(&dynamic_variants::<settings::IconThemeSelection>()[
 564                                settings_content
 565                                    .theme
 566                                    .icon_theme
 567                                    .as_ref()?
 568                                    .discriminant() as usize])
 569                        },
 570                        write: |settings_content, value| {
 571                            let Some(value) = value else {
 572                                settings_content.theme.icon_theme = None;
 573                                return;
 574                            };
 575                            let settings_value = settings_content.theme.icon_theme.get_or_insert_with(|| {
 576                                settings::IconThemeSelection::Static(settings::IconThemeName(theme::default_icon_theme().name.clone().into()))
 577                            });
 578                            *settings_value = match value {
 579                                settings::IconThemeSelectionDiscriminants::Static => {
 580                                    let name = match settings_value {
 581                                        settings::IconThemeSelection::Static(_) => return,
 582                                        settings::IconThemeSelection::Dynamic { mode, light, dark } => {
 583                                            match mode {
 584                                                theme_settings::ThemeAppearanceMode::Light => light.clone(),
 585                                                theme_settings::ThemeAppearanceMode::Dark => dark.clone(),
 586                                                theme_settings::ThemeAppearanceMode::System => dark.clone(), // no cx, can't determine correct choice
 587                                            }
 588                                        },
 589                                    };
 590                                    settings::IconThemeSelection::Static(name)
 591                                },
 592                                settings::IconThemeSelectionDiscriminants::Dynamic => {
 593                                    let static_name = match settings_value {
 594                                        settings::IconThemeSelection::Static(theme_name) => theme_name.clone(),
 595                                        settings::IconThemeSelection::Dynamic {..} => return,
 596                                    };
 597
 598                                    settings::IconThemeSelection::Dynamic {
 599                                        mode: settings::ThemeAppearanceMode::System,
 600                                        light: static_name.clone(),
 601                                        dark: static_name,
 602                                    }
 603                                },
 604                            };
 605                        },
 606                    }),
 607                    metadata: None,
 608                },
 609                pick_discriminant: |settings_content| {
 610                    Some(settings_content.theme.icon_theme.as_ref()?.discriminant() as usize)
 611                },
 612                fields: dynamic_variants::<settings::IconThemeSelection>().into_iter().map(|variant| {
 613                    match variant {
 614                        settings::IconThemeSelectionDiscriminants::Static => vec![
 615                            SettingItem {
 616                                files: USER,
 617                                title: "Icon Theme Name",
 618                                description: "The name of your selected icon theme.",
 619                                field: Box::new(SettingField {
 620                                    json_path: Some("icon_theme$string"),
 621                                    pick: |settings_content| {
 622                                        match settings_content.theme.icon_theme.as_ref() {
 623                                            Some(settings::IconThemeSelection::Static(name)) => Some(name),
 624                                            _ => None
 625                                        }
 626                                    },
 627                                    write: |settings_content, value| {
 628                                        let Some(value) = value else {
 629                                            return;
 630                                        };
 631                                        match settings_content
 632                                            .theme
 633                                            .icon_theme.as_mut() {
 634                                                Some(settings::IconThemeSelection::Static(theme_name)) => *theme_name = value,
 635                                                _ => return
 636                                            }
 637                                    },
 638                                }),
 639                                metadata: None,
 640                            }
 641                        ],
 642                        settings::IconThemeSelectionDiscriminants::Dynamic => vec![
 643                            SettingItem {
 644                                files: USER,
 645                                title: "Mode",
 646                                description: "Choose whether to use the selected light or dark icon theme or to follow your OS appearance configuration.",
 647                                field: Box::new(SettingField {
 648                                    json_path: Some("icon_theme"),
 649                                    pick: |settings_content| {
 650                                        match settings_content.theme.icon_theme.as_ref() {
 651                                            Some(settings::IconThemeSelection::Dynamic { mode, ..}) => Some(mode),
 652                                            _ => None
 653                                        }
 654                                    },
 655                                    write: |settings_content, value| {
 656                                        let Some(value) = value else {
 657                                            return;
 658                                        };
 659                                        match settings_content
 660                                            .theme
 661                                            .icon_theme.as_mut() {
 662                                                Some(settings::IconThemeSelection::Dynamic{ mode, ..}) => *mode = value,
 663                                                _ => return
 664                                            }
 665                                    },
 666                                }),
 667                                metadata: None,
 668                            },
 669                            SettingItem {
 670                                files: USER,
 671                                title: "Light Icon Theme",
 672                                description: "The icon theme to use when mode is set to light, or when mode is set to system and it is in light mode.",
 673                                field: Box::new(SettingField {
 674                                    json_path: Some("icon_theme.light"),
 675                                    pick: |settings_content| {
 676                                        match settings_content.theme.icon_theme.as_ref() {
 677                                            Some(settings::IconThemeSelection::Dynamic { light, ..}) => Some(light),
 678                                            _ => None
 679                                        }
 680                                    },
 681                                    write: |settings_content, value| {
 682                                        let Some(value) = value else {
 683                                            return;
 684                                        };
 685                                        match settings_content
 686                                            .theme
 687                                            .icon_theme.as_mut() {
 688                                                Some(settings::IconThemeSelection::Dynamic{ light, ..}) => *light = value,
 689                                                _ => return
 690                                            }
 691                                    },
 692                                }),
 693                                metadata: None,
 694                            },
 695                            SettingItem {
 696                                files: USER,
 697                                title: "Dark Icon Theme",
 698                                description: "The icon theme to use when mode is set to dark, or when mode is set to system and it is in dark mode.",
 699                                field: Box::new(SettingField {
 700                                    json_path: Some("icon_theme.dark"),
 701                                    pick: |settings_content| {
 702                                        match settings_content.theme.icon_theme.as_ref() {
 703                                            Some(settings::IconThemeSelection::Dynamic { dark, ..}) => Some(dark),
 704                                            _ => None
 705                                        }
 706                                    },
 707                                    write: |settings_content, value| {
 708                                        let Some(value) = value else {
 709                                            return;
 710                                        };
 711                                        match settings_content
 712                                            .theme
 713                                            .icon_theme.as_mut() {
 714                                                Some(settings::IconThemeSelection::Dynamic{ dark, ..}) => *dark = value,
 715                                                _ => return
 716                                            }
 717                                    },
 718                                }),
 719                                metadata: None,
 720                            }
 721                        ],
 722                    }
 723                }).collect(),
 724            }),
 725        ]
 726    }
 727
 728    fn buffer_font_section() -> [SettingsPageItem; 7] {
 729        [
 730            SettingsPageItem::SectionHeader("Buffer Font"),
 731            SettingsPageItem::SettingItem(SettingItem {
 732                title: "Font Family",
 733                description: "Font family for editor text.",
 734                field: Box::new(SettingField {
 735                    json_path: Some("buffer_font_family"),
 736                    pick: |settings_content| settings_content.theme.buffer_font_family.as_ref(),
 737                    write: |settings_content, value| {
 738                        settings_content.theme.buffer_font_family = value;
 739                    },
 740                }),
 741                metadata: None,
 742                files: USER,
 743            }),
 744            SettingsPageItem::SettingItem(SettingItem {
 745                title: "Font Size",
 746                description: "Font size for editor text.",
 747                field: Box::new(SettingField {
 748                    json_path: Some("buffer_font_size"),
 749                    pick: |settings_content| settings_content.theme.buffer_font_size.as_ref(),
 750                    write: |settings_content, value| {
 751                        settings_content.theme.buffer_font_size = value;
 752                    },
 753                }),
 754                metadata: None,
 755                files: USER,
 756            }),
 757            SettingsPageItem::SettingItem(SettingItem {
 758                title: "Font Weight",
 759                description: "Font weight for editor text (100-900).",
 760                field: Box::new(SettingField {
 761                    json_path: Some("buffer_font_weight"),
 762                    pick: |settings_content| settings_content.theme.buffer_font_weight.as_ref(),
 763                    write: |settings_content, value| {
 764                        settings_content.theme.buffer_font_weight = value;
 765                    },
 766                }),
 767                metadata: None,
 768                files: USER,
 769            }),
 770            SettingsPageItem::DynamicItem(DynamicItem {
 771                discriminant: SettingItem {
 772                    files: USER,
 773                    title: "Line Height",
 774                    description: "Line height for editor text.",
 775                    field: Box::new(SettingField {
 776                        json_path: Some("buffer_line_height$"),
 777                        pick: |settings_content| {
 778                            Some(
 779                                &dynamic_variants::<settings::BufferLineHeight>()[settings_content
 780                                    .theme
 781                                    .buffer_line_height
 782                                    .as_ref()?
 783                                    .discriminant()
 784                                    as usize],
 785                            )
 786                        },
 787                        write: |settings_content, value| {
 788                            let Some(value) = value else {
 789                                settings_content.theme.buffer_line_height = None;
 790                                return;
 791                            };
 792                            let settings_value = settings_content
 793                                .theme
 794                                .buffer_line_height
 795                                .get_or_insert_with(|| settings::BufferLineHeight::default());
 796                            *settings_value = match value {
 797                                settings::BufferLineHeightDiscriminants::Comfortable => {
 798                                    settings::BufferLineHeight::Comfortable
 799                                }
 800                                settings::BufferLineHeightDiscriminants::Standard => {
 801                                    settings::BufferLineHeight::Standard
 802                                }
 803                                settings::BufferLineHeightDiscriminants::Custom => {
 804                                    let custom_value =
 805                                        theme_settings::BufferLineHeight::from(*settings_value)
 806                                            .value();
 807                                    settings::BufferLineHeight::Custom(custom_value)
 808                                }
 809                            };
 810                        },
 811                    }),
 812                    metadata: None,
 813                },
 814                pick_discriminant: |settings_content| {
 815                    Some(
 816                        settings_content
 817                            .theme
 818                            .buffer_line_height
 819                            .as_ref()?
 820                            .discriminant() as usize,
 821                    )
 822                },
 823                fields: dynamic_variants::<settings::BufferLineHeight>()
 824                    .into_iter()
 825                    .map(|variant| match variant {
 826                        settings::BufferLineHeightDiscriminants::Comfortable => vec![],
 827                        settings::BufferLineHeightDiscriminants::Standard => vec![],
 828                        settings::BufferLineHeightDiscriminants::Custom => vec![SettingItem {
 829                            files: USER,
 830                            title: "Custom Line Height",
 831                            description: "Custom line height value (must be at least 1.0).",
 832                            field: Box::new(SettingField {
 833                                json_path: Some("buffer_line_height"),
 834                                pick: |settings_content| match settings_content
 835                                    .theme
 836                                    .buffer_line_height
 837                                    .as_ref()
 838                                {
 839                                    Some(settings::BufferLineHeight::Custom(value)) => Some(value),
 840                                    _ => None,
 841                                },
 842                                write: |settings_content, value| {
 843                                    let Some(value) = value else {
 844                                        return;
 845                                    };
 846                                    match settings_content.theme.buffer_line_height.as_mut() {
 847                                        Some(settings::BufferLineHeight::Custom(line_height)) => {
 848                                            *line_height = f32::max(value, 1.0)
 849                                        }
 850                                        _ => return,
 851                                    }
 852                                },
 853                            }),
 854                            metadata: None,
 855                        }],
 856                    })
 857                    .collect(),
 858            }),
 859            SettingsPageItem::SettingItem(SettingItem {
 860                files: USER,
 861                title: "Font Features",
 862                description: "The OpenType features to enable for rendering in text buffers.",
 863                field: Box::new(
 864                    SettingField {
 865                        json_path: Some("buffer_font_features"),
 866                        pick: |settings_content| {
 867                            settings_content.theme.buffer_font_features.as_ref()
 868                        },
 869                        write: |settings_content, value| {
 870                            settings_content.theme.buffer_font_features = value;
 871                        },
 872                    }
 873                    .unimplemented(),
 874                ),
 875                metadata: None,
 876            }),
 877            SettingsPageItem::SettingItem(SettingItem {
 878                files: USER,
 879                title: "Font Fallbacks",
 880                description: "The font fallbacks to use for rendering in text buffers.",
 881                field: Box::new(
 882                    SettingField {
 883                        json_path: Some("buffer_font_fallbacks"),
 884                        pick: |settings_content| {
 885                            settings_content.theme.buffer_font_fallbacks.as_ref()
 886                        },
 887                        write: |settings_content, value| {
 888                            settings_content.theme.buffer_font_fallbacks = value;
 889                        },
 890                    }
 891                    .unimplemented(),
 892                ),
 893                metadata: None,
 894            }),
 895        ]
 896    }
 897
 898    fn ui_font_section() -> [SettingsPageItem; 6] {
 899        [
 900            SettingsPageItem::SectionHeader("UI Font"),
 901            SettingsPageItem::SettingItem(SettingItem {
 902                title: "Font Family",
 903                description: "Font family for UI elements.",
 904                field: Box::new(SettingField {
 905                    json_path: Some("ui_font_family"),
 906                    pick: |settings_content| settings_content.theme.ui_font_family.as_ref(),
 907                    write: |settings_content, value| {
 908                        settings_content.theme.ui_font_family = value;
 909                    },
 910                }),
 911                metadata: None,
 912                files: USER,
 913            }),
 914            SettingsPageItem::SettingItem(SettingItem {
 915                title: "Font Size",
 916                description: "Font size for UI elements.",
 917                field: Box::new(SettingField {
 918                    json_path: Some("ui_font_size"),
 919                    pick: |settings_content| settings_content.theme.ui_font_size.as_ref(),
 920                    write: |settings_content, value| {
 921                        settings_content.theme.ui_font_size = value;
 922                    },
 923                }),
 924                metadata: None,
 925                files: USER,
 926            }),
 927            SettingsPageItem::SettingItem(SettingItem {
 928                title: "Font Weight",
 929                description: "Font weight for UI elements (100-900).",
 930                field: Box::new(SettingField {
 931                    json_path: Some("ui_font_weight"),
 932                    pick: |settings_content| settings_content.theme.ui_font_weight.as_ref(),
 933                    write: |settings_content, value| {
 934                        settings_content.theme.ui_font_weight = value;
 935                    },
 936                }),
 937                metadata: None,
 938                files: USER,
 939            }),
 940            SettingsPageItem::SettingItem(SettingItem {
 941                files: USER,
 942                title: "Font Features",
 943                description: "The OpenType features to enable for rendering in UI elements.",
 944                field: Box::new(
 945                    SettingField {
 946                        json_path: Some("ui_font_features"),
 947                        pick: |settings_content| settings_content.theme.ui_font_features.as_ref(),
 948                        write: |settings_content, value| {
 949                            settings_content.theme.ui_font_features = value;
 950                        },
 951                    }
 952                    .unimplemented(),
 953                ),
 954                metadata: None,
 955            }),
 956            SettingsPageItem::SettingItem(SettingItem {
 957                files: USER,
 958                title: "Font Fallbacks",
 959                description: "The font fallbacks to use for rendering in the UI.",
 960                field: Box::new(
 961                    SettingField {
 962                        json_path: Some("ui_font_fallbacks"),
 963                        pick: |settings_content| settings_content.theme.ui_font_fallbacks.as_ref(),
 964                        write: |settings_content, value| {
 965                            settings_content.theme.ui_font_fallbacks = value;
 966                        },
 967                    }
 968                    .unimplemented(),
 969                ),
 970                metadata: None,
 971            }),
 972        ]
 973    }
 974
 975    fn agent_panel_font_section() -> [SettingsPageItem; 3] {
 976        [
 977            SettingsPageItem::SectionHeader("Agent Panel Font"),
 978            SettingsPageItem::SettingItem(SettingItem {
 979                title: "UI Font Size",
 980                description: "Font size for agent response text in the agent panel. Falls back to the regular UI font size.",
 981                field: Box::new(SettingField {
 982                    json_path: Some("agent_ui_font_size"),
 983                    pick: |settings_content| {
 984                        settings_content
 985                            .theme
 986                            .agent_ui_font_size
 987                            .as_ref()
 988                            .or(settings_content.theme.ui_font_size.as_ref())
 989                    },
 990                    write: |settings_content, value| {
 991                        settings_content.theme.agent_ui_font_size = value;
 992                    },
 993                }),
 994                metadata: None,
 995                files: USER,
 996            }),
 997            SettingsPageItem::SettingItem(SettingItem {
 998                title: "Buffer Font Size",
 999                description: "Font size for user messages text in the agent panel.",
1000                field: Box::new(SettingField {
1001                    json_path: Some("agent_buffer_font_size"),
1002                    pick: |settings_content| {
1003                        settings_content
1004                            .theme
1005                            .agent_buffer_font_size
1006                            .as_ref()
1007                            .or(settings_content.theme.buffer_font_size.as_ref())
1008                    },
1009                    write: |settings_content, value| {
1010                        settings_content.theme.agent_buffer_font_size = value;
1011                    },
1012                }),
1013                metadata: None,
1014                files: USER,
1015            }),
1016        ]
1017    }
1018
1019    fn text_rendering_section() -> [SettingsPageItem; 2] {
1020        [
1021            SettingsPageItem::SectionHeader("Text Rendering"),
1022            SettingsPageItem::SettingItem(SettingItem {
1023                title: "Text Rendering Mode",
1024                description: "The text rendering mode to use.",
1025                field: Box::new(SettingField {
1026                    json_path: Some("text_rendering_mode"),
1027                    pick: |settings_content| {
1028                        settings_content.workspace.text_rendering_mode.as_ref()
1029                    },
1030                    write: |settings_content, value| {
1031                        settings_content.workspace.text_rendering_mode = value;
1032                    },
1033                }),
1034                metadata: None,
1035                files: USER,
1036            }),
1037        ]
1038    }
1039
1040    fn cursor_section() -> [SettingsPageItem; 5] {
1041        [
1042            SettingsPageItem::SectionHeader("Cursor"),
1043            SettingsPageItem::SettingItem(SettingItem {
1044                title: "Multi Cursor Modifier",
1045                description: "Modifier key for adding multiple cursors.",
1046                field: Box::new(SettingField {
1047                    json_path: Some("multi_cursor_modifier"),
1048                    pick: |settings_content| settings_content.editor.multi_cursor_modifier.as_ref(),
1049                    write: |settings_content, value| {
1050                        settings_content.editor.multi_cursor_modifier = value;
1051                    },
1052                }),
1053                metadata: None,
1054                files: USER,
1055            }),
1056            SettingsPageItem::SettingItem(SettingItem {
1057                title: "Cursor Blink",
1058                description: "Whether the cursor blinks in the editor.",
1059                field: Box::new(SettingField {
1060                    json_path: Some("cursor_blink"),
1061                    pick: |settings_content| settings_content.editor.cursor_blink.as_ref(),
1062                    write: |settings_content, value| {
1063                        settings_content.editor.cursor_blink = value;
1064                    },
1065                }),
1066                metadata: None,
1067                files: USER,
1068            }),
1069            SettingsPageItem::SettingItem(SettingItem {
1070                title: "Cursor Shape",
1071                description: "Cursor shape for the editor.",
1072                field: Box::new(SettingField {
1073                    json_path: Some("cursor_shape"),
1074                    pick: |settings_content| settings_content.editor.cursor_shape.as_ref(),
1075                    write: |settings_content, value| {
1076                        settings_content.editor.cursor_shape = value;
1077                    },
1078                }),
1079                metadata: None,
1080                files: USER,
1081            }),
1082            SettingsPageItem::SettingItem(SettingItem {
1083                title: "Hide Mouse",
1084                description: "When to hide the mouse cursor.",
1085                field: Box::new(SettingField {
1086                    json_path: Some("hide_mouse"),
1087                    pick: |settings_content| settings_content.editor.hide_mouse.as_ref(),
1088                    write: |settings_content, value| {
1089                        settings_content.editor.hide_mouse = value;
1090                    },
1091                }),
1092                metadata: None,
1093                files: USER,
1094            }),
1095        ]
1096    }
1097
1098    fn highlighting_section() -> [SettingsPageItem; 6] {
1099        [
1100            SettingsPageItem::SectionHeader("Highlighting"),
1101            SettingsPageItem::SettingItem(SettingItem {
1102                title: "Unnecessary Code Fade",
1103                description: "How much to fade out unused code (0.0 - 0.9).",
1104                field: Box::new(SettingField {
1105                    json_path: Some("unnecessary_code_fade"),
1106                    pick: |settings_content| settings_content.theme.unnecessary_code_fade.as_ref(),
1107                    write: |settings_content, value| {
1108                        settings_content.theme.unnecessary_code_fade = value;
1109                    },
1110                }),
1111                metadata: None,
1112                files: USER,
1113            }),
1114            SettingsPageItem::SettingItem(SettingItem {
1115                title: "Current Line Highlight",
1116                description: "How to highlight the current line.",
1117                field: Box::new(SettingField {
1118                    json_path: Some("current_line_highlight"),
1119                    pick: |settings_content| {
1120                        settings_content.editor.current_line_highlight.as_ref()
1121                    },
1122                    write: |settings_content, value| {
1123                        settings_content.editor.current_line_highlight = value;
1124                    },
1125                }),
1126                metadata: None,
1127                files: USER,
1128            }),
1129            SettingsPageItem::SettingItem(SettingItem {
1130                title: "Selection Highlight",
1131                description: "Highlight all occurrences of selected text.",
1132                field: Box::new(SettingField {
1133                    json_path: Some("selection_highlight"),
1134                    pick: |settings_content| settings_content.editor.selection_highlight.as_ref(),
1135                    write: |settings_content, value| {
1136                        settings_content.editor.selection_highlight = value;
1137                    },
1138                }),
1139                metadata: None,
1140                files: USER,
1141            }),
1142            SettingsPageItem::SettingItem(SettingItem {
1143                title: "Rounded Selection",
1144                description: "Whether the text selection should have rounded corners.",
1145                field: Box::new(SettingField {
1146                    json_path: Some("rounded_selection"),
1147                    pick: |settings_content| settings_content.editor.rounded_selection.as_ref(),
1148                    write: |settings_content, value| {
1149                        settings_content.editor.rounded_selection = value;
1150                    },
1151                }),
1152                metadata: None,
1153                files: USER,
1154            }),
1155            SettingsPageItem::SettingItem(SettingItem {
1156                title: "Minimum Contrast For Highlights",
1157                description: "The minimum APCA perceptual contrast to maintain when rendering text over highlight backgrounds.",
1158                field: Box::new(SettingField {
1159                    json_path: Some("minimum_contrast_for_highlights"),
1160                    pick: |settings_content| {
1161                        settings_content
1162                            .editor
1163                            .minimum_contrast_for_highlights
1164                            .as_ref()
1165                    },
1166                    write: |settings_content, value| {
1167                        settings_content.editor.minimum_contrast_for_highlights = value;
1168                    },
1169                }),
1170                metadata: None,
1171                files: USER,
1172            }),
1173        ]
1174    }
1175
1176    fn guides_section() -> [SettingsPageItem; 3] {
1177        [
1178            SettingsPageItem::SectionHeader("Guides"),
1179            SettingsPageItem::SettingItem(SettingItem {
1180                title: "Show Wrap Guides",
1181                description: "Show wrap guides (vertical rulers).",
1182                field: Box::new(SettingField {
1183                    json_path: Some("show_wrap_guides"),
1184                    pick: |settings_content| {
1185                        settings_content
1186                            .project
1187                            .all_languages
1188                            .defaults
1189                            .show_wrap_guides
1190                            .as_ref()
1191                    },
1192                    write: |settings_content, value| {
1193                        settings_content
1194                            .project
1195                            .all_languages
1196                            .defaults
1197                            .show_wrap_guides = value;
1198                    },
1199                }),
1200                metadata: None,
1201                files: USER | PROJECT,
1202            }),
1203            // todo(settings_ui): This needs a custom component
1204            SettingsPageItem::SettingItem(SettingItem {
1205                title: "Wrap Guides",
1206                description: "Character counts at which to show wrap guides.",
1207                field: Box::new(
1208                    SettingField {
1209                        json_path: Some("wrap_guides"),
1210                        pick: |settings_content| {
1211                            settings_content
1212                                .project
1213                                .all_languages
1214                                .defaults
1215                                .wrap_guides
1216                                .as_ref()
1217                        },
1218                        write: |settings_content, value| {
1219                            settings_content.project.all_languages.defaults.wrap_guides = value;
1220                        },
1221                    }
1222                    .unimplemented(),
1223                ),
1224                metadata: None,
1225                files: USER | PROJECT,
1226            }),
1227        ]
1228    }
1229
1230    let items: Box<[SettingsPageItem]> = concat_sections!(
1231        theme_section(),
1232        buffer_font_section(),
1233        ui_font_section(),
1234        agent_panel_font_section(),
1235        text_rendering_section(),
1236        cursor_section(),
1237        highlighting_section(),
1238        guides_section(),
1239    );
1240
1241    SettingsPage {
1242        title: "Appearance",
1243        items,
1244    }
1245}
1246
1247fn keymap_page() -> SettingsPage {
1248    fn keybindings_section() -> [SettingsPageItem; 2] {
1249        [
1250            SettingsPageItem::SectionHeader("Keybindings"),
1251            SettingsPageItem::ActionLink(ActionLink {
1252                title: "Edit Keybindings".into(),
1253                description: Some("Customize keybindings in the keymap editor.".into()),
1254                button_text: "Open Keymap".into(),
1255                on_click: Arc::new(|settings_window, window, cx| {
1256                    let Some(original_window) = settings_window.original_window else {
1257                        return;
1258                    };
1259                    original_window
1260                        .update(cx, |_workspace, original_window, cx| {
1261                            original_window
1262                                .dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx);
1263                            original_window.activate_window();
1264                        })
1265                        .ok();
1266                    window.remove_window();
1267                }),
1268                files: USER,
1269            }),
1270        ]
1271    }
1272
1273    fn base_keymap_section() -> [SettingsPageItem; 2] {
1274        [
1275            SettingsPageItem::SectionHeader("Base Keymap"),
1276            SettingsPageItem::SettingItem(SettingItem {
1277                title: "Base Keymap",
1278                description: "The name of a base set of key bindings to use.",
1279                field: Box::new(SettingField {
1280                    json_path: Some("base_keymap"),
1281                    pick: |settings_content| settings_content.base_keymap.as_ref(),
1282                    write: |settings_content, value| {
1283                        settings_content.base_keymap = value;
1284                    },
1285                }),
1286                metadata: Some(Box::new(SettingsFieldMetadata {
1287                    should_do_titlecase: Some(false),
1288                    ..Default::default()
1289                })),
1290                files: USER,
1291            }),
1292        ]
1293    }
1294
1295    fn modal_editing_section() -> [SettingsPageItem; 3] {
1296        [
1297            SettingsPageItem::SectionHeader("Modal Editing"),
1298            SettingsPageItem::SettingItem(SettingItem {
1299                title: "Vim Mode",
1300                description: "Enable Vim mode and key bindings.",
1301                field: Box::new(SettingField {
1302                    json_path: Some("vim_mode"),
1303                    pick: |settings_content| settings_content.vim_mode.as_ref(),
1304                    write: write_vim_mode,
1305                }),
1306                metadata: None,
1307                files: USER,
1308            }),
1309            SettingsPageItem::SettingItem(SettingItem {
1310                title: "Helix Mode",
1311                description: "Enable Helix mode and key bindings.",
1312                field: Box::new(SettingField {
1313                    json_path: Some("helix_mode"),
1314                    pick: |settings_content| settings_content.helix_mode.as_ref(),
1315                    write: write_helix_mode,
1316                }),
1317                metadata: None,
1318                files: USER,
1319            }),
1320        ]
1321    }
1322
1323    let items: Box<[SettingsPageItem]> = concat_sections!(
1324        keybindings_section(),
1325        base_keymap_section(),
1326        modal_editing_section(),
1327    );
1328
1329    SettingsPage {
1330        title: "Keymap",
1331        items,
1332    }
1333}
1334
1335fn editor_page() -> SettingsPage {
1336    fn auto_save_section() -> [SettingsPageItem; 2] {
1337        [
1338            SettingsPageItem::SectionHeader("Auto Save"),
1339            SettingsPageItem::DynamicItem(DynamicItem {
1340                discriminant: SettingItem {
1341                    files: USER,
1342                    title: "Auto Save Mode",
1343                    description: "When to auto save buffer changes.",
1344                    field: Box::new(SettingField {
1345                        json_path: Some("autosave$"),
1346                        pick: |settings_content| {
1347                            Some(
1348                                &dynamic_variants::<settings::AutosaveSetting>()[settings_content
1349                                    .workspace
1350                                    .autosave
1351                                    .as_ref()?
1352                                    .discriminant()
1353                                    as usize],
1354                            )
1355                        },
1356                        write: |settings_content, value| {
1357                            let Some(value) = value else {
1358                                settings_content.workspace.autosave = None;
1359                                return;
1360                            };
1361                            let settings_value = settings_content
1362                                .workspace
1363                                .autosave
1364                                .get_or_insert_with(|| settings::AutosaveSetting::Off);
1365                            *settings_value = match value {
1366                                settings::AutosaveSettingDiscriminants::Off => {
1367                                    settings::AutosaveSetting::Off
1368                                }
1369                                settings::AutosaveSettingDiscriminants::AfterDelay => {
1370                                    let milliseconds = match settings_value {
1371                                        settings::AutosaveSetting::AfterDelay { milliseconds } => {
1372                                            *milliseconds
1373                                        }
1374                                        _ => settings::DelayMs(1000),
1375                                    };
1376                                    settings::AutosaveSetting::AfterDelay { milliseconds }
1377                                }
1378                                settings::AutosaveSettingDiscriminants::OnFocusChange => {
1379                                    settings::AutosaveSetting::OnFocusChange
1380                                }
1381                                settings::AutosaveSettingDiscriminants::OnWindowChange => {
1382                                    settings::AutosaveSetting::OnWindowChange
1383                                }
1384                            };
1385                        },
1386                    }),
1387                    metadata: None,
1388                },
1389                pick_discriminant: |settings_content| {
1390                    Some(settings_content.workspace.autosave.as_ref()?.discriminant() as usize)
1391                },
1392                fields: dynamic_variants::<settings::AutosaveSetting>()
1393                    .into_iter()
1394                    .map(|variant| match variant {
1395                        settings::AutosaveSettingDiscriminants::Off => vec![],
1396                        settings::AutosaveSettingDiscriminants::AfterDelay => vec![SettingItem {
1397                            files: USER,
1398                            title: "Delay (milliseconds)",
1399                            description: "Save after inactivity period (in milliseconds).",
1400                            field: Box::new(SettingField {
1401                                json_path: Some("autosave.after_delay.milliseconds"),
1402                                pick: |settings_content| match settings_content
1403                                    .workspace
1404                                    .autosave
1405                                    .as_ref()
1406                                {
1407                                    Some(settings::AutosaveSetting::AfterDelay {
1408                                        milliseconds,
1409                                    }) => Some(milliseconds),
1410                                    _ => None,
1411                                },
1412                                write: |settings_content, value| {
1413                                    let Some(value) = value else {
1414                                        settings_content.workspace.autosave = None;
1415                                        return;
1416                                    };
1417                                    match settings_content.workspace.autosave.as_mut() {
1418                                        Some(settings::AutosaveSetting::AfterDelay {
1419                                            milliseconds,
1420                                        }) => *milliseconds = value,
1421                                        _ => return,
1422                                    }
1423                                },
1424                            }),
1425                            metadata: None,
1426                        }],
1427                        settings::AutosaveSettingDiscriminants::OnFocusChange => vec![],
1428                        settings::AutosaveSettingDiscriminants::OnWindowChange => vec![],
1429                    })
1430                    .collect(),
1431            }),
1432        ]
1433    }
1434
1435    fn which_key_section() -> [SettingsPageItem; 3] {
1436        [
1437            SettingsPageItem::SectionHeader("Which-key Menu"),
1438            SettingsPageItem::SettingItem(SettingItem {
1439                title: "Show Which-key Menu",
1440                description: "Display the which-key menu with matching bindings while a multi-stroke binding is pending.",
1441                field: Box::new(SettingField {
1442                    json_path: Some("which_key.enabled"),
1443                    pick: |settings_content| {
1444                        settings_content
1445                            .which_key
1446                            .as_ref()
1447                            .and_then(|settings| settings.enabled.as_ref())
1448                    },
1449                    write: |settings_content, value| {
1450                        settings_content.which_key.get_or_insert_default().enabled = value;
1451                    },
1452                }),
1453                metadata: None,
1454                files: USER,
1455            }),
1456            SettingsPageItem::SettingItem(SettingItem {
1457                title: "Menu Delay",
1458                description: "Delay in milliseconds before the which-key menu appears.",
1459                field: Box::new(SettingField {
1460                    json_path: Some("which_key.delay_ms"),
1461                    pick: |settings_content| {
1462                        settings_content
1463                            .which_key
1464                            .as_ref()
1465                            .and_then(|settings| settings.delay_ms.as_ref())
1466                    },
1467                    write: |settings_content, value| {
1468                        settings_content.which_key.get_or_insert_default().delay_ms = value;
1469                    },
1470                }),
1471                metadata: None,
1472                files: USER,
1473            }),
1474        ]
1475    }
1476
1477    fn multibuffer_section() -> [SettingsPageItem; 7] {
1478        [
1479            SettingsPageItem::SectionHeader("Multibuffer"),
1480            SettingsPageItem::SettingItem(SettingItem {
1481                title: "Double Click In Multibuffer",
1482                description: "What to do when multibuffer is double-clicked in some of its excerpts.",
1483                field: Box::new(SettingField {
1484                    json_path: Some("double_click_in_multibuffer"),
1485                    pick: |settings_content| {
1486                        settings_content.editor.double_click_in_multibuffer.as_ref()
1487                    },
1488                    write: |settings_content, value| {
1489                        settings_content.editor.double_click_in_multibuffer = value;
1490                    },
1491                }),
1492                metadata: None,
1493                files: USER,
1494            }),
1495            SettingsPageItem::SettingItem(SettingItem {
1496                title: "Expand Excerpt Lines",
1497                description: "How many lines to expand the multibuffer excerpts by default.",
1498                field: Box::new(SettingField {
1499                    json_path: Some("expand_excerpt_lines"),
1500                    pick: |settings_content| settings_content.editor.expand_excerpt_lines.as_ref(),
1501                    write: |settings_content, value| {
1502                        settings_content.editor.expand_excerpt_lines = value;
1503                    },
1504                }),
1505                metadata: None,
1506                files: USER,
1507            }),
1508            SettingsPageItem::SettingItem(SettingItem {
1509                title: "Excerpt Context Lines",
1510                description: "How many lines of context to provide in multibuffer excerpts by default.",
1511                field: Box::new(SettingField {
1512                    json_path: Some("excerpt_context_lines"),
1513                    pick: |settings_content| settings_content.editor.excerpt_context_lines.as_ref(),
1514                    write: |settings_content, value| {
1515                        settings_content.editor.excerpt_context_lines = value;
1516                    },
1517                }),
1518                metadata: None,
1519                files: USER,
1520            }),
1521            SettingsPageItem::SettingItem(SettingItem {
1522                title: "Expand Outlines With Depth",
1523                description: "Default depth to expand outline items in the current file.",
1524                field: Box::new(SettingField {
1525                    json_path: Some("outline_panel.expand_outlines_with_depth"),
1526                    pick: |settings_content| {
1527                        settings_content
1528                            .outline_panel
1529                            .as_ref()
1530                            .and_then(|outline_panel| {
1531                                outline_panel.expand_outlines_with_depth.as_ref()
1532                            })
1533                    },
1534                    write: |settings_content, value| {
1535                        settings_content
1536                            .outline_panel
1537                            .get_or_insert_default()
1538                            .expand_outlines_with_depth = value;
1539                    },
1540                }),
1541                metadata: None,
1542                files: USER,
1543            }),
1544            SettingsPageItem::SettingItem(SettingItem {
1545                title: "Diff View Style",
1546                description: "How to display diffs in the editor.",
1547                field: Box::new(SettingField {
1548                    json_path: Some("diff_view_style"),
1549                    pick: |settings_content| settings_content.editor.diff_view_style.as_ref(),
1550                    write: |settings_content, value| {
1551                        settings_content.editor.diff_view_style = value;
1552                    },
1553                }),
1554                metadata: None,
1555                files: USER,
1556            }),
1557            SettingsPageItem::SettingItem(SettingItem {
1558                title: "Minimum Split Diff Width",
1559                description: "The minimum width (in columns) at which the split diff view is used. When the editor is narrower, the diff view automatically switches to unified mode. Set to 0 to disable.",
1560                field: Box::new(SettingField {
1561                    json_path: Some("minimum_split_diff_width"),
1562                    pick: |settings_content| {
1563                        settings_content.editor.minimum_split_diff_width.as_ref()
1564                    },
1565                    write: |settings_content, value| {
1566                        settings_content.editor.minimum_split_diff_width = value;
1567                    },
1568                }),
1569                metadata: None,
1570                files: USER,
1571            }),
1572        ]
1573    }
1574
1575    fn scrolling_section() -> [SettingsPageItem; 8] {
1576        [
1577            SettingsPageItem::SectionHeader("Scrolling"),
1578            SettingsPageItem::SettingItem(SettingItem {
1579                title: "Scroll Beyond Last Line",
1580                description: "Whether the editor will scroll beyond the last line.",
1581                field: Box::new(SettingField {
1582                    json_path: Some("scroll_beyond_last_line"),
1583                    pick: |settings_content| {
1584                        settings_content.editor.scroll_beyond_last_line.as_ref()
1585                    },
1586                    write: |settings_content, value| {
1587                        settings_content.editor.scroll_beyond_last_line = value;
1588                    },
1589                }),
1590                metadata: None,
1591                files: USER,
1592            }),
1593            SettingsPageItem::SettingItem(SettingItem {
1594                title: "Vertical Scroll Margin",
1595                description: "The number of lines to keep above/below the cursor when auto-scrolling.",
1596                field: Box::new(SettingField {
1597                    json_path: Some("vertical_scroll_margin"),
1598                    pick: |settings_content| {
1599                        settings_content.editor.vertical_scroll_margin.as_ref()
1600                    },
1601                    write: |settings_content, value| {
1602                        settings_content.editor.vertical_scroll_margin = value;
1603                    },
1604                }),
1605                metadata: None,
1606                files: USER,
1607            }),
1608            SettingsPageItem::SettingItem(SettingItem {
1609                title: "Horizontal Scroll Margin",
1610                description: "The number of characters to keep on either side when scrolling with the mouse.",
1611                field: Box::new(SettingField {
1612                    json_path: Some("horizontal_scroll_margin"),
1613                    pick: |settings_content| {
1614                        settings_content.editor.horizontal_scroll_margin.as_ref()
1615                    },
1616                    write: |settings_content, value| {
1617                        settings_content.editor.horizontal_scroll_margin = value;
1618                    },
1619                }),
1620                metadata: None,
1621                files: USER,
1622            }),
1623            SettingsPageItem::SettingItem(SettingItem {
1624                title: "Scroll Sensitivity",
1625                description: "Scroll sensitivity multiplier for both horizontal and vertical scrolling.",
1626                field: Box::new(SettingField {
1627                    json_path: Some("scroll_sensitivity"),
1628                    pick: |settings_content| settings_content.editor.scroll_sensitivity.as_ref(),
1629                    write: |settings_content, value| {
1630                        settings_content.editor.scroll_sensitivity = value;
1631                    },
1632                }),
1633                metadata: None,
1634                files: USER,
1635            }),
1636            SettingsPageItem::SettingItem(SettingItem {
1637                title: "Fast Scroll Sensitivity",
1638                description: "Fast scroll sensitivity multiplier for both horizontal and vertical scrolling.",
1639                field: Box::new(SettingField {
1640                    json_path: Some("fast_scroll_sensitivity"),
1641                    pick: |settings_content| {
1642                        settings_content.editor.fast_scroll_sensitivity.as_ref()
1643                    },
1644                    write: |settings_content, value| {
1645                        settings_content.editor.fast_scroll_sensitivity = value;
1646                    },
1647                }),
1648                metadata: None,
1649                files: USER,
1650            }),
1651            SettingsPageItem::SettingItem(SettingItem {
1652                title: "Autoscroll On Clicks",
1653                description: "Whether to scroll when clicking near the edge of the visible text area.",
1654                field: Box::new(SettingField {
1655                    json_path: Some("autoscroll_on_clicks"),
1656                    pick: |settings_content| settings_content.editor.autoscroll_on_clicks.as_ref(),
1657                    write: |settings_content, value| {
1658                        settings_content.editor.autoscroll_on_clicks = value;
1659                    },
1660                }),
1661                metadata: None,
1662                files: USER,
1663            }),
1664            SettingsPageItem::SettingItem(SettingItem {
1665                title: "Sticky Scroll",
1666                description: "Whether to stick scopes to the top of the editor",
1667                field: Box::new(SettingField {
1668                    json_path: Some("sticky_scroll.enabled"),
1669                    pick: |settings_content| {
1670                        settings_content
1671                            .editor
1672                            .sticky_scroll
1673                            .as_ref()
1674                            .and_then(|sticky_scroll| sticky_scroll.enabled.as_ref())
1675                    },
1676                    write: |settings_content, value| {
1677                        settings_content
1678                            .editor
1679                            .sticky_scroll
1680                            .get_or_insert_default()
1681                            .enabled = value;
1682                    },
1683                }),
1684                metadata: None,
1685                files: USER,
1686            }),
1687        ]
1688    }
1689
1690    fn signature_help_section() -> [SettingsPageItem; 4] {
1691        [
1692            SettingsPageItem::SectionHeader("Signature Help"),
1693            SettingsPageItem::SettingItem(SettingItem {
1694                title: "Auto Signature Help",
1695                description: "Automatically show a signature help pop-up.",
1696                field: Box::new(SettingField {
1697                    json_path: Some("auto_signature_help"),
1698                    pick: |settings_content| settings_content.editor.auto_signature_help.as_ref(),
1699                    write: |settings_content, value| {
1700                        settings_content.editor.auto_signature_help = value;
1701                    },
1702                }),
1703                metadata: None,
1704                files: USER,
1705            }),
1706            SettingsPageItem::SettingItem(SettingItem {
1707                title: "Show Signature Help After Edits",
1708                description: "Show the signature help pop-up after completions or bracket pairs are inserted.",
1709                field: Box::new(SettingField {
1710                    json_path: Some("show_signature_help_after_edits"),
1711                    pick: |settings_content| {
1712                        settings_content
1713                            .editor
1714                            .show_signature_help_after_edits
1715                            .as_ref()
1716                    },
1717                    write: |settings_content, value| {
1718                        settings_content.editor.show_signature_help_after_edits = value;
1719                    },
1720                }),
1721                metadata: None,
1722                files: USER,
1723            }),
1724            SettingsPageItem::SettingItem(SettingItem {
1725                title: "Snippet Sort Order",
1726                description: "Determines how snippets are sorted relative to other completion items.",
1727                field: Box::new(SettingField {
1728                    json_path: Some("snippet_sort_order"),
1729                    pick: |settings_content| settings_content.editor.snippet_sort_order.as_ref(),
1730                    write: |settings_content, value| {
1731                        settings_content.editor.snippet_sort_order = value;
1732                    },
1733                }),
1734                metadata: None,
1735                files: USER,
1736            }),
1737        ]
1738    }
1739
1740    fn hover_popover_section() -> [SettingsPageItem; 3] {
1741        [
1742            SettingsPageItem::SectionHeader("Hover Popover"),
1743            SettingsPageItem::SettingItem(SettingItem {
1744                title: "Enabled",
1745                description: "Show the informational hover box when moving the mouse over symbols in the editor.",
1746                field: Box::new(SettingField {
1747                    json_path: Some("hover_popover_enabled"),
1748                    pick: |settings_content| settings_content.editor.hover_popover_enabled.as_ref(),
1749                    write: |settings_content, value| {
1750                        settings_content.editor.hover_popover_enabled = value;
1751                    },
1752                }),
1753                metadata: None,
1754                files: USER,
1755            }),
1756            // todo(settings ui): add units to this number input
1757            SettingsPageItem::SettingItem(SettingItem {
1758                title: "Delay",
1759                description: "Time to wait in milliseconds before showing the informational hover box.",
1760                field: Box::new(SettingField {
1761                    json_path: Some("hover_popover_enabled"),
1762                    pick: |settings_content| settings_content.editor.hover_popover_delay.as_ref(),
1763                    write: |settings_content, value| {
1764                        settings_content.editor.hover_popover_delay = value;
1765                    },
1766                }),
1767                metadata: None,
1768                files: USER,
1769            }),
1770        ]
1771    }
1772
1773    fn drag_and_drop_selection_section() -> [SettingsPageItem; 3] {
1774        [
1775            SettingsPageItem::SectionHeader("Drag And Drop Selection"),
1776            SettingsPageItem::SettingItem(SettingItem {
1777                title: "Enabled",
1778                description: "Enable drag and drop selection.",
1779                field: Box::new(SettingField {
1780                    json_path: Some("drag_and_drop_selection.enabled"),
1781                    pick: |settings_content| {
1782                        settings_content
1783                            .editor
1784                            .drag_and_drop_selection
1785                            .as_ref()
1786                            .and_then(|drag_and_drop| drag_and_drop.enabled.as_ref())
1787                    },
1788                    write: |settings_content, value| {
1789                        settings_content
1790                            .editor
1791                            .drag_and_drop_selection
1792                            .get_or_insert_default()
1793                            .enabled = value;
1794                    },
1795                }),
1796                metadata: None,
1797                files: USER,
1798            }),
1799            SettingsPageItem::SettingItem(SettingItem {
1800                title: "Delay",
1801                description: "Delay in milliseconds before drag and drop selection starts.",
1802                field: Box::new(SettingField {
1803                    json_path: Some("drag_and_drop_selection.delay"),
1804                    pick: |settings_content| {
1805                        settings_content
1806                            .editor
1807                            .drag_and_drop_selection
1808                            .as_ref()
1809                            .and_then(|drag_and_drop| drag_and_drop.delay.as_ref())
1810                    },
1811                    write: |settings_content, value| {
1812                        settings_content
1813                            .editor
1814                            .drag_and_drop_selection
1815                            .get_or_insert_default()
1816                            .delay = value;
1817                    },
1818                }),
1819                metadata: None,
1820                files: USER,
1821            }),
1822        ]
1823    }
1824
1825    fn gutter_section() -> [SettingsPageItem; 8] {
1826        [
1827            SettingsPageItem::SectionHeader("Gutter"),
1828            SettingsPageItem::SettingItem(SettingItem {
1829                title: "Show Line Numbers",
1830                description: "Show line numbers in the gutter.",
1831                field: Box::new(SettingField {
1832                    json_path: Some("gutter.line_numbers"),
1833                    pick: |settings_content| {
1834                        settings_content
1835                            .editor
1836                            .gutter
1837                            .as_ref()
1838                            .and_then(|gutter| gutter.line_numbers.as_ref())
1839                    },
1840                    write: |settings_content, value| {
1841                        settings_content
1842                            .editor
1843                            .gutter
1844                            .get_or_insert_default()
1845                            .line_numbers = value;
1846                    },
1847                }),
1848                metadata: None,
1849                files: USER,
1850            }),
1851            SettingsPageItem::SettingItem(SettingItem {
1852                title: "Relative Line Numbers",
1853                description: "Controls line number display in the editor's gutter. \"disabled\" shows absolute line numbers, \"enabled\" shows relative line numbers for each absolute line, and \"wrapped\" shows relative line numbers for every line, absolute or wrapped.",
1854                field: Box::new(SettingField {
1855                    json_path: Some("relative_line_numbers"),
1856                    pick: |settings_content| settings_content.editor.relative_line_numbers.as_ref(),
1857                    write: |settings_content, value| {
1858                        settings_content.editor.relative_line_numbers = value;
1859                    },
1860                }),
1861                metadata: None,
1862                files: USER,
1863            }),
1864            SettingsPageItem::SettingItem(SettingItem {
1865                title: "Show Runnables",
1866                description: "Show runnable buttons in the gutter.",
1867                field: Box::new(SettingField {
1868                    json_path: Some("gutter.runnables"),
1869                    pick: |settings_content| {
1870                        settings_content
1871                            .editor
1872                            .gutter
1873                            .as_ref()
1874                            .and_then(|gutter| gutter.runnables.as_ref())
1875                    },
1876                    write: |settings_content, value| {
1877                        settings_content
1878                            .editor
1879                            .gutter
1880                            .get_or_insert_default()
1881                            .runnables = value;
1882                    },
1883                }),
1884                metadata: None,
1885                files: USER,
1886            }),
1887            SettingsPageItem::SettingItem(SettingItem {
1888                title: "Show Breakpoints",
1889                description: "Show breakpoints in the gutter.",
1890                field: Box::new(SettingField {
1891                    json_path: Some("gutter.breakpoints"),
1892                    pick: |settings_content| {
1893                        settings_content
1894                            .editor
1895                            .gutter
1896                            .as_ref()
1897                            .and_then(|gutter| gutter.breakpoints.as_ref())
1898                    },
1899                    write: |settings_content, value| {
1900                        settings_content
1901                            .editor
1902                            .gutter
1903                            .get_or_insert_default()
1904                            .breakpoints = value;
1905                    },
1906                }),
1907                metadata: None,
1908                files: USER,
1909            }),
1910            SettingsPageItem::SettingItem(SettingItem {
1911                title: "Show Folds",
1912                description: "Show code folding controls in the gutter.",
1913                field: Box::new(SettingField {
1914                    json_path: Some("gutter.folds"),
1915                    pick: |settings_content| {
1916                        settings_content
1917                            .editor
1918                            .gutter
1919                            .as_ref()
1920                            .and_then(|gutter| gutter.folds.as_ref())
1921                    },
1922                    write: |settings_content, value| {
1923                        settings_content.editor.gutter.get_or_insert_default().folds = value;
1924                    },
1925                }),
1926                metadata: None,
1927                files: USER,
1928            }),
1929            SettingsPageItem::SettingItem(SettingItem {
1930                title: "Min Line Number Digits",
1931                description: "Minimum number of characters to reserve space for in the gutter.",
1932                field: Box::new(SettingField {
1933                    json_path: Some("gutter.min_line_number_digits"),
1934                    pick: |settings_content| {
1935                        settings_content
1936                            .editor
1937                            .gutter
1938                            .as_ref()
1939                            .and_then(|gutter| gutter.min_line_number_digits.as_ref())
1940                    },
1941                    write: |settings_content, value| {
1942                        settings_content
1943                            .editor
1944                            .gutter
1945                            .get_or_insert_default()
1946                            .min_line_number_digits = value;
1947                    },
1948                }),
1949                metadata: None,
1950                files: USER,
1951            }),
1952            SettingsPageItem::SettingItem(SettingItem {
1953                title: "Inline Code Actions",
1954                description: "Show code action button at start of buffer line.",
1955                field: Box::new(SettingField {
1956                    json_path: Some("inline_code_actions"),
1957                    pick: |settings_content| settings_content.editor.inline_code_actions.as_ref(),
1958                    write: |settings_content, value| {
1959                        settings_content.editor.inline_code_actions = value;
1960                    },
1961                }),
1962                metadata: None,
1963                files: USER,
1964            }),
1965        ]
1966    }
1967
1968    fn scrollbar_section() -> [SettingsPageItem; 10] {
1969        [
1970            SettingsPageItem::SectionHeader("Scrollbar"),
1971            SettingsPageItem::SettingItem(SettingItem {
1972                title: "Show",
1973                description: "When to show the scrollbar in the editor.",
1974                field: Box::new(SettingField {
1975                    json_path: Some("scrollbar"),
1976                    pick: |settings_content| {
1977                        settings_content.editor.scrollbar.as_ref()?.show.as_ref()
1978                    },
1979                    write: |settings_content, value| {
1980                        settings_content
1981                            .editor
1982                            .scrollbar
1983                            .get_or_insert_default()
1984                            .show = value;
1985                    },
1986                }),
1987                metadata: None,
1988                files: USER,
1989            }),
1990            SettingsPageItem::SettingItem(SettingItem {
1991                title: "Cursors",
1992                description: "Show cursor positions in the scrollbar.",
1993                field: Box::new(SettingField {
1994                    json_path: Some("scrollbar.cursors"),
1995                    pick: |settings_content| {
1996                        settings_content.editor.scrollbar.as_ref()?.cursors.as_ref()
1997                    },
1998                    write: |settings_content, value| {
1999                        settings_content
2000                            .editor
2001                            .scrollbar
2002                            .get_or_insert_default()
2003                            .cursors = value;
2004                    },
2005                }),
2006                metadata: None,
2007                files: USER,
2008            }),
2009            SettingsPageItem::SettingItem(SettingItem {
2010                title: "Git Diff",
2011                description: "Show Git diff indicators in the scrollbar.",
2012                field: Box::new(SettingField {
2013                    json_path: Some("scrollbar.git_diff"),
2014                    pick: |settings_content| {
2015                        settings_content
2016                            .editor
2017                            .scrollbar
2018                            .as_ref()?
2019                            .git_diff
2020                            .as_ref()
2021                    },
2022                    write: |settings_content, value| {
2023                        settings_content
2024                            .editor
2025                            .scrollbar
2026                            .get_or_insert_default()
2027                            .git_diff = value;
2028                    },
2029                }),
2030                metadata: None,
2031                files: USER,
2032            }),
2033            SettingsPageItem::SettingItem(SettingItem {
2034                title: "Search Results",
2035                description: "Show buffer search result indicators in the scrollbar.",
2036                field: Box::new(SettingField {
2037                    json_path: Some("scrollbar.search_results"),
2038                    pick: |settings_content| {
2039                        settings_content
2040                            .editor
2041                            .scrollbar
2042                            .as_ref()?
2043                            .search_results
2044                            .as_ref()
2045                    },
2046                    write: |settings_content, value| {
2047                        settings_content
2048                            .editor
2049                            .scrollbar
2050                            .get_or_insert_default()
2051                            .search_results = value;
2052                    },
2053                }),
2054                metadata: None,
2055                files: USER,
2056            }),
2057            SettingsPageItem::SettingItem(SettingItem {
2058                title: "Selected Text",
2059                description: "Show selected text occurrences in the scrollbar.",
2060                field: Box::new(SettingField {
2061                    json_path: Some("scrollbar.selected_text"),
2062                    pick: |settings_content| {
2063                        settings_content
2064                            .editor
2065                            .scrollbar
2066                            .as_ref()?
2067                            .selected_text
2068                            .as_ref()
2069                    },
2070                    write: |settings_content, value| {
2071                        settings_content
2072                            .editor
2073                            .scrollbar
2074                            .get_or_insert_default()
2075                            .selected_text = value;
2076                    },
2077                }),
2078                metadata: None,
2079                files: USER,
2080            }),
2081            SettingsPageItem::SettingItem(SettingItem {
2082                title: "Selected Symbol",
2083                description: "Show selected symbol occurrences in the scrollbar.",
2084                field: Box::new(SettingField {
2085                    json_path: Some("scrollbar.selected_symbol"),
2086                    pick: |settings_content| {
2087                        settings_content
2088                            .editor
2089                            .scrollbar
2090                            .as_ref()?
2091                            .selected_symbol
2092                            .as_ref()
2093                    },
2094                    write: |settings_content, value| {
2095                        settings_content
2096                            .editor
2097                            .scrollbar
2098                            .get_or_insert_default()
2099                            .selected_symbol = value;
2100                    },
2101                }),
2102                metadata: None,
2103                files: USER,
2104            }),
2105            SettingsPageItem::SettingItem(SettingItem {
2106                title: "Diagnostics",
2107                description: "Which diagnostic indicators to show in the scrollbar.",
2108                field: Box::new(SettingField {
2109                    json_path: Some("scrollbar.diagnostics"),
2110                    pick: |settings_content| {
2111                        settings_content
2112                            .editor
2113                            .scrollbar
2114                            .as_ref()?
2115                            .diagnostics
2116                            .as_ref()
2117                    },
2118                    write: |settings_content, value| {
2119                        settings_content
2120                            .editor
2121                            .scrollbar
2122                            .get_or_insert_default()
2123                            .diagnostics = value;
2124                    },
2125                }),
2126                metadata: None,
2127                files: USER,
2128            }),
2129            SettingsPageItem::SettingItem(SettingItem {
2130                title: "Horizontal Scrollbar",
2131                description: "When false, forcefully disables the horizontal scrollbar.",
2132                field: Box::new(SettingField {
2133                    json_path: Some("scrollbar.axes.horizontal"),
2134                    pick: |settings_content| {
2135                        settings_content
2136                            .editor
2137                            .scrollbar
2138                            .as_ref()?
2139                            .axes
2140                            .as_ref()?
2141                            .horizontal
2142                            .as_ref()
2143                    },
2144                    write: |settings_content, value| {
2145                        settings_content
2146                            .editor
2147                            .scrollbar
2148                            .get_or_insert_default()
2149                            .axes
2150                            .get_or_insert_default()
2151                            .horizontal = value;
2152                    },
2153                }),
2154                metadata: None,
2155                files: USER,
2156            }),
2157            SettingsPageItem::SettingItem(SettingItem {
2158                title: "Vertical Scrollbar",
2159                description: "When false, forcefully disables the vertical scrollbar.",
2160                field: Box::new(SettingField {
2161                    json_path: Some("scrollbar.axes.vertical"),
2162                    pick: |settings_content| {
2163                        settings_content
2164                            .editor
2165                            .scrollbar
2166                            .as_ref()?
2167                            .axes
2168                            .as_ref()?
2169                            .vertical
2170                            .as_ref()
2171                    },
2172                    write: |settings_content, value| {
2173                        settings_content
2174                            .editor
2175                            .scrollbar
2176                            .get_or_insert_default()
2177                            .axes
2178                            .get_or_insert_default()
2179                            .vertical = value;
2180                    },
2181                }),
2182                metadata: None,
2183                files: USER,
2184            }),
2185        ]
2186    }
2187
2188    fn minimap_section() -> [SettingsPageItem; 7] {
2189        [
2190            SettingsPageItem::SectionHeader("Minimap"),
2191            SettingsPageItem::SettingItem(SettingItem {
2192                title: "Show",
2193                description: "When to show the minimap in the editor.",
2194                field: Box::new(SettingField {
2195                    json_path: Some("minimap.show"),
2196                    pick: |settings_content| {
2197                        settings_content.editor.minimap.as_ref()?.show.as_ref()
2198                    },
2199                    write: |settings_content, value| {
2200                        settings_content.editor.minimap.get_or_insert_default().show = value;
2201                    },
2202                }),
2203                metadata: None,
2204                files: USER,
2205            }),
2206            SettingsPageItem::SettingItem(SettingItem {
2207                title: "Display In",
2208                description: "Where to show the minimap in the editor.",
2209                field: Box::new(SettingField {
2210                    json_path: Some("minimap.display_in"),
2211                    pick: |settings_content| {
2212                        settings_content
2213                            .editor
2214                            .minimap
2215                            .as_ref()?
2216                            .display_in
2217                            .as_ref()
2218                    },
2219                    write: |settings_content, value| {
2220                        settings_content
2221                            .editor
2222                            .minimap
2223                            .get_or_insert_default()
2224                            .display_in = value;
2225                    },
2226                }),
2227                metadata: None,
2228                files: USER,
2229            }),
2230            SettingsPageItem::SettingItem(SettingItem {
2231                title: "Thumb",
2232                description: "When to show the minimap thumb.",
2233                field: Box::new(SettingField {
2234                    json_path: Some("minimap.thumb"),
2235                    pick: |settings_content| {
2236                        settings_content.editor.minimap.as_ref()?.thumb.as_ref()
2237                    },
2238                    write: |settings_content, value| {
2239                        settings_content
2240                            .editor
2241                            .minimap
2242                            .get_or_insert_default()
2243                            .thumb = value;
2244                    },
2245                }),
2246                metadata: None,
2247                files: USER,
2248            }),
2249            SettingsPageItem::SettingItem(SettingItem {
2250                title: "Thumb Border",
2251                description: "Border style for the minimap's scrollbar thumb.",
2252                field: Box::new(SettingField {
2253                    json_path: Some("minimap.thumb_border"),
2254                    pick: |settings_content| {
2255                        settings_content
2256                            .editor
2257                            .minimap
2258                            .as_ref()?
2259                            .thumb_border
2260                            .as_ref()
2261                    },
2262                    write: |settings_content, value| {
2263                        settings_content
2264                            .editor
2265                            .minimap
2266                            .get_or_insert_default()
2267                            .thumb_border = value;
2268                    },
2269                }),
2270                metadata: None,
2271                files: USER,
2272            }),
2273            SettingsPageItem::SettingItem(SettingItem {
2274                title: "Current Line Highlight",
2275                description: "How to highlight the current line in the minimap.",
2276                field: Box::new(SettingField {
2277                    json_path: Some("minimap.current_line_highlight"),
2278                    pick: |settings_content| {
2279                        settings_content
2280                            .editor
2281                            .minimap
2282                            .as_ref()
2283                            .and_then(|minimap| minimap.current_line_highlight.as_ref())
2284                            .or(settings_content.editor.current_line_highlight.as_ref())
2285                    },
2286                    write: |settings_content, value| {
2287                        settings_content
2288                            .editor
2289                            .minimap
2290                            .get_or_insert_default()
2291                            .current_line_highlight = value;
2292                    },
2293                }),
2294                metadata: None,
2295                files: USER,
2296            }),
2297            SettingsPageItem::SettingItem(SettingItem {
2298                title: "Max Width Columns",
2299                description: "Maximum number of columns to display in the minimap.",
2300                field: Box::new(SettingField {
2301                    json_path: Some("minimap.max_width_columns"),
2302                    pick: |settings_content| {
2303                        settings_content
2304                            .editor
2305                            .minimap
2306                            .as_ref()?
2307                            .max_width_columns
2308                            .as_ref()
2309                    },
2310                    write: |settings_content, value| {
2311                        settings_content
2312                            .editor
2313                            .minimap
2314                            .get_or_insert_default()
2315                            .max_width_columns = value;
2316                    },
2317                }),
2318                metadata: None,
2319                files: USER,
2320            }),
2321        ]
2322    }
2323
2324    fn toolbar_section() -> [SettingsPageItem; 6] {
2325        [
2326            SettingsPageItem::SectionHeader("Toolbar"),
2327            SettingsPageItem::SettingItem(SettingItem {
2328                title: "Breadcrumbs",
2329                description: "Show breadcrumbs.",
2330                field: Box::new(SettingField {
2331                    json_path: Some("toolbar.breadcrumbs"),
2332                    pick: |settings_content| {
2333                        settings_content
2334                            .editor
2335                            .toolbar
2336                            .as_ref()?
2337                            .breadcrumbs
2338                            .as_ref()
2339                    },
2340                    write: |settings_content, value| {
2341                        settings_content
2342                            .editor
2343                            .toolbar
2344                            .get_or_insert_default()
2345                            .breadcrumbs = value;
2346                    },
2347                }),
2348                metadata: None,
2349                files: USER,
2350            }),
2351            SettingsPageItem::SettingItem(SettingItem {
2352                title: "Quick Actions",
2353                description: "Show quick action buttons (e.g., search, selection, editor controls, etc.).",
2354                field: Box::new(SettingField {
2355                    json_path: Some("toolbar.quick_actions"),
2356                    pick: |settings_content| {
2357                        settings_content
2358                            .editor
2359                            .toolbar
2360                            .as_ref()?
2361                            .quick_actions
2362                            .as_ref()
2363                    },
2364                    write: |settings_content, value| {
2365                        settings_content
2366                            .editor
2367                            .toolbar
2368                            .get_or_insert_default()
2369                            .quick_actions = value;
2370                    },
2371                }),
2372                metadata: None,
2373                files: USER,
2374            }),
2375            SettingsPageItem::SettingItem(SettingItem {
2376                title: "Selections Menu",
2377                description: "Show the selections menu in the editor toolbar.",
2378                field: Box::new(SettingField {
2379                    json_path: Some("toolbar.selections_menu"),
2380                    pick: |settings_content| {
2381                        settings_content
2382                            .editor
2383                            .toolbar
2384                            .as_ref()?
2385                            .selections_menu
2386                            .as_ref()
2387                    },
2388                    write: |settings_content, value| {
2389                        settings_content
2390                            .editor
2391                            .toolbar
2392                            .get_or_insert_default()
2393                            .selections_menu = value;
2394                    },
2395                }),
2396                metadata: None,
2397                files: USER,
2398            }),
2399            SettingsPageItem::SettingItem(SettingItem {
2400                title: "Agent Review",
2401                description: "Show agent review buttons in the editor toolbar.",
2402                field: Box::new(SettingField {
2403                    json_path: Some("toolbar.agent_review"),
2404                    pick: |settings_content| {
2405                        settings_content
2406                            .editor
2407                            .toolbar
2408                            .as_ref()?
2409                            .agent_review
2410                            .as_ref()
2411                    },
2412                    write: |settings_content, value| {
2413                        settings_content
2414                            .editor
2415                            .toolbar
2416                            .get_or_insert_default()
2417                            .agent_review = value;
2418                    },
2419                }),
2420                metadata: None,
2421                files: USER,
2422            }),
2423            SettingsPageItem::SettingItem(SettingItem {
2424                title: "Code Actions",
2425                description: "Show code action buttons in the editor toolbar.",
2426                field: Box::new(SettingField {
2427                    json_path: Some("toolbar.code_actions"),
2428                    pick: |settings_content| {
2429                        settings_content
2430                            .editor
2431                            .toolbar
2432                            .as_ref()?
2433                            .code_actions
2434                            .as_ref()
2435                    },
2436                    write: |settings_content, value| {
2437                        settings_content
2438                            .editor
2439                            .toolbar
2440                            .get_or_insert_default()
2441                            .code_actions = value;
2442                    },
2443                }),
2444                metadata: None,
2445                files: USER,
2446            }),
2447        ]
2448    }
2449
2450    fn vim_settings_section() -> [SettingsPageItem; 12] {
2451        [
2452            SettingsPageItem::SectionHeader("Vim"),
2453            SettingsPageItem::SettingItem(SettingItem {
2454                title: "Default Mode",
2455                description: "The default mode when Vim starts.",
2456                field: Box::new(SettingField {
2457                    json_path: Some("vim.default_mode"),
2458                    pick: |settings_content| settings_content.vim.as_ref()?.default_mode.as_ref(),
2459                    write: |settings_content, value| {
2460                        settings_content.vim.get_or_insert_default().default_mode = value;
2461                    },
2462                }),
2463                metadata: None,
2464                files: USER,
2465            }),
2466            SettingsPageItem::SettingItem(SettingItem {
2467                title: "Toggle Relative Line Numbers",
2468                description: "Toggle relative line numbers in Vim mode.",
2469                field: Box::new(SettingField {
2470                    json_path: Some("vim.toggle_relative_line_numbers"),
2471                    pick: |settings_content| {
2472                        settings_content
2473                            .vim
2474                            .as_ref()?
2475                            .toggle_relative_line_numbers
2476                            .as_ref()
2477                    },
2478                    write: |settings_content, value| {
2479                        settings_content
2480                            .vim
2481                            .get_or_insert_default()
2482                            .toggle_relative_line_numbers = value;
2483                    },
2484                }),
2485                metadata: None,
2486                files: USER,
2487            }),
2488            SettingsPageItem::SettingItem(SettingItem {
2489                title: "Use System Clipboard",
2490                description: "Controls when to use system clipboard in Vim mode.",
2491                field: Box::new(SettingField {
2492                    json_path: Some("vim.use_system_clipboard"),
2493                    pick: |settings_content| {
2494                        settings_content.vim.as_ref()?.use_system_clipboard.as_ref()
2495                    },
2496                    write: |settings_content, value| {
2497                        settings_content
2498                            .vim
2499                            .get_or_insert_default()
2500                            .use_system_clipboard = value;
2501                    },
2502                }),
2503                metadata: None,
2504                files: USER,
2505            }),
2506            SettingsPageItem::SettingItem(SettingItem {
2507                title: "Use Smartcase Find",
2508                description: "Enable smartcase searching in Vim mode.",
2509                field: Box::new(SettingField {
2510                    json_path: Some("vim.use_smartcase_find"),
2511                    pick: |settings_content| {
2512                        settings_content.vim.as_ref()?.use_smartcase_find.as_ref()
2513                    },
2514                    write: |settings_content, value| {
2515                        settings_content
2516                            .vim
2517                            .get_or_insert_default()
2518                            .use_smartcase_find = value;
2519                    },
2520                }),
2521                metadata: None,
2522                files: USER,
2523            }),
2524            SettingsPageItem::SettingItem(SettingItem {
2525                title: "Global Substitution Default",
2526                description: "When enabled, the :substitute command replaces all matches in a line by default. The 'g' flag then toggles this behavior.",
2527                field: Box::new(SettingField {
2528                    json_path: Some("vim.gdefault"),
2529                    pick: |settings_content| settings_content.vim.as_ref()?.gdefault.as_ref(),
2530                    write: |settings_content, value| {
2531                        settings_content.vim.get_or_insert_default().gdefault = value;
2532                    },
2533                }),
2534                metadata: None,
2535                files: USER,
2536            }),
2537            SettingsPageItem::SettingItem(SettingItem {
2538                title: "Highlight on Yank Duration",
2539                description: "Duration in milliseconds to highlight yanked text in Vim mode.",
2540                field: Box::new(SettingField {
2541                    json_path: Some("vim.highlight_on_yank_duration"),
2542                    pick: |settings_content| {
2543                        settings_content
2544                            .vim
2545                            .as_ref()?
2546                            .highlight_on_yank_duration
2547                            .as_ref()
2548                    },
2549                    write: |settings_content, value| {
2550                        settings_content
2551                            .vim
2552                            .get_or_insert_default()
2553                            .highlight_on_yank_duration = value;
2554                    },
2555                }),
2556                metadata: None,
2557                files: USER,
2558            }),
2559            SettingsPageItem::SettingItem(SettingItem {
2560                title: "Cursor Shape - Normal Mode",
2561                description: "Cursor shape for normal mode.",
2562                field: Box::new(SettingField {
2563                    json_path: Some("vim.cursor_shape.normal"),
2564                    pick: |settings_content| {
2565                        settings_content
2566                            .vim
2567                            .as_ref()?
2568                            .cursor_shape
2569                            .as_ref()?
2570                            .normal
2571                            .as_ref()
2572                    },
2573                    write: |settings_content, value| {
2574                        settings_content
2575                            .vim
2576                            .get_or_insert_default()
2577                            .cursor_shape
2578                            .get_or_insert_default()
2579                            .normal = value;
2580                    },
2581                }),
2582                metadata: None,
2583                files: USER,
2584            }),
2585            SettingsPageItem::SettingItem(SettingItem {
2586                title: "Cursor Shape - Insert Mode",
2587                description: "Cursor shape for insert mode. Inherit uses the editor's cursor shape.",
2588                field: Box::new(SettingField {
2589                    json_path: Some("vim.cursor_shape.insert"),
2590                    pick: |settings_content| {
2591                        settings_content
2592                            .vim
2593                            .as_ref()?
2594                            .cursor_shape
2595                            .as_ref()?
2596                            .insert
2597                            .as_ref()
2598                    },
2599                    write: |settings_content, value| {
2600                        settings_content
2601                            .vim
2602                            .get_or_insert_default()
2603                            .cursor_shape
2604                            .get_or_insert_default()
2605                            .insert = value;
2606                    },
2607                }),
2608                metadata: None,
2609                files: USER,
2610            }),
2611            SettingsPageItem::SettingItem(SettingItem {
2612                title: "Cursor Shape - Replace Mode",
2613                description: "Cursor shape for replace mode.",
2614                field: Box::new(SettingField {
2615                    json_path: Some("vim.cursor_shape.replace"),
2616                    pick: |settings_content| {
2617                        settings_content
2618                            .vim
2619                            .as_ref()?
2620                            .cursor_shape
2621                            .as_ref()?
2622                            .replace
2623                            .as_ref()
2624                    },
2625                    write: |settings_content, value| {
2626                        settings_content
2627                            .vim
2628                            .get_or_insert_default()
2629                            .cursor_shape
2630                            .get_or_insert_default()
2631                            .replace = value;
2632                    },
2633                }),
2634                metadata: None,
2635                files: USER,
2636            }),
2637            SettingsPageItem::SettingItem(SettingItem {
2638                title: "Cursor Shape - Visual Mode",
2639                description: "Cursor shape for visual mode.",
2640                field: Box::new(SettingField {
2641                    json_path: Some("vim.cursor_shape.visual"),
2642                    pick: |settings_content| {
2643                        settings_content
2644                            .vim
2645                            .as_ref()?
2646                            .cursor_shape
2647                            .as_ref()?
2648                            .visual
2649                            .as_ref()
2650                    },
2651                    write: |settings_content, value| {
2652                        settings_content
2653                            .vim
2654                            .get_or_insert_default()
2655                            .cursor_shape
2656                            .get_or_insert_default()
2657                            .visual = value;
2658                    },
2659                }),
2660                metadata: None,
2661                files: USER,
2662            }),
2663            SettingsPageItem::SettingItem(SettingItem {
2664                title: "Custom Digraphs",
2665                description: "Custom digraph mappings for Vim mode.",
2666                field: Box::new(
2667                    SettingField {
2668                        json_path: Some("vim.custom_digraphs"),
2669                        pick: |settings_content| {
2670                            settings_content.vim.as_ref()?.custom_digraphs.as_ref()
2671                        },
2672                        write: |settings_content, value| {
2673                            settings_content.vim.get_or_insert_default().custom_digraphs = value;
2674                        },
2675                    }
2676                    .unimplemented(),
2677                ),
2678                metadata: None,
2679                files: USER,
2680            }),
2681        ]
2682    }
2683
2684    let items = concat_sections!(
2685        auto_save_section(),
2686        which_key_section(),
2687        multibuffer_section(),
2688        scrolling_section(),
2689        signature_help_section(),
2690        hover_popover_section(),
2691        drag_and_drop_selection_section(),
2692        gutter_section(),
2693        scrollbar_section(),
2694        minimap_section(),
2695        toolbar_section(),
2696        vim_settings_section(),
2697        language_settings_data(),
2698    );
2699
2700    SettingsPage {
2701        title: "Editor",
2702        items: items,
2703    }
2704}
2705
2706fn languages_and_tools_page(cx: &App) -> SettingsPage {
2707    fn file_types_section() -> [SettingsPageItem; 2] {
2708        [
2709            SettingsPageItem::SectionHeader("File Types"),
2710            SettingsPageItem::SettingItem(SettingItem {
2711                title: "File Type Associations",
2712                description: "A mapping from languages to files and file extensions that should be treated as that language.",
2713                field: Box::new(
2714                    SettingField {
2715                        json_path: Some("file_type_associations"),
2716                        pick: |settings_content| {
2717                            settings_content.project.all_languages.file_types.as_ref()
2718                        },
2719                        write: |settings_content, value| {
2720                            settings_content.project.all_languages.file_types = value;
2721                        },
2722                    }
2723                    .unimplemented(),
2724                ),
2725                metadata: None,
2726                files: USER | PROJECT,
2727            }),
2728        ]
2729    }
2730
2731    fn diagnostics_section() -> [SettingsPageItem; 3] {
2732        [
2733            SettingsPageItem::SectionHeader("Diagnostics"),
2734            SettingsPageItem::SettingItem(SettingItem {
2735                title: "Max Severity",
2736                description: "Which level to use to filter out diagnostics displayed in the editor.",
2737                field: Box::new(SettingField {
2738                    json_path: Some("diagnostics_max_severity"),
2739                    pick: |settings_content| {
2740                        settings_content.editor.diagnostics_max_severity.as_ref()
2741                    },
2742                    write: |settings_content, value| {
2743                        settings_content.editor.diagnostics_max_severity = value;
2744                    },
2745                }),
2746                metadata: None,
2747                files: USER,
2748            }),
2749            SettingsPageItem::SettingItem(SettingItem {
2750                title: "Include Warnings",
2751                description: "Whether to show warnings or not by default.",
2752                field: Box::new(SettingField {
2753                    json_path: Some("diagnostics.include_warnings"),
2754                    pick: |settings_content| {
2755                        settings_content
2756                            .diagnostics
2757                            .as_ref()?
2758                            .include_warnings
2759                            .as_ref()
2760                    },
2761                    write: |settings_content, value| {
2762                        settings_content
2763                            .diagnostics
2764                            .get_or_insert_default()
2765                            .include_warnings = value;
2766                    },
2767                }),
2768                metadata: None,
2769                files: USER,
2770            }),
2771        ]
2772    }
2773
2774    fn inline_diagnostics_section() -> [SettingsPageItem; 5] {
2775        [
2776            SettingsPageItem::SectionHeader("Inline Diagnostics"),
2777            SettingsPageItem::SettingItem(SettingItem {
2778                title: "Enabled",
2779                description: "Whether to show diagnostics inline or not.",
2780                field: Box::new(SettingField {
2781                    json_path: Some("diagnostics.inline.enabled"),
2782                    pick: |settings_content| {
2783                        settings_content
2784                            .diagnostics
2785                            .as_ref()?
2786                            .inline
2787                            .as_ref()?
2788                            .enabled
2789                            .as_ref()
2790                    },
2791                    write: |settings_content, value| {
2792                        settings_content
2793                            .diagnostics
2794                            .get_or_insert_default()
2795                            .inline
2796                            .get_or_insert_default()
2797                            .enabled = value;
2798                    },
2799                }),
2800                metadata: None,
2801                files: USER,
2802            }),
2803            SettingsPageItem::SettingItem(SettingItem {
2804                title: "Update Debounce",
2805                description: "The delay in milliseconds to show inline diagnostics after the last diagnostic update.",
2806                field: Box::new(SettingField {
2807                    json_path: Some("diagnostics.inline.update_debounce_ms"),
2808                    pick: |settings_content| {
2809                        settings_content
2810                            .diagnostics
2811                            .as_ref()?
2812                            .inline
2813                            .as_ref()?
2814                            .update_debounce_ms
2815                            .as_ref()
2816                    },
2817                    write: |settings_content, value| {
2818                        settings_content
2819                            .diagnostics
2820                            .get_or_insert_default()
2821                            .inline
2822                            .get_or_insert_default()
2823                            .update_debounce_ms = value;
2824                    },
2825                }),
2826                metadata: None,
2827                files: USER,
2828            }),
2829            SettingsPageItem::SettingItem(SettingItem {
2830                title: "Padding",
2831                description: "The amount of padding between the end of the source line and the start of the inline diagnostic.",
2832                field: Box::new(SettingField {
2833                    json_path: Some("diagnostics.inline.padding"),
2834                    pick: |settings_content| {
2835                        settings_content
2836                            .diagnostics
2837                            .as_ref()?
2838                            .inline
2839                            .as_ref()?
2840                            .padding
2841                            .as_ref()
2842                    },
2843                    write: |settings_content, value| {
2844                        settings_content
2845                            .diagnostics
2846                            .get_or_insert_default()
2847                            .inline
2848                            .get_or_insert_default()
2849                            .padding = value;
2850                    },
2851                }),
2852                metadata: None,
2853                files: USER,
2854            }),
2855            SettingsPageItem::SettingItem(SettingItem {
2856                title: "Minimum Column",
2857                description: "The minimum column at which to display inline diagnostics.",
2858                field: Box::new(SettingField {
2859                    json_path: Some("diagnostics.inline.min_column"),
2860                    pick: |settings_content| {
2861                        settings_content
2862                            .diagnostics
2863                            .as_ref()?
2864                            .inline
2865                            .as_ref()?
2866                            .min_column
2867                            .as_ref()
2868                    },
2869                    write: |settings_content, value| {
2870                        settings_content
2871                            .diagnostics
2872                            .get_or_insert_default()
2873                            .inline
2874                            .get_or_insert_default()
2875                            .min_column = value;
2876                    },
2877                }),
2878                metadata: None,
2879                files: USER,
2880            }),
2881        ]
2882    }
2883
2884    fn lsp_pull_diagnostics_section() -> [SettingsPageItem; 3] {
2885        [
2886            SettingsPageItem::SectionHeader("LSP Pull Diagnostics"),
2887            SettingsPageItem::SettingItem(SettingItem {
2888                title: "Enabled",
2889                description: "Whether to pull for language server-powered diagnostics or not.",
2890                field: Box::new(SettingField {
2891                    json_path: Some("diagnostics.lsp_pull_diagnostics.enabled"),
2892                    pick: |settings_content| {
2893                        settings_content
2894                            .diagnostics
2895                            .as_ref()?
2896                            .lsp_pull_diagnostics
2897                            .as_ref()?
2898                            .enabled
2899                            .as_ref()
2900                    },
2901                    write: |settings_content, value| {
2902                        settings_content
2903                            .diagnostics
2904                            .get_or_insert_default()
2905                            .lsp_pull_diagnostics
2906                            .get_or_insert_default()
2907                            .enabled = value;
2908                    },
2909                }),
2910                metadata: None,
2911                files: USER,
2912            }),
2913            // todo(settings_ui): Needs unit
2914            SettingsPageItem::SettingItem(SettingItem {
2915                title: "Debounce",
2916                description: "Minimum time to wait before pulling diagnostics from the language server(s).",
2917                field: Box::new(SettingField {
2918                    json_path: Some("diagnostics.lsp_pull_diagnostics.debounce_ms"),
2919                    pick: |settings_content| {
2920                        settings_content
2921                            .diagnostics
2922                            .as_ref()?
2923                            .lsp_pull_diagnostics
2924                            .as_ref()?
2925                            .debounce_ms
2926                            .as_ref()
2927                    },
2928                    write: |settings_content, value| {
2929                        settings_content
2930                            .diagnostics
2931                            .get_or_insert_default()
2932                            .lsp_pull_diagnostics
2933                            .get_or_insert_default()
2934                            .debounce_ms = value;
2935                    },
2936                }),
2937                metadata: None,
2938                files: USER,
2939            }),
2940        ]
2941    }
2942
2943    fn lsp_highlights_section() -> [SettingsPageItem; 2] {
2944        [
2945            SettingsPageItem::SectionHeader("LSP Highlights"),
2946            SettingsPageItem::SettingItem(SettingItem {
2947                title: "Debounce",
2948                description: "The debounce delay before querying highlights from the language.",
2949                field: Box::new(SettingField {
2950                    json_path: Some("lsp_highlight_debounce"),
2951                    pick: |settings_content| {
2952                        settings_content.editor.lsp_highlight_debounce.as_ref()
2953                    },
2954                    write: |settings_content, value| {
2955                        settings_content.editor.lsp_highlight_debounce = value;
2956                    },
2957                }),
2958                metadata: None,
2959                files: USER,
2960            }),
2961        ]
2962    }
2963
2964    fn languages_list_section(cx: &App) -> Box<[SettingsPageItem]> {
2965        // todo(settings_ui): Refresh on extension (un)/installed
2966        // Note that `crates/json_schema_store` solves the same problem, there is probably a way to unify the two
2967        std::iter::once(SettingsPageItem::SectionHeader("Languages"))
2968            .chain(all_language_names(cx).into_iter().map(|language_name| {
2969                let link = format!("languages.{language_name}");
2970                SettingsPageItem::SubPageLink(SubPageLink {
2971                    title: language_name,
2972                    r#type: crate::SubPageType::Language,
2973                    description: None,
2974                    json_path: Some(link.leak()),
2975                    in_json: true,
2976                    files: USER | PROJECT,
2977                    render: |this, scroll_handle, window, cx| {
2978                        let items: Box<[SettingsPageItem]> = concat_sections!(
2979                            language_settings_data(),
2980                            non_editor_language_settings_data(),
2981                            edit_prediction_language_settings_section()
2982                        );
2983                        this.render_sub_page_items(
2984                            items.iter().enumerate(),
2985                            scroll_handle,
2986                            window,
2987                            cx,
2988                        )
2989                        .into_any_element()
2990                    },
2991                })
2992            }))
2993            .collect()
2994    }
2995
2996    SettingsPage {
2997        title: "Languages & Tools",
2998        items: {
2999            concat_sections!(
3000                non_editor_language_settings_data(),
3001                file_types_section(),
3002                diagnostics_section(),
3003                inline_diagnostics_section(),
3004                lsp_pull_diagnostics_section(),
3005                lsp_highlights_section(),
3006                languages_list_section(cx),
3007            )
3008        },
3009    }
3010}
3011
3012fn search_and_files_page() -> SettingsPage {
3013    fn search_section() -> [SettingsPageItem; 9] {
3014        [
3015            SettingsPageItem::SectionHeader("Search"),
3016            SettingsPageItem::SettingItem(SettingItem {
3017                title: "Whole Word",
3018                description: "Search for whole words by default.",
3019                field: Box::new(SettingField {
3020                    json_path: Some("search.whole_word"),
3021                    pick: |settings_content| {
3022                        settings_content.editor.search.as_ref()?.whole_word.as_ref()
3023                    },
3024                    write: |settings_content, value| {
3025                        settings_content
3026                            .editor
3027                            .search
3028                            .get_or_insert_default()
3029                            .whole_word = value;
3030                    },
3031                }),
3032                metadata: None,
3033                files: USER,
3034            }),
3035            SettingsPageItem::SettingItem(SettingItem {
3036                title: "Case Sensitive",
3037                description: "Search case-sensitively by default.",
3038                field: Box::new(SettingField {
3039                    json_path: Some("search.case_sensitive"),
3040                    pick: |settings_content| {
3041                        settings_content
3042                            .editor
3043                            .search
3044                            .as_ref()?
3045                            .case_sensitive
3046                            .as_ref()
3047                    },
3048                    write: |settings_content, value| {
3049                        settings_content
3050                            .editor
3051                            .search
3052                            .get_or_insert_default()
3053                            .case_sensitive = value;
3054                    },
3055                }),
3056                metadata: None,
3057                files: USER,
3058            }),
3059            SettingsPageItem::SettingItem(SettingItem {
3060                title: "Use Smartcase Search",
3061                description: "Whether to automatically enable case-sensitive search based on the search query.",
3062                field: Box::new(SettingField {
3063                    json_path: Some("use_smartcase_search"),
3064                    pick: |settings_content| settings_content.editor.use_smartcase_search.as_ref(),
3065                    write: |settings_content, value| {
3066                        settings_content.editor.use_smartcase_search = value;
3067                    },
3068                }),
3069                metadata: None,
3070                files: USER,
3071            }),
3072            SettingsPageItem::SettingItem(SettingItem {
3073                title: "Include Ignored",
3074                description: "Include ignored files in search results by default.",
3075                field: Box::new(SettingField {
3076                    json_path: Some("search.include_ignored"),
3077                    pick: |settings_content| {
3078                        settings_content
3079                            .editor
3080                            .search
3081                            .as_ref()?
3082                            .include_ignored
3083                            .as_ref()
3084                    },
3085                    write: |settings_content, value| {
3086                        settings_content
3087                            .editor
3088                            .search
3089                            .get_or_insert_default()
3090                            .include_ignored = value;
3091                    },
3092                }),
3093                metadata: None,
3094                files: USER,
3095            }),
3096            SettingsPageItem::SettingItem(SettingItem {
3097                title: "Regex",
3098                description: "Use regex search by default.",
3099                field: Box::new(SettingField {
3100                    json_path: Some("search.regex"),
3101                    pick: |settings_content| {
3102                        settings_content.editor.search.as_ref()?.regex.as_ref()
3103                    },
3104                    write: |settings_content, value| {
3105                        settings_content.editor.search.get_or_insert_default().regex = value;
3106                    },
3107                }),
3108                metadata: None,
3109                files: USER,
3110            }),
3111            SettingsPageItem::SettingItem(SettingItem {
3112                title: "Search Wrap",
3113                description: "Whether the editor search results will loop.",
3114                field: Box::new(SettingField {
3115                    json_path: Some("search_wrap"),
3116                    pick: |settings_content| settings_content.editor.search_wrap.as_ref(),
3117                    write: |settings_content, value| {
3118                        settings_content.editor.search_wrap = value;
3119                    },
3120                }),
3121                metadata: None,
3122                files: USER,
3123            }),
3124            SettingsPageItem::SettingItem(SettingItem {
3125                title: "Center on Match",
3126                description: "Whether to center the current match in the editor",
3127                field: Box::new(SettingField {
3128                    json_path: Some("editor.search.center_on_match"),
3129                    pick: |settings_content| {
3130                        settings_content
3131                            .editor
3132                            .search
3133                            .as_ref()
3134                            .and_then(|search| search.center_on_match.as_ref())
3135                    },
3136                    write: |settings_content, value| {
3137                        settings_content
3138                            .editor
3139                            .search
3140                            .get_or_insert_default()
3141                            .center_on_match = value;
3142                    },
3143                }),
3144                metadata: None,
3145                files: USER,
3146            }),
3147            SettingsPageItem::SettingItem(SettingItem {
3148                title: "Seed Search Query From Cursor",
3149                description: "When to populate a new search's query based on the text under the cursor.",
3150                field: Box::new(SettingField {
3151                    json_path: Some("seed_search_query_from_cursor"),
3152                    pick: |settings_content| {
3153                        settings_content
3154                            .editor
3155                            .seed_search_query_from_cursor
3156                            .as_ref()
3157                    },
3158                    write: |settings_content, value| {
3159                        settings_content.editor.seed_search_query_from_cursor = value;
3160                    },
3161                }),
3162                metadata: None,
3163                files: USER,
3164            }),
3165        ]
3166    }
3167
3168    fn file_finder_section() -> [SettingsPageItem; 5] {
3169        [
3170            SettingsPageItem::SectionHeader("File Finder"),
3171            // todo: null by default
3172            SettingsPageItem::SettingItem(SettingItem {
3173                title: "Include Ignored in Search",
3174                description: "Use gitignored files when searching.",
3175                field: Box::new(SettingField {
3176                    json_path: Some("file_finder.include_ignored"),
3177                    pick: |settings_content| {
3178                        settings_content
3179                            .file_finder
3180                            .as_ref()?
3181                            .include_ignored
3182                            .as_ref()
3183                    },
3184                    write: |settings_content, value| {
3185                        settings_content
3186                            .file_finder
3187                            .get_or_insert_default()
3188                            .include_ignored = value;
3189                    },
3190                }),
3191                metadata: None,
3192                files: USER,
3193            }),
3194            SettingsPageItem::SettingItem(SettingItem {
3195                title: "File Icons",
3196                description: "Show file icons in the file finder.",
3197                field: Box::new(SettingField {
3198                    json_path: Some("file_finder.file_icons"),
3199                    pick: |settings_content| {
3200                        settings_content.file_finder.as_ref()?.file_icons.as_ref()
3201                    },
3202                    write: |settings_content, value| {
3203                        settings_content
3204                            .file_finder
3205                            .get_or_insert_default()
3206                            .file_icons = value;
3207                    },
3208                }),
3209                metadata: None,
3210                files: USER,
3211            }),
3212            SettingsPageItem::SettingItem(SettingItem {
3213                title: "Modal Max Width",
3214                description: "Determines how much space the file finder can take up in relation to the available window width.",
3215                field: Box::new(SettingField {
3216                    json_path: Some("file_finder.modal_max_width"),
3217                    pick: |settings_content| {
3218                        settings_content
3219                            .file_finder
3220                            .as_ref()?
3221                            .modal_max_width
3222                            .as_ref()
3223                    },
3224                    write: |settings_content, value| {
3225                        settings_content
3226                            .file_finder
3227                            .get_or_insert_default()
3228                            .modal_max_width = value;
3229                    },
3230                }),
3231                metadata: None,
3232                files: USER,
3233            }),
3234            SettingsPageItem::SettingItem(SettingItem {
3235                title: "Skip Focus For Active In Search",
3236                description: "Whether the file finder should skip focus for the active file in search results.",
3237                field: Box::new(SettingField {
3238                    json_path: Some("file_finder.skip_focus_for_active_in_search"),
3239                    pick: |settings_content| {
3240                        settings_content
3241                            .file_finder
3242                            .as_ref()?
3243                            .skip_focus_for_active_in_search
3244                            .as_ref()
3245                    },
3246                    write: |settings_content, value| {
3247                        settings_content
3248                            .file_finder
3249                            .get_or_insert_default()
3250                            .skip_focus_for_active_in_search = value;
3251                    },
3252                }),
3253                metadata: None,
3254                files: USER,
3255            }),
3256        ]
3257    }
3258
3259    fn file_scan_section() -> [SettingsPageItem; 5] {
3260        [
3261            SettingsPageItem::SectionHeader("File Scan"),
3262            SettingsPageItem::SettingItem(SettingItem {
3263                title: "File Scan Exclusions",
3264                description: "Files or globs of files that will be excluded by Zed entirely. They will be skipped during file scans, file searches, and not be displayed in the project file tree. Takes precedence over \"File Scan Inclusions\"",
3265                field: Box::new(
3266                    SettingField {
3267                        json_path: Some("file_scan_exclusions"),
3268                        pick: |settings_content| {
3269                            settings_content
3270                                .project
3271                                .worktree
3272                                .file_scan_exclusions
3273                                .as_ref()
3274                        },
3275                        write: |settings_content, value| {
3276                            settings_content.project.worktree.file_scan_exclusions = value;
3277                        },
3278                    }
3279                    .unimplemented(),
3280                ),
3281                metadata: None,
3282                files: USER,
3283            }),
3284            SettingsPageItem::SettingItem(SettingItem {
3285                title: "File Scan Inclusions",
3286                description: "Files or globs of files that will be included by Zed, even when ignored by git. This is useful for files that are not tracked by git, but are still important to your project. Note that globs that are overly broad can slow down Zed's file scanning. \"File Scan Exclusions\" takes precedence over these inclusions",
3287                field: Box::new(
3288                    SettingField {
3289                        json_path: Some("file_scan_inclusions"),
3290                        pick: |settings_content| {
3291                            settings_content
3292                                .project
3293                                .worktree
3294                                .file_scan_inclusions
3295                                .as_ref()
3296                        },
3297                        write: |settings_content, value| {
3298                            settings_content.project.worktree.file_scan_inclusions = value;
3299                        },
3300                    }
3301                    .unimplemented(),
3302                ),
3303                metadata: None,
3304                files: USER,
3305            }),
3306            SettingsPageItem::SettingItem(SettingItem {
3307                title: "Restore File State",
3308                description: "Restore previous file state when reopening.",
3309                field: Box::new(SettingField {
3310                    json_path: Some("restore_on_file_reopen"),
3311                    pick: |settings_content| {
3312                        settings_content.workspace.restore_on_file_reopen.as_ref()
3313                    },
3314                    write: |settings_content, value| {
3315                        settings_content.workspace.restore_on_file_reopen = value;
3316                    },
3317                }),
3318                metadata: None,
3319                files: USER,
3320            }),
3321            SettingsPageItem::SettingItem(SettingItem {
3322                title: "Close on File Delete",
3323                description: "Automatically close files that have been deleted.",
3324                field: Box::new(SettingField {
3325                    json_path: Some("close_on_file_delete"),
3326                    pick: |settings_content| {
3327                        settings_content.workspace.close_on_file_delete.as_ref()
3328                    },
3329                    write: |settings_content, value| {
3330                        settings_content.workspace.close_on_file_delete = value;
3331                    },
3332                }),
3333                metadata: None,
3334                files: USER,
3335            }),
3336        ]
3337    }
3338
3339    SettingsPage {
3340        title: "Search & Files",
3341        items: concat_sections![search_section(), file_finder_section(), file_scan_section()],
3342    }
3343}
3344
3345fn window_and_layout_page() -> SettingsPage {
3346    fn status_bar_section() -> [SettingsPageItem; 10] {
3347        [
3348            SettingsPageItem::SectionHeader("Status Bar"),
3349            SettingsPageItem::SettingItem(SettingItem {
3350                title: "Project Panel Button",
3351                description: "Show the project panel button in the status bar.",
3352                field: Box::new(SettingField {
3353                    json_path: Some("project_panel.button"),
3354                    pick: |settings_content| {
3355                        settings_content.project_panel.as_ref()?.button.as_ref()
3356                    },
3357                    write: |settings_content, value| {
3358                        settings_content
3359                            .project_panel
3360                            .get_or_insert_default()
3361                            .button = value;
3362                    },
3363                }),
3364                metadata: None,
3365                files: USER,
3366            }),
3367            SettingsPageItem::SettingItem(SettingItem {
3368                title: "Active Language Button",
3369                description: "Show the active language button in the status bar.",
3370                field: Box::new(SettingField {
3371                    json_path: Some("status_bar.active_language_button"),
3372                    pick: |settings_content| {
3373                        settings_content
3374                            .status_bar
3375                            .as_ref()?
3376                            .active_language_button
3377                            .as_ref()
3378                    },
3379                    write: |settings_content, value| {
3380                        settings_content
3381                            .status_bar
3382                            .get_or_insert_default()
3383                            .active_language_button = value;
3384                    },
3385                }),
3386                metadata: None,
3387                files: USER,
3388            }),
3389            SettingsPageItem::SettingItem(SettingItem {
3390                title: "Active Encoding Button",
3391                description: "Control when to show the active encoding in the status bar.",
3392                field: Box::new(SettingField {
3393                    json_path: Some("status_bar.active_encoding_button"),
3394                    pick: |settings_content| {
3395                        settings_content
3396                            .status_bar
3397                            .as_ref()?
3398                            .active_encoding_button
3399                            .as_ref()
3400                    },
3401                    write: |settings_content, value| {
3402                        settings_content
3403                            .status_bar
3404                            .get_or_insert_default()
3405                            .active_encoding_button = value;
3406                    },
3407                }),
3408                metadata: None,
3409                files: USER,
3410            }),
3411            SettingsPageItem::SettingItem(SettingItem {
3412                title: "Cursor Position Button",
3413                description: "Show the cursor position button in the status bar.",
3414                field: Box::new(SettingField {
3415                    json_path: Some("status_bar.cursor_position_button"),
3416                    pick: |settings_content| {
3417                        settings_content
3418                            .status_bar
3419                            .as_ref()?
3420                            .cursor_position_button
3421                            .as_ref()
3422                    },
3423                    write: |settings_content, value| {
3424                        settings_content
3425                            .status_bar
3426                            .get_or_insert_default()
3427                            .cursor_position_button = value;
3428                    },
3429                }),
3430                metadata: None,
3431                files: USER,
3432            }),
3433            SettingsPageItem::SettingItem(SettingItem {
3434                title: "Terminal Button",
3435                description: "Show the terminal button in the status bar.",
3436                field: Box::new(SettingField {
3437                    json_path: Some("terminal.button"),
3438                    pick: |settings_content| settings_content.terminal.as_ref()?.button.as_ref(),
3439                    write: |settings_content, value| {
3440                        settings_content.terminal.get_or_insert_default().button = value;
3441                    },
3442                }),
3443                metadata: None,
3444                files: USER,
3445            }),
3446            SettingsPageItem::SettingItem(SettingItem {
3447                title: "Diagnostics Button",
3448                description: "Show the project diagnostics button in the status bar.",
3449                field: Box::new(SettingField {
3450                    json_path: Some("diagnostics.button"),
3451                    pick: |settings_content| settings_content.diagnostics.as_ref()?.button.as_ref(),
3452                    write: |settings_content, value| {
3453                        settings_content.diagnostics.get_or_insert_default().button = value;
3454                    },
3455                }),
3456                metadata: None,
3457                files: USER,
3458            }),
3459            SettingsPageItem::SettingItem(SettingItem {
3460                title: "Project Search Button",
3461                description: "Show the project search button in the status bar.",
3462                field: Box::new(SettingField {
3463                    json_path: Some("search.button"),
3464                    pick: |settings_content| {
3465                        settings_content.editor.search.as_ref()?.button.as_ref()
3466                    },
3467                    write: |settings_content, value| {
3468                        settings_content
3469                            .editor
3470                            .search
3471                            .get_or_insert_default()
3472                            .button = value;
3473                    },
3474                }),
3475                metadata: None,
3476                files: USER,
3477            }),
3478            SettingsPageItem::SettingItem(SettingItem {
3479                title: "Debugger Button",
3480                description: "Show the debugger button in the status bar.",
3481                field: Box::new(SettingField {
3482                    json_path: Some("debugger.button"),
3483                    pick: |settings_content| settings_content.debugger.as_ref()?.button.as_ref(),
3484                    write: |settings_content, value| {
3485                        settings_content.debugger.get_or_insert_default().button = value;
3486                    },
3487                }),
3488                metadata: None,
3489                files: USER,
3490            }),
3491            SettingsPageItem::SettingItem(SettingItem {
3492                title: "Active File Name",
3493                description: "Show the name of the active file in the status bar.",
3494                field: Box::new(SettingField {
3495                    json_path: Some("status_bar.show_active_file"),
3496                    pick: |settings_content| {
3497                        settings_content
3498                            .status_bar
3499                            .as_ref()?
3500                            .show_active_file
3501                            .as_ref()
3502                    },
3503                    write: |settings_content, value| {
3504                        settings_content
3505                            .status_bar
3506                            .get_or_insert_default()
3507                            .show_active_file = value;
3508                    },
3509                }),
3510                metadata: None,
3511                files: USER,
3512            }),
3513        ]
3514    }
3515
3516    fn title_bar_section() -> [SettingsPageItem; 10] {
3517        [
3518            SettingsPageItem::SectionHeader("Title Bar"),
3519            SettingsPageItem::SettingItem(SettingItem {
3520                title: "Show Branch Icon",
3521                description: "Show the branch icon beside branch switcher in the titlebar.",
3522                field: Box::new(SettingField {
3523                    json_path: Some("title_bar.show_branch_icon"),
3524                    pick: |settings_content| {
3525                        settings_content
3526                            .title_bar
3527                            .as_ref()?
3528                            .show_branch_icon
3529                            .as_ref()
3530                    },
3531                    write: |settings_content, value| {
3532                        settings_content
3533                            .title_bar
3534                            .get_or_insert_default()
3535                            .show_branch_icon = value;
3536                    },
3537                }),
3538                metadata: None,
3539                files: USER,
3540            }),
3541            SettingsPageItem::SettingItem(SettingItem {
3542                title: "Show Branch Name",
3543                description: "Show the branch name button in the titlebar.",
3544                field: Box::new(SettingField {
3545                    json_path: Some("title_bar.show_branch_name"),
3546                    pick: |settings_content| {
3547                        settings_content
3548                            .title_bar
3549                            .as_ref()?
3550                            .show_branch_name
3551                            .as_ref()
3552                    },
3553                    write: |settings_content, value| {
3554                        settings_content
3555                            .title_bar
3556                            .get_or_insert_default()
3557                            .show_branch_name = value;
3558                    },
3559                }),
3560                metadata: None,
3561                files: USER,
3562            }),
3563            SettingsPageItem::SettingItem(SettingItem {
3564                title: "Show Project Items",
3565                description: "Show the project host and name in the titlebar.",
3566                field: Box::new(SettingField {
3567                    json_path: Some("title_bar.show_project_items"),
3568                    pick: |settings_content| {
3569                        settings_content
3570                            .title_bar
3571                            .as_ref()?
3572                            .show_project_items
3573                            .as_ref()
3574                    },
3575                    write: |settings_content, value| {
3576                        settings_content
3577                            .title_bar
3578                            .get_or_insert_default()
3579                            .show_project_items = value;
3580                    },
3581                }),
3582                metadata: None,
3583                files: USER,
3584            }),
3585            SettingsPageItem::SettingItem(SettingItem {
3586                title: "Show Onboarding Banner",
3587                description: "Show banners announcing new features in the titlebar.",
3588                field: Box::new(SettingField {
3589                    json_path: Some("title_bar.show_onboarding_banner"),
3590                    pick: |settings_content| {
3591                        settings_content
3592                            .title_bar
3593                            .as_ref()?
3594                            .show_onboarding_banner
3595                            .as_ref()
3596                    },
3597                    write: |settings_content, value| {
3598                        settings_content
3599                            .title_bar
3600                            .get_or_insert_default()
3601                            .show_onboarding_banner = value;
3602                    },
3603                }),
3604                metadata: None,
3605                files: USER,
3606            }),
3607            SettingsPageItem::SettingItem(SettingItem {
3608                title: "Show Sign In",
3609                description: "Show the sign in button in the titlebar.",
3610                field: Box::new(SettingField {
3611                    json_path: Some("title_bar.show_sign_in"),
3612                    pick: |settings_content| {
3613                        settings_content.title_bar.as_ref()?.show_sign_in.as_ref()
3614                    },
3615                    write: |settings_content, value| {
3616                        settings_content
3617                            .title_bar
3618                            .get_or_insert_default()
3619                            .show_sign_in = value;
3620                    },
3621                }),
3622                metadata: None,
3623                files: USER,
3624            }),
3625            SettingsPageItem::SettingItem(SettingItem {
3626                title: "Show User Menu",
3627                description: "Show the user menu button in the titlebar.",
3628                field: Box::new(SettingField {
3629                    json_path: Some("title_bar.show_user_menu"),
3630                    pick: |settings_content| {
3631                        settings_content.title_bar.as_ref()?.show_user_menu.as_ref()
3632                    },
3633                    write: |settings_content, value| {
3634                        settings_content
3635                            .title_bar
3636                            .get_or_insert_default()
3637                            .show_user_menu = value;
3638                    },
3639                }),
3640                metadata: None,
3641                files: USER,
3642            }),
3643            SettingsPageItem::SettingItem(SettingItem {
3644                title: "Show User Picture",
3645                description: "Show user picture in the titlebar.",
3646                field: Box::new(SettingField {
3647                    json_path: Some("title_bar.show_user_picture"),
3648                    pick: |settings_content| {
3649                        settings_content
3650                            .title_bar
3651                            .as_ref()?
3652                            .show_user_picture
3653                            .as_ref()
3654                    },
3655                    write: |settings_content, value| {
3656                        settings_content
3657                            .title_bar
3658                            .get_or_insert_default()
3659                            .show_user_picture = value;
3660                    },
3661                }),
3662                metadata: None,
3663                files: USER,
3664            }),
3665            SettingsPageItem::SettingItem(SettingItem {
3666                title: "Show Menus",
3667                description: "Show the menus in the titlebar.",
3668                field: Box::new(SettingField {
3669                    json_path: Some("title_bar.show_menus"),
3670                    pick: |settings_content| {
3671                        settings_content.title_bar.as_ref()?.show_menus.as_ref()
3672                    },
3673                    write: |settings_content, value| {
3674                        settings_content
3675                            .title_bar
3676                            .get_or_insert_default()
3677                            .show_menus = value;
3678                    },
3679                }),
3680                metadata: None,
3681                files: USER,
3682            }),
3683            SettingsPageItem::DynamicItem(DynamicItem {
3684                discriminant: SettingItem {
3685                    files: USER,
3686                    title: "Button Layout",
3687                    description:
3688                        "(Linux only) choose how window control buttons are laid out in the titlebar.",
3689                    field: Box::new(SettingField {
3690                        json_path: Some("title_bar.button_layout$"),
3691                        pick: |settings_content| {
3692                            Some(
3693                                &dynamic_variants::<settings::WindowButtonLayoutContent>()[settings_content
3694                                    .title_bar
3695                                    .as_ref()?
3696                                    .button_layout
3697                                    .as_ref()?
3698                                    .discriminant()
3699                                    as usize],
3700                            )
3701                        },
3702                        write: |settings_content, value| {
3703                            let Some(value) = value else {
3704                                settings_content
3705                                    .title_bar
3706                                    .get_or_insert_default()
3707                                    .button_layout = None;
3708                                return;
3709                            };
3710
3711                            let current_custom_layout = settings_content
3712                                .title_bar
3713                                .as_ref()
3714                                .and_then(|title_bar| title_bar.button_layout.as_ref())
3715                                .and_then(|button_layout| match button_layout {
3716                                    settings::WindowButtonLayoutContent::Custom(layout) => {
3717                                        Some(layout.clone())
3718                                    }
3719                                    _ => None,
3720                                });
3721
3722                            let button_layout = match value {
3723                                settings::WindowButtonLayoutContentDiscriminants::PlatformDefault => {
3724                                    settings::WindowButtonLayoutContent::PlatformDefault
3725                                }
3726                                settings::WindowButtonLayoutContentDiscriminants::Standard => {
3727                                    settings::WindowButtonLayoutContent::Standard
3728                                }
3729                                settings::WindowButtonLayoutContentDiscriminants::Custom => {
3730                                    settings::WindowButtonLayoutContent::Custom(
3731                                        current_custom_layout.unwrap_or_else(|| {
3732                                            "close:minimize,maximize".to_string()
3733                                        }),
3734                                    )
3735                                }
3736                            };
3737
3738                            settings_content
3739                                .title_bar
3740                                .get_or_insert_default()
3741                                .button_layout = Some(button_layout);
3742                        },
3743                    }),
3744                    metadata: None,
3745                },
3746                pick_discriminant: |settings_content| {
3747                    Some(
3748                        settings_content
3749                            .title_bar
3750                            .as_ref()?
3751                            .button_layout
3752                            .as_ref()?
3753                            .discriminant() as usize,
3754                    )
3755                },
3756                fields: dynamic_variants::<settings::WindowButtonLayoutContent>()
3757                    .into_iter()
3758                    .map(|variant| match variant {
3759                        settings::WindowButtonLayoutContentDiscriminants::PlatformDefault => {
3760                            vec![]
3761                        }
3762                        settings::WindowButtonLayoutContentDiscriminants::Standard => vec![],
3763                        settings::WindowButtonLayoutContentDiscriminants::Custom => vec![
3764                            SettingItem {
3765                                files: USER,
3766                                title: "Custom Button Layout",
3767                                description:
3768                                    "GNOME-style layout string such as \"close:minimize,maximize\".",
3769                                field: Box::new(SettingField {
3770                                    json_path: Some("title_bar.button_layout"),
3771                                    pick: |settings_content| match settings_content
3772                                        .title_bar
3773                                        .as_ref()?
3774                                        .button_layout
3775                                        .as_ref()?
3776                                    {
3777                                        settings::WindowButtonLayoutContent::Custom(layout) => {
3778                                            Some(layout)
3779                                        }
3780                                        _ => DEFAULT_EMPTY_STRING,
3781                                    },
3782                                    write: |settings_content, value| {
3783                                        settings_content
3784                                            .title_bar
3785                                            .get_or_insert_default()
3786                                            .button_layout = value
3787                                            .map(settings::WindowButtonLayoutContent::Custom);
3788                                    },
3789                                }),
3790                                metadata: Some(Box::new(SettingsFieldMetadata {
3791                                    placeholder: Some("close:minimize,maximize"),
3792                                    ..Default::default()
3793                                })),
3794                            },
3795                        ],
3796                    })
3797                    .collect(),
3798            }),
3799        ]
3800    }
3801
3802    fn tab_bar_section() -> [SettingsPageItem; 9] {
3803        [
3804            SettingsPageItem::SectionHeader("Tab Bar"),
3805            SettingsPageItem::SettingItem(SettingItem {
3806                title: "Show Tab Bar",
3807                description: "Show the tab bar in the editor.",
3808                field: Box::new(SettingField {
3809                    json_path: Some("tab_bar.show"),
3810                    pick: |settings_content| settings_content.tab_bar.as_ref()?.show.as_ref(),
3811                    write: |settings_content, value| {
3812                        settings_content.tab_bar.get_or_insert_default().show = value;
3813                    },
3814                }),
3815                metadata: None,
3816                files: USER,
3817            }),
3818            SettingsPageItem::SettingItem(SettingItem {
3819                title: "Show Git Status In Tabs",
3820                description: "Show the Git file status on a tab item.",
3821                field: Box::new(SettingField {
3822                    json_path: Some("tabs.git_status"),
3823                    pick: |settings_content| settings_content.tabs.as_ref()?.git_status.as_ref(),
3824                    write: |settings_content, value| {
3825                        settings_content.tabs.get_or_insert_default().git_status = value;
3826                    },
3827                }),
3828                metadata: None,
3829                files: USER,
3830            }),
3831            SettingsPageItem::SettingItem(SettingItem {
3832                title: "Show File Icons In Tabs",
3833                description: "Show the file icon for a tab.",
3834                field: Box::new(SettingField {
3835                    json_path: Some("tabs.file_icons"),
3836                    pick: |settings_content| settings_content.tabs.as_ref()?.file_icons.as_ref(),
3837                    write: |settings_content, value| {
3838                        settings_content.tabs.get_or_insert_default().file_icons = value;
3839                    },
3840                }),
3841                metadata: None,
3842                files: USER,
3843            }),
3844            SettingsPageItem::SettingItem(SettingItem {
3845                title: "Tab Close Position",
3846                description: "Position of the close button in a tab.",
3847                field: Box::new(SettingField {
3848                    json_path: Some("tabs.close_position"),
3849                    pick: |settings_content| {
3850                        settings_content.tabs.as_ref()?.close_position.as_ref()
3851                    },
3852                    write: |settings_content, value| {
3853                        settings_content.tabs.get_or_insert_default().close_position = value;
3854                    },
3855                }),
3856                metadata: None,
3857                files: USER,
3858            }),
3859            SettingsPageItem::SettingItem(SettingItem {
3860                files: USER,
3861                title: "Maximum Tabs",
3862                description: "Maximum open tabs in a pane. Will not close an unsaved tab.",
3863                // todo(settings_ui): The default for this value is null and it's use in code
3864                // is complex, so I'm going to come back to this later
3865                field: Box::new(
3866                    SettingField {
3867                        json_path: Some("max_tabs"),
3868                        pick: |settings_content| settings_content.workspace.max_tabs.as_ref(),
3869                        write: |settings_content, value| {
3870                            settings_content.workspace.max_tabs = value;
3871                        },
3872                    }
3873                    .unimplemented(),
3874                ),
3875                metadata: None,
3876            }),
3877            SettingsPageItem::SettingItem(SettingItem {
3878                title: "Show Navigation History Buttons",
3879                description: "Show the navigation history buttons in the tab bar.",
3880                field: Box::new(SettingField {
3881                    json_path: Some("tab_bar.show_nav_history_buttons"),
3882                    pick: |settings_content| {
3883                        settings_content
3884                            .tab_bar
3885                            .as_ref()?
3886                            .show_nav_history_buttons
3887                            .as_ref()
3888                    },
3889                    write: |settings_content, value| {
3890                        settings_content
3891                            .tab_bar
3892                            .get_or_insert_default()
3893                            .show_nav_history_buttons = value;
3894                    },
3895                }),
3896                metadata: None,
3897                files: USER,
3898            }),
3899            SettingsPageItem::SettingItem(SettingItem {
3900                title: "Show Tab Bar Buttons",
3901                description: "Show the tab bar buttons (New, Split Pane, Zoom).",
3902                field: Box::new(SettingField {
3903                    json_path: Some("tab_bar.show_tab_bar_buttons"),
3904                    pick: |settings_content| {
3905                        settings_content
3906                            .tab_bar
3907                            .as_ref()?
3908                            .show_tab_bar_buttons
3909                            .as_ref()
3910                    },
3911                    write: |settings_content, value| {
3912                        settings_content
3913                            .tab_bar
3914                            .get_or_insert_default()
3915                            .show_tab_bar_buttons = value;
3916                    },
3917                }),
3918                metadata: None,
3919                files: USER,
3920            }),
3921            SettingsPageItem::SettingItem(SettingItem {
3922                title: "Pinned Tabs Layout",
3923                description: "Show pinned tabs in a separate row above unpinned tabs.",
3924                field: Box::new(SettingField {
3925                    json_path: Some("tab_bar.show_pinned_tabs_in_separate_row"),
3926                    pick: |settings_content| {
3927                        settings_content
3928                            .tab_bar
3929                            .as_ref()?
3930                            .show_pinned_tabs_in_separate_row
3931                            .as_ref()
3932                    },
3933                    write: |settings_content, value| {
3934                        settings_content
3935                            .tab_bar
3936                            .get_or_insert_default()
3937                            .show_pinned_tabs_in_separate_row = value;
3938                    },
3939                }),
3940                metadata: None,
3941                files: USER,
3942            }),
3943        ]
3944    }
3945
3946    fn tab_settings_section() -> [SettingsPageItem; 4] {
3947        [
3948            SettingsPageItem::SectionHeader("Tab Settings"),
3949            SettingsPageItem::SettingItem(SettingItem {
3950                title: "Activate On Close",
3951                description: "What to do after closing the current tab.",
3952                field: Box::new(SettingField {
3953                    json_path: Some("tabs.activate_on_close"),
3954                    pick: |settings_content| {
3955                        settings_content.tabs.as_ref()?.activate_on_close.as_ref()
3956                    },
3957                    write: |settings_content, value| {
3958                        settings_content
3959                            .tabs
3960                            .get_or_insert_default()
3961                            .activate_on_close = value;
3962                    },
3963                }),
3964                metadata: None,
3965                files: USER,
3966            }),
3967            SettingsPageItem::SettingItem(SettingItem {
3968                title: "Tab Show Diagnostics",
3969                description: "Which files containing diagnostic errors/warnings to mark in the tabs.",
3970                field: Box::new(SettingField {
3971                    json_path: Some("tabs.show_diagnostics"),
3972                    pick: |settings_content| {
3973                        settings_content.tabs.as_ref()?.show_diagnostics.as_ref()
3974                    },
3975                    write: |settings_content, value| {
3976                        settings_content
3977                            .tabs
3978                            .get_or_insert_default()
3979                            .show_diagnostics = value;
3980                    },
3981                }),
3982                metadata: None,
3983                files: USER,
3984            }),
3985            SettingsPageItem::SettingItem(SettingItem {
3986                title: "Show Close Button",
3987                description: "Controls the appearance behavior of the tab's close button.",
3988                field: Box::new(SettingField {
3989                    json_path: Some("tabs.show_close_button"),
3990                    pick: |settings_content| {
3991                        settings_content.tabs.as_ref()?.show_close_button.as_ref()
3992                    },
3993                    write: |settings_content, value| {
3994                        settings_content
3995                            .tabs
3996                            .get_or_insert_default()
3997                            .show_close_button = value;
3998                    },
3999                }),
4000                metadata: None,
4001                files: USER,
4002            }),
4003        ]
4004    }
4005
4006    fn preview_tabs_section() -> [SettingsPageItem; 8] {
4007        [
4008            SettingsPageItem::SectionHeader("Preview Tabs"),
4009            SettingsPageItem::SettingItem(SettingItem {
4010                title: "Preview Tabs Enabled",
4011                description: "Show opened editors as preview tabs.",
4012                field: Box::new(SettingField {
4013                    json_path: Some("preview_tabs.enabled"),
4014                    pick: |settings_content| {
4015                        settings_content.preview_tabs.as_ref()?.enabled.as_ref()
4016                    },
4017                    write: |settings_content, value| {
4018                        settings_content
4019                            .preview_tabs
4020                            .get_or_insert_default()
4021                            .enabled = value;
4022                    },
4023                }),
4024                metadata: None,
4025                files: USER,
4026            }),
4027            SettingsPageItem::SettingItem(SettingItem {
4028                title: "Enable Preview From Project Panel",
4029                description: "Whether to open tabs in preview mode when opened from the project panel with a single click.",
4030                field: Box::new(SettingField {
4031                    json_path: Some("preview_tabs.enable_preview_from_project_panel"),
4032                    pick: |settings_content| {
4033                        settings_content
4034                            .preview_tabs
4035                            .as_ref()?
4036                            .enable_preview_from_project_panel
4037                            .as_ref()
4038                    },
4039                    write: |settings_content, value| {
4040                        settings_content
4041                            .preview_tabs
4042                            .get_or_insert_default()
4043                            .enable_preview_from_project_panel = value;
4044                    },
4045                }),
4046                metadata: None,
4047                files: USER,
4048            }),
4049            SettingsPageItem::SettingItem(SettingItem {
4050                title: "Enable Preview From File Finder",
4051                description: "Whether to open tabs in preview mode when selected from the file finder.",
4052                field: Box::new(SettingField {
4053                    json_path: Some("preview_tabs.enable_preview_from_file_finder"),
4054                    pick: |settings_content| {
4055                        settings_content
4056                            .preview_tabs
4057                            .as_ref()?
4058                            .enable_preview_from_file_finder
4059                            .as_ref()
4060                    },
4061                    write: |settings_content, value| {
4062                        settings_content
4063                            .preview_tabs
4064                            .get_or_insert_default()
4065                            .enable_preview_from_file_finder = value;
4066                    },
4067                }),
4068                metadata: None,
4069                files: USER,
4070            }),
4071            SettingsPageItem::SettingItem(SettingItem {
4072                title: "Enable Preview From Multibuffer",
4073                description: "Whether to open tabs in preview mode when opened from a multibuffer.",
4074                field: Box::new(SettingField {
4075                    json_path: Some("preview_tabs.enable_preview_from_multibuffer"),
4076                    pick: |settings_content| {
4077                        settings_content
4078                            .preview_tabs
4079                            .as_ref()?
4080                            .enable_preview_from_multibuffer
4081                            .as_ref()
4082                    },
4083                    write: |settings_content, value| {
4084                        settings_content
4085                            .preview_tabs
4086                            .get_or_insert_default()
4087                            .enable_preview_from_multibuffer = value;
4088                    },
4089                }),
4090                metadata: None,
4091                files: USER,
4092            }),
4093            SettingsPageItem::SettingItem(SettingItem {
4094                title: "Enable Preview Multibuffer From Code Navigation",
4095                description: "Whether to open tabs in preview mode when code navigation is used to open a multibuffer.",
4096                field: Box::new(SettingField {
4097                    json_path: Some("preview_tabs.enable_preview_multibuffer_from_code_navigation"),
4098                    pick: |settings_content| {
4099                        settings_content
4100                            .preview_tabs
4101                            .as_ref()?
4102                            .enable_preview_multibuffer_from_code_navigation
4103                            .as_ref()
4104                    },
4105                    write: |settings_content, value| {
4106                        settings_content
4107                            .preview_tabs
4108                            .get_or_insert_default()
4109                            .enable_preview_multibuffer_from_code_navigation = value;
4110                    },
4111                }),
4112                metadata: None,
4113                files: USER,
4114            }),
4115            SettingsPageItem::SettingItem(SettingItem {
4116                title: "Enable Preview File From Code Navigation",
4117                description: "Whether to open tabs in preview mode when code navigation is used to open a single file.",
4118                field: Box::new(SettingField {
4119                    json_path: Some("preview_tabs.enable_preview_file_from_code_navigation"),
4120                    pick: |settings_content| {
4121                        settings_content
4122                            .preview_tabs
4123                            .as_ref()?
4124                            .enable_preview_file_from_code_navigation
4125                            .as_ref()
4126                    },
4127                    write: |settings_content, value| {
4128                        settings_content
4129                            .preview_tabs
4130                            .get_or_insert_default()
4131                            .enable_preview_file_from_code_navigation = value;
4132                    },
4133                }),
4134                metadata: None,
4135                files: USER,
4136            }),
4137            SettingsPageItem::SettingItem(SettingItem {
4138                title: "Enable Keep Preview On Code Navigation",
4139                description: "Whether to keep tabs in preview mode when code navigation is used to navigate away from them. If `enable_preview_file_from_code_navigation` or `enable_preview_multibuffer_from_code_navigation` is also true, the new tab may replace the existing one.",
4140                field: Box::new(SettingField {
4141                    json_path: Some("preview_tabs.enable_keep_preview_on_code_navigation"),
4142                    pick: |settings_content| {
4143                        settings_content
4144                            .preview_tabs
4145                            .as_ref()?
4146                            .enable_keep_preview_on_code_navigation
4147                            .as_ref()
4148                    },
4149                    write: |settings_content, value| {
4150                        settings_content
4151                            .preview_tabs
4152                            .get_or_insert_default()
4153                            .enable_keep_preview_on_code_navigation = value;
4154                    },
4155                }),
4156                metadata: None,
4157                files: USER,
4158            }),
4159        ]
4160    }
4161
4162    fn layout_section() -> [SettingsPageItem; 4] {
4163        [
4164            SettingsPageItem::SectionHeader("Layout"),
4165            SettingsPageItem::SettingItem(SettingItem {
4166                title: "Bottom Dock Layout",
4167                description: "Layout mode for the bottom dock.",
4168                field: Box::new(SettingField {
4169                    json_path: Some("bottom_dock_layout"),
4170                    pick: |settings_content| settings_content.workspace.bottom_dock_layout.as_ref(),
4171                    write: |settings_content, value| {
4172                        settings_content.workspace.bottom_dock_layout = value;
4173                    },
4174                }),
4175                metadata: None,
4176                files: USER,
4177            }),
4178            SettingsPageItem::SettingItem(SettingItem {
4179                files: USER,
4180                title: "Centered Layout Left Padding",
4181                description: "Left padding for centered layout.",
4182                field: Box::new(SettingField {
4183                    json_path: Some("centered_layout.left_padding"),
4184                    pick: |settings_content| {
4185                        settings_content
4186                            .workspace
4187                            .centered_layout
4188                            .as_ref()?
4189                            .left_padding
4190                            .as_ref()
4191                    },
4192                    write: |settings_content, value| {
4193                        settings_content
4194                            .workspace
4195                            .centered_layout
4196                            .get_or_insert_default()
4197                            .left_padding = value;
4198                    },
4199                }),
4200                metadata: None,
4201            }),
4202            SettingsPageItem::SettingItem(SettingItem {
4203                files: USER,
4204                title: "Centered Layout Right Padding",
4205                description: "Right padding for centered layout.",
4206                field: Box::new(SettingField {
4207                    json_path: Some("centered_layout.right_padding"),
4208                    pick: |settings_content| {
4209                        settings_content
4210                            .workspace
4211                            .centered_layout
4212                            .as_ref()?
4213                            .right_padding
4214                            .as_ref()
4215                    },
4216                    write: |settings_content, value| {
4217                        settings_content
4218                            .workspace
4219                            .centered_layout
4220                            .get_or_insert_default()
4221                            .right_padding = value;
4222                    },
4223                }),
4224                metadata: None,
4225            }),
4226        ]
4227    }
4228
4229    fn window_section() -> [SettingsPageItem; 3] {
4230        [
4231            SettingsPageItem::SectionHeader("Window"),
4232            // todo(settings_ui): Should we filter by platform.as_ref()?
4233            SettingsPageItem::SettingItem(SettingItem {
4234                title: "Use System Window Tabs",
4235                description: "(macOS only) whether to allow Windows to tab together.",
4236                field: Box::new(SettingField {
4237                    json_path: Some("use_system_window_tabs"),
4238                    pick: |settings_content| {
4239                        settings_content.workspace.use_system_window_tabs.as_ref()
4240                    },
4241                    write: |settings_content, value| {
4242                        settings_content.workspace.use_system_window_tabs = value;
4243                    },
4244                }),
4245                metadata: None,
4246                files: USER,
4247            }),
4248            SettingsPageItem::SettingItem(SettingItem {
4249                title: "Window Decorations",
4250                description: "(Linux only) whether Zed or your compositor should draw window decorations.",
4251                field: Box::new(SettingField {
4252                    json_path: Some("window_decorations"),
4253                    pick: |settings_content| settings_content.workspace.window_decorations.as_ref(),
4254                    write: |settings_content, value| {
4255                        settings_content.workspace.window_decorations = value;
4256                    },
4257                }),
4258                metadata: None,
4259                files: USER,
4260            }),
4261        ]
4262    }
4263
4264    fn pane_modifiers_section() -> [SettingsPageItem; 4] {
4265        [
4266            SettingsPageItem::SectionHeader("Pane Modifiers"),
4267            SettingsPageItem::SettingItem(SettingItem {
4268                title: "Inactive Opacity",
4269                description: "Opacity of inactive panels (0.0 - 1.0).",
4270                field: Box::new(SettingField {
4271                    json_path: Some("active_pane_modifiers.inactive_opacity"),
4272                    pick: |settings_content| {
4273                        settings_content
4274                            .workspace
4275                            .active_pane_modifiers
4276                            .as_ref()?
4277                            .inactive_opacity
4278                            .as_ref()
4279                    },
4280                    write: |settings_content, value| {
4281                        settings_content
4282                            .workspace
4283                            .active_pane_modifiers
4284                            .get_or_insert_default()
4285                            .inactive_opacity = value;
4286                    },
4287                }),
4288                metadata: None,
4289                files: USER,
4290            }),
4291            SettingsPageItem::SettingItem(SettingItem {
4292                title: "Border Size",
4293                description: "Size of the border surrounding the active pane.",
4294                field: Box::new(SettingField {
4295                    json_path: Some("active_pane_modifiers.border_size"),
4296                    pick: |settings_content| {
4297                        settings_content
4298                            .workspace
4299                            .active_pane_modifiers
4300                            .as_ref()?
4301                            .border_size
4302                            .as_ref()
4303                    },
4304                    write: |settings_content, value| {
4305                        settings_content
4306                            .workspace
4307                            .active_pane_modifiers
4308                            .get_or_insert_default()
4309                            .border_size = value;
4310                    },
4311                }),
4312                metadata: None,
4313                files: USER,
4314            }),
4315            SettingsPageItem::SettingItem(SettingItem {
4316                title: "Zoomed Padding",
4317                description: "Show padding for zoomed panes.",
4318                field: Box::new(SettingField {
4319                    json_path: Some("zoomed_padding"),
4320                    pick: |settings_content| settings_content.workspace.zoomed_padding.as_ref(),
4321                    write: |settings_content, value| {
4322                        settings_content.workspace.zoomed_padding = value;
4323                    },
4324                }),
4325                metadata: None,
4326                files: USER,
4327            }),
4328        ]
4329    }
4330
4331    fn pane_split_direction_section() -> [SettingsPageItem; 3] {
4332        [
4333            SettingsPageItem::SectionHeader("Pane Split Direction"),
4334            SettingsPageItem::SettingItem(SettingItem {
4335                title: "Vertical Split Direction",
4336                description: "Direction to split vertically.",
4337                field: Box::new(SettingField {
4338                    json_path: Some("pane_split_direction_vertical"),
4339                    pick: |settings_content| {
4340                        settings_content
4341                            .workspace
4342                            .pane_split_direction_vertical
4343                            .as_ref()
4344                    },
4345                    write: |settings_content, value| {
4346                        settings_content.workspace.pane_split_direction_vertical = value;
4347                    },
4348                }),
4349                metadata: None,
4350                files: USER,
4351            }),
4352            SettingsPageItem::SettingItem(SettingItem {
4353                title: "Horizontal Split Direction",
4354                description: "Direction to split horizontally.",
4355                field: Box::new(SettingField {
4356                    json_path: Some("pane_split_direction_horizontal"),
4357                    pick: |settings_content| {
4358                        settings_content
4359                            .workspace
4360                            .pane_split_direction_horizontal
4361                            .as_ref()
4362                    },
4363                    write: |settings_content, value| {
4364                        settings_content.workspace.pane_split_direction_horizontal = value;
4365                    },
4366                }),
4367                metadata: None,
4368                files: USER,
4369            }),
4370        ]
4371    }
4372
4373    SettingsPage {
4374        title: "Window & Layout",
4375        items: concat_sections![
4376            status_bar_section(),
4377            title_bar_section(),
4378            tab_bar_section(),
4379            tab_settings_section(),
4380            preview_tabs_section(),
4381            layout_section(),
4382            window_section(),
4383            pane_modifiers_section(),
4384            pane_split_direction_section(),
4385        ],
4386    }
4387}
4388
4389fn panels_page() -> SettingsPage {
4390    fn project_panel_section() -> [SettingsPageItem; 24] {
4391        [
4392            SettingsPageItem::SectionHeader("Project Panel"),
4393            SettingsPageItem::SettingItem(SettingItem {
4394                title: "Project Panel Dock",
4395                description: "Where to dock the project panel.",
4396                field: Box::new(SettingField {
4397                    json_path: Some("project_panel.dock"),
4398                    pick: |settings_content| settings_content.project_panel.as_ref()?.dock.as_ref(),
4399                    write: |settings_content, value| {
4400                        settings_content.project_panel.get_or_insert_default().dock = value;
4401                    },
4402                }),
4403                metadata: None,
4404                files: USER,
4405            }),
4406            SettingsPageItem::SettingItem(SettingItem {
4407                title: "Project Panel Default Width",
4408                description: "Default width of the project panel in pixels.",
4409                field: Box::new(SettingField {
4410                    json_path: Some("project_panel.default_width"),
4411                    pick: |settings_content| {
4412                        settings_content
4413                            .project_panel
4414                            .as_ref()?
4415                            .default_width
4416                            .as_ref()
4417                    },
4418                    write: |settings_content, value| {
4419                        settings_content
4420                            .project_panel
4421                            .get_or_insert_default()
4422                            .default_width = value;
4423                    },
4424                }),
4425                metadata: None,
4426                files: USER,
4427            }),
4428            SettingsPageItem::SettingItem(SettingItem {
4429                title: "Hide .gitignore",
4430                description: "Whether to hide the gitignore entries in the project panel.",
4431                field: Box::new(SettingField {
4432                    json_path: Some("project_panel.hide_gitignore"),
4433                    pick: |settings_content| {
4434                        settings_content
4435                            .project_panel
4436                            .as_ref()?
4437                            .hide_gitignore
4438                            .as_ref()
4439                    },
4440                    write: |settings_content, value| {
4441                        settings_content
4442                            .project_panel
4443                            .get_or_insert_default()
4444                            .hide_gitignore = value;
4445                    },
4446                }),
4447                metadata: None,
4448                files: USER,
4449            }),
4450            SettingsPageItem::SettingItem(SettingItem {
4451                title: "Entry Spacing",
4452                description: "Spacing between worktree entries in the project panel.",
4453                field: Box::new(SettingField {
4454                    json_path: Some("project_panel.entry_spacing"),
4455                    pick: |settings_content| {
4456                        settings_content
4457                            .project_panel
4458                            .as_ref()?
4459                            .entry_spacing
4460                            .as_ref()
4461                    },
4462                    write: |settings_content, value| {
4463                        settings_content
4464                            .project_panel
4465                            .get_or_insert_default()
4466                            .entry_spacing = value;
4467                    },
4468                }),
4469                metadata: None,
4470                files: USER,
4471            }),
4472            SettingsPageItem::SettingItem(SettingItem {
4473                title: "File Icons",
4474                description: "Show file icons in the project panel.",
4475                field: Box::new(SettingField {
4476                    json_path: Some("project_panel.file_icons"),
4477                    pick: |settings_content| {
4478                        settings_content.project_panel.as_ref()?.file_icons.as_ref()
4479                    },
4480                    write: |settings_content, value| {
4481                        settings_content
4482                            .project_panel
4483                            .get_or_insert_default()
4484                            .file_icons = value;
4485                    },
4486                }),
4487                metadata: None,
4488                files: USER,
4489            }),
4490            SettingsPageItem::SettingItem(SettingItem {
4491                title: "Folder Icons",
4492                description: "Whether to show folder icons or chevrons for directories in the project panel.",
4493                field: Box::new(SettingField {
4494                    json_path: Some("project_panel.folder_icons"),
4495                    pick: |settings_content| {
4496                        settings_content
4497                            .project_panel
4498                            .as_ref()?
4499                            .folder_icons
4500                            .as_ref()
4501                    },
4502                    write: |settings_content, value| {
4503                        settings_content
4504                            .project_panel
4505                            .get_or_insert_default()
4506                            .folder_icons = value;
4507                    },
4508                }),
4509                metadata: None,
4510                files: USER,
4511            }),
4512            SettingsPageItem::SettingItem(SettingItem {
4513                title: "Git Status",
4514                description: "Show the Git status in the project panel.",
4515                field: Box::new(SettingField {
4516                    json_path: Some("project_panel.git_status"),
4517                    pick: |settings_content| {
4518                        settings_content.project_panel.as_ref()?.git_status.as_ref()
4519                    },
4520                    write: |settings_content, value| {
4521                        settings_content
4522                            .project_panel
4523                            .get_or_insert_default()
4524                            .git_status = value;
4525                    },
4526                }),
4527                metadata: None,
4528                files: USER,
4529            }),
4530            SettingsPageItem::SettingItem(SettingItem {
4531                title: "Indent Size",
4532                description: "Amount of indentation for nested items.",
4533                field: Box::new(SettingField {
4534                    json_path: Some("project_panel.indent_size"),
4535                    pick: |settings_content| {
4536                        settings_content
4537                            .project_panel
4538                            .as_ref()?
4539                            .indent_size
4540                            .as_ref()
4541                    },
4542                    write: |settings_content, value| {
4543                        settings_content
4544                            .project_panel
4545                            .get_or_insert_default()
4546                            .indent_size = value;
4547                    },
4548                }),
4549                metadata: None,
4550                files: USER,
4551            }),
4552            SettingsPageItem::SettingItem(SettingItem {
4553                title: "Auto Reveal Entries",
4554                description: "Whether to reveal entries in the project panel automatically when a corresponding project entry becomes active.",
4555                field: Box::new(SettingField {
4556                    json_path: Some("project_panel.auto_reveal_entries"),
4557                    pick: |settings_content| {
4558                        settings_content
4559                            .project_panel
4560                            .as_ref()?
4561                            .auto_reveal_entries
4562                            .as_ref()
4563                    },
4564                    write: |settings_content, value| {
4565                        settings_content
4566                            .project_panel
4567                            .get_or_insert_default()
4568                            .auto_reveal_entries = value;
4569                    },
4570                }),
4571                metadata: None,
4572                files: USER,
4573            }),
4574            SettingsPageItem::SettingItem(SettingItem {
4575                title: "Starts Open",
4576                description: "Whether the project panel should open on startup.",
4577                field: Box::new(SettingField {
4578                    json_path: Some("project_panel.starts_open"),
4579                    pick: |settings_content| {
4580                        settings_content
4581                            .project_panel
4582                            .as_ref()?
4583                            .starts_open
4584                            .as_ref()
4585                    },
4586                    write: |settings_content, value| {
4587                        settings_content
4588                            .project_panel
4589                            .get_or_insert_default()
4590                            .starts_open = value;
4591                    },
4592                }),
4593                metadata: None,
4594                files: USER,
4595            }),
4596            SettingsPageItem::SettingItem(SettingItem {
4597                title: "Auto Fold Directories",
4598                description: "Whether to fold directories automatically and show compact folders when a directory has only one subdirectory inside.",
4599                field: Box::new(SettingField {
4600                    json_path: Some("project_panel.auto_fold_dirs"),
4601                    pick: |settings_content| {
4602                        settings_content
4603                            .project_panel
4604                            .as_ref()?
4605                            .auto_fold_dirs
4606                            .as_ref()
4607                    },
4608                    write: |settings_content, value| {
4609                        settings_content
4610                            .project_panel
4611                            .get_or_insert_default()
4612                            .auto_fold_dirs = value;
4613                    },
4614                }),
4615                metadata: None,
4616                files: USER,
4617            }),
4618            SettingsPageItem::SettingItem(SettingItem {
4619                title: "Bold Folder Labels",
4620                description: "Whether to show folder names with bold text in the project panel.",
4621                field: Box::new(SettingField {
4622                    json_path: Some("project_panel.bold_folder_labels"),
4623                    pick: |settings_content| {
4624                        settings_content
4625                            .project_panel
4626                            .as_ref()?
4627                            .bold_folder_labels
4628                            .as_ref()
4629                    },
4630                    write: |settings_content, value| {
4631                        settings_content
4632                            .project_panel
4633                            .get_or_insert_default()
4634                            .bold_folder_labels = value;
4635                    },
4636                }),
4637                metadata: None,
4638                files: USER,
4639            }),
4640            SettingsPageItem::SettingItem(SettingItem {
4641                title: "Show Scrollbar",
4642                description: "Show the scrollbar in the project panel.",
4643                field: Box::new(SettingField {
4644                    json_path: Some("project_panel.scrollbar.show"),
4645                    pick: |settings_content| {
4646                        show_scrollbar_or_editor(settings_content, |settings_content| {
4647                            settings_content
4648                                .project_panel
4649                                .as_ref()?
4650                                .scrollbar
4651                                .as_ref()?
4652                                .show
4653                                .as_ref()
4654                        })
4655                    },
4656                    write: |settings_content, value| {
4657                        settings_content
4658                            .project_panel
4659                            .get_or_insert_default()
4660                            .scrollbar
4661                            .get_or_insert_default()
4662                            .show = value;
4663                    },
4664                }),
4665                metadata: None,
4666                files: USER,
4667            }),
4668            SettingsPageItem::SettingItem(SettingItem {
4669                title: "Horizontal Scroll",
4670                description: "Whether to allow horizontal scrolling in the project panel. When disabled, the view is always locked to the leftmost position and long file names are clipped.",
4671                field: Box::new(SettingField {
4672                    json_path: Some("project_panel.scrollbar.horizontal_scroll"),
4673                    pick: |settings_content| {
4674                        settings_content
4675                            .project_panel
4676                            .as_ref()?
4677                            .scrollbar
4678                            .as_ref()?
4679                            .horizontal_scroll
4680                            .as_ref()
4681                    },
4682                    write: |settings_content, value| {
4683                        settings_content
4684                            .project_panel
4685                            .get_or_insert_default()
4686                            .scrollbar
4687                            .get_or_insert_default()
4688                            .horizontal_scroll = value;
4689                    },
4690                }),
4691                metadata: None,
4692                files: USER,
4693            }),
4694            SettingsPageItem::SettingItem(SettingItem {
4695                title: "Show Diagnostics",
4696                description: "Which files containing diagnostic errors/warnings to mark in the project panel.",
4697                field: Box::new(SettingField {
4698                    json_path: Some("project_panel.show_diagnostics"),
4699                    pick: |settings_content| {
4700                        settings_content
4701                            .project_panel
4702                            .as_ref()?
4703                            .show_diagnostics
4704                            .as_ref()
4705                    },
4706                    write: |settings_content, value| {
4707                        settings_content
4708                            .project_panel
4709                            .get_or_insert_default()
4710                            .show_diagnostics = value;
4711                    },
4712                }),
4713                metadata: None,
4714                files: USER,
4715            }),
4716            SettingsPageItem::SettingItem(SettingItem {
4717                title: "Diagnostic Badges",
4718                description: "Show error and warning count badges next to file names in the project panel.",
4719                field: Box::new(SettingField {
4720                    json_path: Some("project_panel.diagnostic_badges"),
4721                    pick: |settings_content| {
4722                        settings_content
4723                            .project_panel
4724                            .as_ref()?
4725                            .diagnostic_badges
4726                            .as_ref()
4727                    },
4728                    write: |settings_content, value| {
4729                        settings_content
4730                            .project_panel
4731                            .get_or_insert_default()
4732                            .diagnostic_badges = value;
4733                    },
4734                }),
4735                metadata: None,
4736                files: USER,
4737            }),
4738            SettingsPageItem::SettingItem(SettingItem {
4739                title: "Git Status Indicator",
4740                description: "Show a git status indicator next to file names in the project panel.",
4741                field: Box::new(SettingField {
4742                    json_path: Some("project_panel.git_status_indicator"),
4743                    pick: |settings_content| {
4744                        settings_content
4745                            .project_panel
4746                            .as_ref()?
4747                            .git_status_indicator
4748                            .as_ref()
4749                    },
4750                    write: |settings_content, value| {
4751                        settings_content
4752                            .project_panel
4753                            .get_or_insert_default()
4754                            .git_status_indicator = value;
4755                    },
4756                }),
4757                metadata: None,
4758                files: USER,
4759            }),
4760            SettingsPageItem::SettingItem(SettingItem {
4761                title: "Sticky Scroll",
4762                description: "Whether to stick parent directories at top of the project panel.",
4763                field: Box::new(SettingField {
4764                    json_path: Some("project_panel.sticky_scroll"),
4765                    pick: |settings_content| {
4766                        settings_content
4767                            .project_panel
4768                            .as_ref()?
4769                            .sticky_scroll
4770                            .as_ref()
4771                    },
4772                    write: |settings_content, value| {
4773                        settings_content
4774                            .project_panel
4775                            .get_or_insert_default()
4776                            .sticky_scroll = value;
4777                    },
4778                }),
4779                metadata: None,
4780                files: USER,
4781            }),
4782            SettingsPageItem::SettingItem(SettingItem {
4783                files: USER,
4784                title: "Show Indent Guides",
4785                description: "Show indent guides in the project panel.",
4786                field: Box::new(SettingField {
4787                    json_path: Some("project_panel.indent_guides.show"),
4788                    pick: |settings_content| {
4789                        settings_content
4790                            .project_panel
4791                            .as_ref()?
4792                            .indent_guides
4793                            .as_ref()?
4794                            .show
4795                            .as_ref()
4796                    },
4797                    write: |settings_content, value| {
4798                        settings_content
4799                            .project_panel
4800                            .get_or_insert_default()
4801                            .indent_guides
4802                            .get_or_insert_default()
4803                            .show = value;
4804                    },
4805                }),
4806                metadata: None,
4807            }),
4808            SettingsPageItem::SettingItem(SettingItem {
4809                title: "Drag and Drop",
4810                description: "Whether to enable drag-and-drop operations in the project panel.",
4811                field: Box::new(SettingField {
4812                    json_path: Some("project_panel.drag_and_drop"),
4813                    pick: |settings_content| {
4814                        settings_content
4815                            .project_panel
4816                            .as_ref()?
4817                            .drag_and_drop
4818                            .as_ref()
4819                    },
4820                    write: |settings_content, value| {
4821                        settings_content
4822                            .project_panel
4823                            .get_or_insert_default()
4824                            .drag_and_drop = value;
4825                    },
4826                }),
4827                metadata: None,
4828                files: USER,
4829            }),
4830            SettingsPageItem::SettingItem(SettingItem {
4831                title: "Hide Root",
4832                description: "Whether to hide the root entry when only one folder is open in the window.",
4833                field: Box::new(SettingField {
4834                    json_path: Some("project_panel.hide_root"),
4835                    pick: |settings_content| {
4836                        settings_content.project_panel.as_ref()?.hide_root.as_ref()
4837                    },
4838                    write: |settings_content, value| {
4839                        settings_content
4840                            .project_panel
4841                            .get_or_insert_default()
4842                            .hide_root = value;
4843                    },
4844                }),
4845                metadata: None,
4846                files: USER,
4847            }),
4848            SettingsPageItem::SettingItem(SettingItem {
4849                title: "Hide Hidden",
4850                description: "Whether to hide the hidden entries in the project panel.",
4851                field: Box::new(SettingField {
4852                    json_path: Some("project_panel.hide_hidden"),
4853                    pick: |settings_content| {
4854                        settings_content
4855                            .project_panel
4856                            .as_ref()?
4857                            .hide_hidden
4858                            .as_ref()
4859                    },
4860                    write: |settings_content, value| {
4861                        settings_content
4862                            .project_panel
4863                            .get_or_insert_default()
4864                            .hide_hidden = value;
4865                    },
4866                }),
4867                metadata: None,
4868                files: USER,
4869            }),
4870            SettingsPageItem::SettingItem(SettingItem {
4871                title: "Hidden Files",
4872                description: "Globs to match files that will be considered \"hidden\" and can be hidden from the project panel.",
4873                field: Box::new(
4874                    SettingField {
4875                        json_path: Some("worktree.hidden_files"),
4876                        pick: |settings_content| {
4877                            settings_content.project.worktree.hidden_files.as_ref()
4878                        },
4879                        write: |settings_content, value| {
4880                            settings_content.project.worktree.hidden_files = value;
4881                        },
4882                    }
4883                    .unimplemented(),
4884                ),
4885                metadata: None,
4886                files: USER,
4887            }),
4888        ]
4889    }
4890
4891    fn auto_open_files_section() -> [SettingsPageItem; 5] {
4892        [
4893            SettingsPageItem::SectionHeader("Auto Open Files"),
4894            SettingsPageItem::SettingItem(SettingItem {
4895                title: "On Create",
4896                description: "Whether to automatically open newly created files in the editor.",
4897                field: Box::new(SettingField {
4898                    json_path: Some("project_panel.auto_open.on_create"),
4899                    pick: |settings_content| {
4900                        settings_content
4901                            .project_panel
4902                            .as_ref()?
4903                            .auto_open
4904                            .as_ref()?
4905                            .on_create
4906                            .as_ref()
4907                    },
4908                    write: |settings_content, value| {
4909                        settings_content
4910                            .project_panel
4911                            .get_or_insert_default()
4912                            .auto_open
4913                            .get_or_insert_default()
4914                            .on_create = value;
4915                    },
4916                }),
4917                metadata: None,
4918                files: USER,
4919            }),
4920            SettingsPageItem::SettingItem(SettingItem {
4921                title: "On Paste",
4922                description: "Whether to automatically open files after pasting or duplicating them.",
4923                field: Box::new(SettingField {
4924                    json_path: Some("project_panel.auto_open.on_paste"),
4925                    pick: |settings_content| {
4926                        settings_content
4927                            .project_panel
4928                            .as_ref()?
4929                            .auto_open
4930                            .as_ref()?
4931                            .on_paste
4932                            .as_ref()
4933                    },
4934                    write: |settings_content, value| {
4935                        settings_content
4936                            .project_panel
4937                            .get_or_insert_default()
4938                            .auto_open
4939                            .get_or_insert_default()
4940                            .on_paste = value;
4941                    },
4942                }),
4943                metadata: None,
4944                files: USER,
4945            }),
4946            SettingsPageItem::SettingItem(SettingItem {
4947                title: "On Drop",
4948                description: "Whether to automatically open files dropped from external sources.",
4949                field: Box::new(SettingField {
4950                    json_path: Some("project_panel.auto_open.on_drop"),
4951                    pick: |settings_content| {
4952                        settings_content
4953                            .project_panel
4954                            .as_ref()?
4955                            .auto_open
4956                            .as_ref()?
4957                            .on_drop
4958                            .as_ref()
4959                    },
4960                    write: |settings_content, value| {
4961                        settings_content
4962                            .project_panel
4963                            .get_or_insert_default()
4964                            .auto_open
4965                            .get_or_insert_default()
4966                            .on_drop = value;
4967                    },
4968                }),
4969                metadata: None,
4970                files: USER,
4971            }),
4972            SettingsPageItem::SettingItem(SettingItem {
4973                title: "Sort Mode",
4974                description: "Sort order for entries in the project panel.",
4975                field: Box::new(SettingField {
4976                    pick: |settings_content| {
4977                        settings_content.project_panel.as_ref()?.sort_mode.as_ref()
4978                    },
4979                    write: |settings_content, value| {
4980                        settings_content
4981                            .project_panel
4982                            .get_or_insert_default()
4983                            .sort_mode = value;
4984                    },
4985                    json_path: Some("project_panel.sort_mode"),
4986                }),
4987                metadata: None,
4988                files: USER,
4989            }),
4990        ]
4991    }
4992
4993    fn terminal_panel_section() -> [SettingsPageItem; 3] {
4994        [
4995            SettingsPageItem::SectionHeader("Terminal Panel"),
4996            SettingsPageItem::SettingItem(SettingItem {
4997                title: "Terminal Dock",
4998                description: "Where to dock the terminal panel.",
4999                field: Box::new(SettingField {
5000                    json_path: Some("terminal.dock"),
5001                    pick: |settings_content| settings_content.terminal.as_ref()?.dock.as_ref(),
5002                    write: |settings_content, value| {
5003                        settings_content.terminal.get_or_insert_default().dock = value;
5004                    },
5005                }),
5006                metadata: None,
5007                files: USER,
5008            }),
5009            SettingsPageItem::SettingItem(SettingItem {
5010                title: "Show Count Badge",
5011                description: "Show a badge on the terminal panel icon with the count of open terminals.",
5012                field: Box::new(SettingField {
5013                    json_path: Some("terminal.show_count_badge"),
5014                    pick: |settings_content| {
5015                        settings_content
5016                            .terminal
5017                            .as_ref()?
5018                            .show_count_badge
5019                            .as_ref()
5020                    },
5021                    write: |settings_content, value| {
5022                        settings_content
5023                            .terminal
5024                            .get_or_insert_default()
5025                            .show_count_badge = value;
5026                    },
5027                }),
5028                metadata: None,
5029                files: USER,
5030            }),
5031        ]
5032    }
5033
5034    fn outline_panel_section() -> [SettingsPageItem; 11] {
5035        [
5036            SettingsPageItem::SectionHeader("Outline Panel"),
5037            SettingsPageItem::SettingItem(SettingItem {
5038                title: "Outline Panel Button",
5039                description: "Show the outline panel button in the status bar.",
5040                field: Box::new(SettingField {
5041                    json_path: Some("outline_panel.button"),
5042                    pick: |settings_content| {
5043                        settings_content.outline_panel.as_ref()?.button.as_ref()
5044                    },
5045                    write: |settings_content, value| {
5046                        settings_content
5047                            .outline_panel
5048                            .get_or_insert_default()
5049                            .button = value;
5050                    },
5051                }),
5052                metadata: None,
5053                files: USER,
5054            }),
5055            SettingsPageItem::SettingItem(SettingItem {
5056                title: "Outline Panel Dock",
5057                description: "Where to dock the outline panel.",
5058                field: Box::new(SettingField {
5059                    json_path: Some("outline_panel.dock"),
5060                    pick: |settings_content| settings_content.outline_panel.as_ref()?.dock.as_ref(),
5061                    write: |settings_content, value| {
5062                        settings_content.outline_panel.get_or_insert_default().dock = value;
5063                    },
5064                }),
5065                metadata: None,
5066                files: USER,
5067            }),
5068            SettingsPageItem::SettingItem(SettingItem {
5069                title: "Outline Panel Default Width",
5070                description: "Default width of the outline panel in pixels.",
5071                field: Box::new(SettingField {
5072                    json_path: Some("outline_panel.default_width"),
5073                    pick: |settings_content| {
5074                        settings_content
5075                            .outline_panel
5076                            .as_ref()?
5077                            .default_width
5078                            .as_ref()
5079                    },
5080                    write: |settings_content, value| {
5081                        settings_content
5082                            .outline_panel
5083                            .get_or_insert_default()
5084                            .default_width = value;
5085                    },
5086                }),
5087                metadata: None,
5088                files: USER,
5089            }),
5090            SettingsPageItem::SettingItem(SettingItem {
5091                title: "File Icons",
5092                description: "Show file icons in the outline panel.",
5093                field: Box::new(SettingField {
5094                    json_path: Some("outline_panel.file_icons"),
5095                    pick: |settings_content| {
5096                        settings_content.outline_panel.as_ref()?.file_icons.as_ref()
5097                    },
5098                    write: |settings_content, value| {
5099                        settings_content
5100                            .outline_panel
5101                            .get_or_insert_default()
5102                            .file_icons = value;
5103                    },
5104                }),
5105                metadata: None,
5106                files: USER,
5107            }),
5108            SettingsPageItem::SettingItem(SettingItem {
5109                title: "Folder Icons",
5110                description: "Whether to show folder icons or chevrons for directories in the outline panel.",
5111                field: Box::new(SettingField {
5112                    json_path: Some("outline_panel.folder_icons"),
5113                    pick: |settings_content| {
5114                        settings_content
5115                            .outline_panel
5116                            .as_ref()?
5117                            .folder_icons
5118                            .as_ref()
5119                    },
5120                    write: |settings_content, value| {
5121                        settings_content
5122                            .outline_panel
5123                            .get_or_insert_default()
5124                            .folder_icons = value;
5125                    },
5126                }),
5127                metadata: None,
5128                files: USER,
5129            }),
5130            SettingsPageItem::SettingItem(SettingItem {
5131                title: "Git Status",
5132                description: "Show the Git status in the outline panel.",
5133                field: Box::new(SettingField {
5134                    json_path: Some("outline_panel.git_status"),
5135                    pick: |settings_content| {
5136                        settings_content.outline_panel.as_ref()?.git_status.as_ref()
5137                    },
5138                    write: |settings_content, value| {
5139                        settings_content
5140                            .outline_panel
5141                            .get_or_insert_default()
5142                            .git_status = value;
5143                    },
5144                }),
5145                metadata: None,
5146                files: USER,
5147            }),
5148            SettingsPageItem::SettingItem(SettingItem {
5149                title: "Indent Size",
5150                description: "Amount of indentation for nested items.",
5151                field: Box::new(SettingField {
5152                    json_path: Some("outline_panel.indent_size"),
5153                    pick: |settings_content| {
5154                        settings_content
5155                            .outline_panel
5156                            .as_ref()?
5157                            .indent_size
5158                            .as_ref()
5159                    },
5160                    write: |settings_content, value| {
5161                        settings_content
5162                            .outline_panel
5163                            .get_or_insert_default()
5164                            .indent_size = value;
5165                    },
5166                }),
5167                metadata: None,
5168                files: USER,
5169            }),
5170            SettingsPageItem::SettingItem(SettingItem {
5171                title: "Auto Reveal Entries",
5172                description: "Whether to reveal when a corresponding outline entry becomes active.",
5173                field: Box::new(SettingField {
5174                    json_path: Some("outline_panel.auto_reveal_entries"),
5175                    pick: |settings_content| {
5176                        settings_content
5177                            .outline_panel
5178                            .as_ref()?
5179                            .auto_reveal_entries
5180                            .as_ref()
5181                    },
5182                    write: |settings_content, value| {
5183                        settings_content
5184                            .outline_panel
5185                            .get_or_insert_default()
5186                            .auto_reveal_entries = value;
5187                    },
5188                }),
5189                metadata: None,
5190                files: USER,
5191            }),
5192            SettingsPageItem::SettingItem(SettingItem {
5193                title: "Auto Fold Directories",
5194                description: "Whether to fold directories automatically when a directory contains only one subdirectory.",
5195                field: Box::new(SettingField {
5196                    json_path: Some("outline_panel.auto_fold_dirs"),
5197                    pick: |settings_content| {
5198                        settings_content
5199                            .outline_panel
5200                            .as_ref()?
5201                            .auto_fold_dirs
5202                            .as_ref()
5203                    },
5204                    write: |settings_content, value| {
5205                        settings_content
5206                            .outline_panel
5207                            .get_or_insert_default()
5208                            .auto_fold_dirs = value;
5209                    },
5210                }),
5211                metadata: None,
5212                files: USER,
5213            }),
5214            SettingsPageItem::SettingItem(SettingItem {
5215                files: USER,
5216                title: "Show Indent Guides",
5217                description: "When to show indent guides in the outline panel.",
5218                field: Box::new(SettingField {
5219                    json_path: Some("outline_panel.indent_guides.show"),
5220                    pick: |settings_content| {
5221                        settings_content
5222                            .outline_panel
5223                            .as_ref()?
5224                            .indent_guides
5225                            .as_ref()?
5226                            .show
5227                            .as_ref()
5228                    },
5229                    write: |settings_content, value| {
5230                        settings_content
5231                            .outline_panel
5232                            .get_or_insert_default()
5233                            .indent_guides
5234                            .get_or_insert_default()
5235                            .show = value;
5236                    },
5237                }),
5238                metadata: None,
5239            }),
5240        ]
5241    }
5242
5243    fn git_panel_section() -> [SettingsPageItem; 14] {
5244        [
5245            SettingsPageItem::SectionHeader("Git Panel"),
5246            SettingsPageItem::SettingItem(SettingItem {
5247                title: "Git Panel Button",
5248                description: "Show the Git panel button in the status bar.",
5249                field: Box::new(SettingField {
5250                    json_path: Some("git_panel.button"),
5251                    pick: |settings_content| settings_content.git_panel.as_ref()?.button.as_ref(),
5252                    write: |settings_content, value| {
5253                        settings_content.git_panel.get_or_insert_default().button = value;
5254                    },
5255                }),
5256                metadata: None,
5257                files: USER,
5258            }),
5259            SettingsPageItem::SettingItem(SettingItem {
5260                title: "Git Panel Dock",
5261                description: "Where to dock the Git panel.",
5262                field: Box::new(SettingField {
5263                    json_path: Some("git_panel.dock"),
5264                    pick: |settings_content| settings_content.git_panel.as_ref()?.dock.as_ref(),
5265                    write: |settings_content, value| {
5266                        settings_content.git_panel.get_or_insert_default().dock = value;
5267                    },
5268                }),
5269                metadata: None,
5270                files: USER,
5271            }),
5272            SettingsPageItem::SettingItem(SettingItem {
5273                title: "Git Panel Default Width",
5274                description: "Default width of the Git panel in pixels.",
5275                field: Box::new(SettingField {
5276                    json_path: Some("git_panel.default_width"),
5277                    pick: |settings_content| {
5278                        settings_content.git_panel.as_ref()?.default_width.as_ref()
5279                    },
5280                    write: |settings_content, value| {
5281                        settings_content
5282                            .git_panel
5283                            .get_or_insert_default()
5284                            .default_width = value;
5285                    },
5286                }),
5287                metadata: None,
5288                files: USER,
5289            }),
5290            SettingsPageItem::SettingItem(SettingItem {
5291                title: "Git Panel Status Style",
5292                description: "How entry statuses are displayed.",
5293                field: Box::new(SettingField {
5294                    json_path: Some("git_panel.status_style"),
5295                    pick: |settings_content| {
5296                        settings_content.git_panel.as_ref()?.status_style.as_ref()
5297                    },
5298                    write: |settings_content, value| {
5299                        settings_content
5300                            .git_panel
5301                            .get_or_insert_default()
5302                            .status_style = value;
5303                    },
5304                }),
5305                metadata: None,
5306                files: USER,
5307            }),
5308            SettingsPageItem::SettingItem(SettingItem {
5309                title: "Fallback Branch Name",
5310                description: "Default branch name will be when init.defaultbranch is not set in Git.",
5311                field: Box::new(SettingField {
5312                    json_path: Some("git_panel.fallback_branch_name"),
5313                    pick: |settings_content| {
5314                        settings_content
5315                            .git_panel
5316                            .as_ref()?
5317                            .fallback_branch_name
5318                            .as_ref()
5319                    },
5320                    write: |settings_content, value| {
5321                        settings_content
5322                            .git_panel
5323                            .get_or_insert_default()
5324                            .fallback_branch_name = value;
5325                    },
5326                }),
5327                metadata: None,
5328                files: USER,
5329            }),
5330            SettingsPageItem::SettingItem(SettingItem {
5331                title: "Sort By Path",
5332                description: "Enable to sort entries in the panel by path, disable to sort by status.",
5333                field: Box::new(SettingField {
5334                    json_path: Some("git_panel.sort_by_path"),
5335                    pick: |settings_content| {
5336                        settings_content.git_panel.as_ref()?.sort_by_path.as_ref()
5337                    },
5338                    write: |settings_content, value| {
5339                        settings_content
5340                            .git_panel
5341                            .get_or_insert_default()
5342                            .sort_by_path = value;
5343                    },
5344                }),
5345                metadata: None,
5346                files: USER,
5347            }),
5348            SettingsPageItem::SettingItem(SettingItem {
5349                title: "Collapse Untracked Diff",
5350                description: "Whether to collapse untracked files in the diff panel.",
5351                field: Box::new(SettingField {
5352                    json_path: Some("git_panel.collapse_untracked_diff"),
5353                    pick: |settings_content| {
5354                        settings_content
5355                            .git_panel
5356                            .as_ref()?
5357                            .collapse_untracked_diff
5358                            .as_ref()
5359                    },
5360                    write: |settings_content, value| {
5361                        settings_content
5362                            .git_panel
5363                            .get_or_insert_default()
5364                            .collapse_untracked_diff = value;
5365                    },
5366                }),
5367                metadata: None,
5368                files: USER,
5369            }),
5370            SettingsPageItem::SettingItem(SettingItem {
5371                title: "Tree View",
5372                description: "Enable to show entries in tree view list, disable to show in flat view list.",
5373                field: Box::new(SettingField {
5374                    json_path: Some("git_panel.tree_view"),
5375                    pick: |settings_content| {
5376                        settings_content.git_panel.as_ref()?.tree_view.as_ref()
5377                    },
5378                    write: |settings_content, value| {
5379                        settings_content.git_panel.get_or_insert_default().tree_view = value;
5380                    },
5381                }),
5382                metadata: None,
5383                files: USER,
5384            }),
5385            SettingsPageItem::SettingItem(SettingItem {
5386                title: "File Icons",
5387                description: "Show file icons next to the Git status icon.",
5388                field: Box::new(SettingField {
5389                    json_path: Some("git_panel.file_icons"),
5390                    pick: |settings_content| {
5391                        settings_content.git_panel.as_ref()?.file_icons.as_ref()
5392                    },
5393                    write: |settings_content, value| {
5394                        settings_content
5395                            .git_panel
5396                            .get_or_insert_default()
5397                            .file_icons = value;
5398                    },
5399                }),
5400                metadata: None,
5401                files: USER,
5402            }),
5403            SettingsPageItem::SettingItem(SettingItem {
5404                title: "Folder Icons",
5405                description: "Whether to show folder icons or chevrons for directories in the git panel.",
5406                field: Box::new(SettingField {
5407                    json_path: Some("git_panel.folder_icons"),
5408                    pick: |settings_content| {
5409                        settings_content.git_panel.as_ref()?.folder_icons.as_ref()
5410                    },
5411                    write: |settings_content, value| {
5412                        settings_content
5413                            .git_panel
5414                            .get_or_insert_default()
5415                            .folder_icons = value;
5416                    },
5417                }),
5418                metadata: None,
5419                files: USER,
5420            }),
5421            SettingsPageItem::SettingItem(SettingItem {
5422                title: "Diff Stats",
5423                description: "Whether to show the addition/deletion change count next to each file in the Git panel.",
5424                field: Box::new(SettingField {
5425                    json_path: Some("git_panel.diff_stats"),
5426                    pick: |settings_content| {
5427                        settings_content.git_panel.as_ref()?.diff_stats.as_ref()
5428                    },
5429                    write: |settings_content, value| {
5430                        settings_content
5431                            .git_panel
5432                            .get_or_insert_default()
5433                            .diff_stats = value;
5434                    },
5435                }),
5436                metadata: None,
5437                files: USER,
5438            }),
5439            SettingsPageItem::SettingItem(SettingItem {
5440                title: "Show Count Badge",
5441                description: "Whether to show a badge on the git panel icon with the count of uncommitted changes.",
5442                field: Box::new(SettingField {
5443                    json_path: Some("git_panel.show_count_badge"),
5444                    pick: |settings_content| {
5445                        settings_content
5446                            .git_panel
5447                            .as_ref()?
5448                            .show_count_badge
5449                            .as_ref()
5450                    },
5451                    write: |settings_content, value| {
5452                        settings_content
5453                            .git_panel
5454                            .get_or_insert_default()
5455                            .show_count_badge = value;
5456                    },
5457                }),
5458                metadata: None,
5459                files: USER,
5460            }),
5461            SettingsPageItem::SettingItem(SettingItem {
5462                title: "Scroll Bar",
5463                description: "How and when the scrollbar should be displayed.",
5464                field: Box::new(SettingField {
5465                    json_path: Some("git_panel.scrollbar.show"),
5466                    pick: |settings_content| {
5467                        show_scrollbar_or_editor(settings_content, |settings_content| {
5468                            settings_content
5469                                .git_panel
5470                                .as_ref()?
5471                                .scrollbar
5472                                .as_ref()?
5473                                .show
5474                                .as_ref()
5475                        })
5476                    },
5477                    write: |settings_content, value| {
5478                        settings_content
5479                            .git_panel
5480                            .get_or_insert_default()
5481                            .scrollbar
5482                            .get_or_insert_default()
5483                            .show = value;
5484                    },
5485                }),
5486                metadata: None,
5487                files: USER,
5488            }),
5489        ]
5490    }
5491
5492    fn debugger_panel_section() -> [SettingsPageItem; 2] {
5493        [
5494            SettingsPageItem::SectionHeader("Debugger Panel"),
5495            SettingsPageItem::SettingItem(SettingItem {
5496                title: "Debugger Panel Dock",
5497                description: "The dock position of the debug panel.",
5498                field: Box::new(SettingField {
5499                    json_path: Some("debugger.dock"),
5500                    pick: |settings_content| settings_content.debugger.as_ref()?.dock.as_ref(),
5501                    write: |settings_content, value| {
5502                        settings_content.debugger.get_or_insert_default().dock = value;
5503                    },
5504                }),
5505                metadata: None,
5506                files: USER,
5507            }),
5508        ]
5509    }
5510
5511    fn notification_panel_section() -> [SettingsPageItem; 5] {
5512        [
5513            SettingsPageItem::SectionHeader("Notification Panel"),
5514            SettingsPageItem::SettingItem(SettingItem {
5515                title: "Notification Panel Button",
5516                description: "Show the notification panel button in the status bar.",
5517                field: Box::new(SettingField {
5518                    json_path: Some("notification_panel.button"),
5519                    pick: |settings_content| {
5520                        settings_content
5521                            .notification_panel
5522                            .as_ref()?
5523                            .button
5524                            .as_ref()
5525                    },
5526                    write: |settings_content, value| {
5527                        settings_content
5528                            .notification_panel
5529                            .get_or_insert_default()
5530                            .button = value;
5531                    },
5532                }),
5533                metadata: None,
5534                files: USER,
5535            }),
5536            SettingsPageItem::SettingItem(SettingItem {
5537                title: "Notification Panel Dock",
5538                description: "Where to dock the notification panel.",
5539                field: Box::new(SettingField {
5540                    json_path: Some("notification_panel.dock"),
5541                    pick: |settings_content| {
5542                        settings_content.notification_panel.as_ref()?.dock.as_ref()
5543                    },
5544                    write: |settings_content, value| {
5545                        settings_content
5546                            .notification_panel
5547                            .get_or_insert_default()
5548                            .dock = value;
5549                    },
5550                }),
5551                metadata: None,
5552                files: USER,
5553            }),
5554            SettingsPageItem::SettingItem(SettingItem {
5555                title: "Notification Panel Default Width",
5556                description: "Default width of the notification panel in pixels.",
5557                field: Box::new(SettingField {
5558                    json_path: Some("notification_panel.default_width"),
5559                    pick: |settings_content| {
5560                        settings_content
5561                            .notification_panel
5562                            .as_ref()?
5563                            .default_width
5564                            .as_ref()
5565                    },
5566                    write: |settings_content, value| {
5567                        settings_content
5568                            .notification_panel
5569                            .get_or_insert_default()
5570                            .default_width = value;
5571                    },
5572                }),
5573                metadata: None,
5574                files: USER,
5575            }),
5576            SettingsPageItem::SettingItem(SettingItem {
5577                title: "Show Count Badge",
5578                description: "Show a badge on the notification panel icon with the count of unread notifications.",
5579                field: Box::new(SettingField {
5580                    json_path: Some("notification_panel.show_count_badge"),
5581                    pick: |settings_content| {
5582                        settings_content
5583                            .notification_panel
5584                            .as_ref()?
5585                            .show_count_badge
5586                            .as_ref()
5587                    },
5588                    write: |settings_content, value| {
5589                        settings_content
5590                            .notification_panel
5591                            .get_or_insert_default()
5592                            .show_count_badge = value;
5593                    },
5594                }),
5595                metadata: None,
5596                files: USER,
5597            }),
5598        ]
5599    }
5600
5601    fn collaboration_panel_section() -> [SettingsPageItem; 4] {
5602        [
5603            SettingsPageItem::SectionHeader("Collaboration Panel"),
5604            SettingsPageItem::SettingItem(SettingItem {
5605                title: "Collaboration Panel Button",
5606                description: "Show the collaboration panel button in the status bar.",
5607                field: Box::new(SettingField {
5608                    json_path: Some("collaboration_panel.button"),
5609                    pick: |settings_content| {
5610                        settings_content
5611                            .collaboration_panel
5612                            .as_ref()?
5613                            .button
5614                            .as_ref()
5615                    },
5616                    write: |settings_content, value| {
5617                        settings_content
5618                            .collaboration_panel
5619                            .get_or_insert_default()
5620                            .button = value;
5621                    },
5622                }),
5623                metadata: None,
5624                files: USER,
5625            }),
5626            SettingsPageItem::SettingItem(SettingItem {
5627                title: "Collaboration Panel Dock",
5628                description: "Where to dock the collaboration panel.",
5629                field: Box::new(SettingField {
5630                    json_path: Some("collaboration_panel.dock"),
5631                    pick: |settings_content| {
5632                        settings_content.collaboration_panel.as_ref()?.dock.as_ref()
5633                    },
5634                    write: |settings_content, value| {
5635                        settings_content
5636                            .collaboration_panel
5637                            .get_or_insert_default()
5638                            .dock = value;
5639                    },
5640                }),
5641                metadata: None,
5642                files: USER,
5643            }),
5644            SettingsPageItem::SettingItem(SettingItem {
5645                title: "Collaboration Panel Default Width",
5646                description: "Default width of the collaboration panel in pixels.",
5647                field: Box::new(SettingField {
5648                    json_path: Some("collaboration_panel.dock"),
5649                    pick: |settings_content| {
5650                        settings_content
5651                            .collaboration_panel
5652                            .as_ref()?
5653                            .default_width
5654                            .as_ref()
5655                    },
5656                    write: |settings_content, value| {
5657                        settings_content
5658                            .collaboration_panel
5659                            .get_or_insert_default()
5660                            .default_width = value;
5661                    },
5662                }),
5663                metadata: None,
5664                files: USER,
5665            }),
5666        ]
5667    }
5668
5669    fn agent_panel_section() -> [SettingsPageItem; 5] {
5670        [
5671            SettingsPageItem::SectionHeader("Agent Panel"),
5672            SettingsPageItem::SettingItem(SettingItem {
5673                title: "Agent Panel Button",
5674                description: "Whether to show the agent panel button in the status bar.",
5675                field: Box::new(SettingField {
5676                    json_path: Some("agent.button"),
5677                    pick: |settings_content| settings_content.agent.as_ref()?.button.as_ref(),
5678                    write: |settings_content, value| {
5679                        settings_content.agent.get_or_insert_default().button = value;
5680                    },
5681                }),
5682                metadata: None,
5683                files: USER,
5684            }),
5685            SettingsPageItem::SettingItem(SettingItem {
5686                title: "Agent Panel Dock",
5687                description: "Where to dock the agent panel.",
5688                field: Box::new(SettingField {
5689                    json_path: Some("agent.dock"),
5690                    pick: |settings_content| settings_content.agent.as_ref()?.dock.as_ref(),
5691                    write: |settings_content, value| {
5692                        settings_content.agent.get_or_insert_default().dock = value;
5693                    },
5694                }),
5695                metadata: None,
5696                files: USER,
5697            }),
5698            SettingsPageItem::SettingItem(SettingItem {
5699                title: "Agent Panel Default Width",
5700                description: "Default width when the agent panel is docked to the left or right.",
5701                field: Box::new(SettingField {
5702                    json_path: Some("agent.default_width"),
5703                    pick: |settings_content| {
5704                        settings_content.agent.as_ref()?.default_width.as_ref()
5705                    },
5706                    write: |settings_content, value| {
5707                        settings_content.agent.get_or_insert_default().default_width = value;
5708                    },
5709                }),
5710                metadata: None,
5711                files: USER,
5712            }),
5713            SettingsPageItem::SettingItem(SettingItem {
5714                title: "Agent Panel Default Height",
5715                description: "Default height when the agent panel is docked to the bottom.",
5716                field: Box::new(SettingField {
5717                    json_path: Some("agent.default_height"),
5718                    pick: |settings_content| {
5719                        settings_content.agent.as_ref()?.default_height.as_ref()
5720                    },
5721                    write: |settings_content, value| {
5722                        settings_content
5723                            .agent
5724                            .get_or_insert_default()
5725                            .default_height = value;
5726                    },
5727                }),
5728                metadata: None,
5729                files: USER,
5730            }),
5731        ]
5732    }
5733
5734    SettingsPage {
5735        title: "Panels",
5736        items: concat_sections![
5737            project_panel_section(),
5738            auto_open_files_section(),
5739            terminal_panel_section(),
5740            outline_panel_section(),
5741            git_panel_section(),
5742            debugger_panel_section(),
5743            notification_panel_section(),
5744            collaboration_panel_section(),
5745            agent_panel_section(),
5746        ],
5747    }
5748}
5749
5750fn debugger_page() -> SettingsPage {
5751    fn general_section() -> [SettingsPageItem; 6] {
5752        [
5753            SettingsPageItem::SectionHeader("General"),
5754            SettingsPageItem::SettingItem(SettingItem {
5755                title: "Stepping Granularity",
5756                description: "Determines the stepping granularity for debug operations.",
5757                field: Box::new(SettingField {
5758                    json_path: Some("debugger.stepping_granularity"),
5759                    pick: |settings_content| {
5760                        settings_content
5761                            .debugger
5762                            .as_ref()?
5763                            .stepping_granularity
5764                            .as_ref()
5765                    },
5766                    write: |settings_content, value| {
5767                        settings_content
5768                            .debugger
5769                            .get_or_insert_default()
5770                            .stepping_granularity = value;
5771                    },
5772                }),
5773                metadata: None,
5774                files: USER,
5775            }),
5776            SettingsPageItem::SettingItem(SettingItem {
5777                title: "Save Breakpoints",
5778                description: "Whether breakpoints should be reused across Zed sessions.",
5779                field: Box::new(SettingField {
5780                    json_path: Some("debugger.save_breakpoints"),
5781                    pick: |settings_content| {
5782                        settings_content
5783                            .debugger
5784                            .as_ref()?
5785                            .save_breakpoints
5786                            .as_ref()
5787                    },
5788                    write: |settings_content, value| {
5789                        settings_content
5790                            .debugger
5791                            .get_or_insert_default()
5792                            .save_breakpoints = value;
5793                    },
5794                }),
5795                metadata: None,
5796                files: USER,
5797            }),
5798            SettingsPageItem::SettingItem(SettingItem {
5799                title: "Timeout",
5800                description: "Time in milliseconds until timeout error when connecting to a TCP debug adapter.",
5801                field: Box::new(SettingField {
5802                    json_path: Some("debugger.timeout"),
5803                    pick: |settings_content| settings_content.debugger.as_ref()?.timeout.as_ref(),
5804                    write: |settings_content, value| {
5805                        settings_content.debugger.get_or_insert_default().timeout = value;
5806                    },
5807                }),
5808                metadata: None,
5809                files: USER,
5810            }),
5811            SettingsPageItem::SettingItem(SettingItem {
5812                title: "Log DAP Communications",
5813                description: "Whether to log messages between active debug adapters and Zed.",
5814                field: Box::new(SettingField {
5815                    json_path: Some("debugger.log_dap_communications"),
5816                    pick: |settings_content| {
5817                        settings_content
5818                            .debugger
5819                            .as_ref()?
5820                            .log_dap_communications
5821                            .as_ref()
5822                    },
5823                    write: |settings_content, value| {
5824                        settings_content
5825                            .debugger
5826                            .get_or_insert_default()
5827                            .log_dap_communications = value;
5828                    },
5829                }),
5830                metadata: None,
5831                files: USER,
5832            }),
5833            SettingsPageItem::SettingItem(SettingItem {
5834                title: "Format DAP Log Messages",
5835                description: "Whether to format DAP messages when adding them to debug adapter logger.",
5836                field: Box::new(SettingField {
5837                    json_path: Some("debugger.format_dap_log_messages"),
5838                    pick: |settings_content| {
5839                        settings_content
5840                            .debugger
5841                            .as_ref()?
5842                            .format_dap_log_messages
5843                            .as_ref()
5844                    },
5845                    write: |settings_content, value| {
5846                        settings_content
5847                            .debugger
5848                            .get_or_insert_default()
5849                            .format_dap_log_messages = value;
5850                    },
5851                }),
5852                metadata: None,
5853                files: USER,
5854            }),
5855        ]
5856    }
5857
5858    SettingsPage {
5859        title: "Debugger",
5860        items: concat_sections![general_section()],
5861    }
5862}
5863
5864fn terminal_page() -> SettingsPage {
5865    fn environment_section() -> [SettingsPageItem; 5] {
5866        [
5867                SettingsPageItem::SectionHeader("Environment"),
5868                SettingsPageItem::DynamicItem(DynamicItem {
5869                    discriminant: SettingItem {
5870                        files: USER | PROJECT,
5871                        title: "Shell",
5872                        description: "What shell to use when opening a terminal.",
5873                        field: Box::new(SettingField {
5874                            json_path: Some("terminal.shell$"),
5875                            pick: |settings_content| {
5876                                Some(&dynamic_variants::<settings::Shell>()[
5877                                    settings_content
5878                                        .terminal
5879                                        .as_ref()?
5880                                        .project
5881                                        .shell
5882                                        .as_ref()?
5883                                        .discriminant() as usize
5884                                ])
5885                            },
5886                            write: |settings_content, value| {
5887                                let Some(value) = value else {
5888                                    if let Some(terminal) = settings_content.terminal.as_mut() {
5889                                        terminal.project.shell = None;
5890                                    }
5891                                    return;
5892                                };
5893                                let settings_value = settings_content
5894                                    .terminal
5895                                    .get_or_insert_default()
5896                                    .project
5897                                    .shell
5898                                    .get_or_insert_with(|| settings::Shell::default());
5899                                let default_shell = if cfg!(target_os = "windows") {
5900                                    "powershell.exe"
5901                                } else {
5902                                    "sh"
5903                                };
5904                                *settings_value = match value {
5905                                    settings::ShellDiscriminants::System => settings::Shell::System,
5906                                    settings::ShellDiscriminants::Program => {
5907                                        let program = match settings_value {
5908                                            settings::Shell::Program(program) => program.clone(),
5909                                            settings::Shell::WithArguments { program, .. } => program.clone(),
5910                                            _ => String::from(default_shell),
5911                                        };
5912                                        settings::Shell::Program(program)
5913                                    }
5914                                    settings::ShellDiscriminants::WithArguments => {
5915                                        let (program, args, title_override) = match settings_value {
5916                                            settings::Shell::Program(program) => (program.clone(), vec![], None),
5917                                            settings::Shell::WithArguments {
5918                                                program,
5919                                                args,
5920                                                title_override,
5921                                            } => (program.clone(), args.clone(), title_override.clone()),
5922                                            _ => (String::from(default_shell), vec![], None),
5923                                        };
5924                                        settings::Shell::WithArguments {
5925                                            program,
5926                                            args,
5927                                            title_override,
5928                                        }
5929                                    }
5930                                };
5931                            },
5932                        }),
5933                        metadata: None,
5934                    },
5935                    pick_discriminant: |settings_content| {
5936                        Some(
5937                            settings_content
5938                                .terminal
5939                                .as_ref()?
5940                                .project
5941                                .shell
5942                                .as_ref()?
5943                                .discriminant() as usize,
5944                        )
5945                    },
5946                    fields: dynamic_variants::<settings::Shell>()
5947                        .into_iter()
5948                        .map(|variant| match variant {
5949                            settings::ShellDiscriminants::System => vec![],
5950                            settings::ShellDiscriminants::Program => vec![SettingItem {
5951                                files: USER | PROJECT,
5952                                title: "Program",
5953                                description: "The shell program to use.",
5954                                field: Box::new(SettingField {
5955                                    json_path: Some("terminal.shell"),
5956                                    pick: |settings_content| match settings_content.terminal.as_ref()?.project.shell.as_ref()
5957                                    {
5958                                        Some(settings::Shell::Program(program)) => Some(program),
5959                                        _ => None,
5960                                    },
5961                                    write: |settings_content, value| {
5962                                        let Some(value) = value else {
5963                                            return;
5964                                        };
5965                                        match settings_content
5966                                            .terminal
5967                                            .get_or_insert_default()
5968                                            .project
5969                                            .shell
5970                                            .as_mut()
5971                                        {
5972                                            Some(settings::Shell::Program(program)) => *program = value,
5973                                            _ => return,
5974                                        }
5975                                    },
5976                                }),
5977                                metadata: None,
5978                            }],
5979                            settings::ShellDiscriminants::WithArguments => vec![
5980                                SettingItem {
5981                                    files: USER | PROJECT,
5982                                    title: "Program",
5983                                    description: "The shell program to run.",
5984                                    field: Box::new(SettingField {
5985                                        json_path: Some("terminal.shell.program"),
5986                                        pick: |settings_content| {
5987                                            match settings_content.terminal.as_ref()?.project.shell.as_ref() {
5988                                                Some(settings::Shell::WithArguments { program, .. }) => Some(program),
5989                                                _ => None,
5990                                            }
5991                                        },
5992                                        write: |settings_content, value| {
5993                                            let Some(value) = value else {
5994                                                return;
5995                                            };
5996                                            match settings_content
5997                                                .terminal
5998                                                .get_or_insert_default()
5999                                                .project
6000                                                .shell
6001                                                .as_mut()
6002                                            {
6003                                                Some(settings::Shell::WithArguments { program, .. }) => {
6004                                                    *program = value
6005                                                }
6006                                                _ => return,
6007                                            }
6008                                        },
6009                                    }),
6010                                    metadata: None,
6011                                },
6012                                SettingItem {
6013                                    files: USER | PROJECT,
6014                                    title: "Arguments",
6015                                    description: "The arguments to pass to the shell program.",
6016                                    field: Box::new(
6017                                        SettingField {
6018                                            json_path: Some("terminal.shell.args"),
6019                                            pick: |settings_content| {
6020                                                match settings_content.terminal.as_ref()?.project.shell.as_ref() {
6021                                                    Some(settings::Shell::WithArguments { args, .. }) => Some(args),
6022                                                    _ => None,
6023                                                }
6024                                            },
6025                                            write: |settings_content, value| {
6026                                                let Some(value) = value else {
6027                                                    return;
6028                                                };
6029                                                match settings_content
6030                                                    .terminal
6031                                                    .get_or_insert_default()
6032                                                    .project
6033                                                    .shell
6034                                                    .as_mut()
6035                                                {
6036                                                    Some(settings::Shell::WithArguments { args, .. }) => *args = value,
6037                                                    _ => return,
6038                                                }
6039                                            },
6040                                        }
6041                                        .unimplemented(),
6042                                    ),
6043                                    metadata: None,
6044                                },
6045                                SettingItem {
6046                                    files: USER | PROJECT,
6047                                    title: "Title Override",
6048                                    description: "An optional string to override the title of the terminal tab.",
6049                                    field: Box::new(SettingField {
6050                                        json_path: Some("terminal.shell.title_override"),
6051                                        pick: |settings_content| {
6052                                            match settings_content.terminal.as_ref()?.project.shell.as_ref() {
6053                                                Some(settings::Shell::WithArguments { title_override, .. }) => {
6054                                                    title_override.as_ref().or(DEFAULT_EMPTY_STRING)
6055                                                }
6056                                                _ => None,
6057                                            }
6058                                        },
6059                                        write: |settings_content, value| {
6060                                            match settings_content
6061                                                .terminal
6062                                                .get_or_insert_default()
6063                                                .project
6064                                                .shell
6065                                                .as_mut()
6066                                            {
6067                                                Some(settings::Shell::WithArguments { title_override, .. }) => {
6068                                                    *title_override = value.filter(|s| !s.is_empty())
6069                                                }
6070                                                _ => return,
6071                                            }
6072                                        },
6073                                    }),
6074                                    metadata: None,
6075                                },
6076                            ],
6077                        })
6078                        .collect(),
6079                }),
6080                SettingsPageItem::DynamicItem(DynamicItem {
6081                    discriminant: SettingItem {
6082                        files: USER | PROJECT,
6083                        title: "Working Directory",
6084                        description: "What working directory to use when launching the terminal.",
6085                        field: Box::new(SettingField {
6086                            json_path: Some("terminal.working_directory$"),
6087                            pick: |settings_content| {
6088                                Some(&dynamic_variants::<settings::WorkingDirectory>()[
6089                                    settings_content
6090                                        .terminal
6091                                        .as_ref()?
6092                                        .project
6093                                        .working_directory
6094                                        .as_ref()?
6095                                        .discriminant() as usize
6096                                ])
6097                            },
6098                            write: |settings_content, value| {
6099                                let Some(value) = value else {
6100                                    if let Some(terminal) = settings_content.terminal.as_mut() {
6101                                        terminal.project.working_directory = None;
6102                                    }
6103                                    return;
6104                                };
6105                                let settings_value = settings_content
6106                                    .terminal
6107                                    .get_or_insert_default()
6108                                    .project
6109                                    .working_directory
6110                                    .get_or_insert_with(|| settings::WorkingDirectory::CurrentProjectDirectory);
6111                                *settings_value = match value {
6112                                    settings::WorkingDirectoryDiscriminants::CurrentFileDirectory => {
6113                                        settings::WorkingDirectory::CurrentFileDirectory
6114                                    },
6115                                    settings::WorkingDirectoryDiscriminants::CurrentProjectDirectory => {
6116                                        settings::WorkingDirectory::CurrentProjectDirectory
6117                                    }
6118                                    settings::WorkingDirectoryDiscriminants::FirstProjectDirectory => {
6119                                        settings::WorkingDirectory::FirstProjectDirectory
6120                                    }
6121                                    settings::WorkingDirectoryDiscriminants::AlwaysHome => {
6122                                        settings::WorkingDirectory::AlwaysHome
6123                                    }
6124                                    settings::WorkingDirectoryDiscriminants::Always => {
6125                                        let directory = match settings_value {
6126                                            settings::WorkingDirectory::Always { .. } => return,
6127                                            _ => String::new(),
6128                                        };
6129                                        settings::WorkingDirectory::Always { directory }
6130                                    }
6131                                };
6132                            },
6133                        }),
6134                        metadata: None,
6135                    },
6136                    pick_discriminant: |settings_content| {
6137                        Some(
6138                            settings_content
6139                                .terminal
6140                                .as_ref()?
6141                                .project
6142                                .working_directory
6143                                .as_ref()?
6144                                .discriminant() as usize,
6145                        )
6146                    },
6147                    fields: dynamic_variants::<settings::WorkingDirectory>()
6148                        .into_iter()
6149                        .map(|variant| match variant {
6150                            settings::WorkingDirectoryDiscriminants::CurrentFileDirectory => vec![],
6151                            settings::WorkingDirectoryDiscriminants::CurrentProjectDirectory => vec![],
6152                            settings::WorkingDirectoryDiscriminants::FirstProjectDirectory => vec![],
6153                            settings::WorkingDirectoryDiscriminants::AlwaysHome => vec![],
6154                            settings::WorkingDirectoryDiscriminants::Always => vec![SettingItem {
6155                                files: USER | PROJECT,
6156                                title: "Directory",
6157                                description: "The directory path to use (will be shell expanded).",
6158                                field: Box::new(SettingField {
6159                                    json_path: Some("terminal.working_directory.always"),
6160                                    pick: |settings_content| {
6161                                        match settings_content.terminal.as_ref()?.project.working_directory.as_ref() {
6162                                            Some(settings::WorkingDirectory::Always { directory }) => Some(directory),
6163                                            _ => None,
6164                                        }
6165                                    },
6166                                    write: |settings_content, value| {
6167                                        let value = value.unwrap_or_default();
6168                                        match settings_content
6169                                            .terminal
6170                                            .get_or_insert_default()
6171                                            .project
6172                                            .working_directory
6173                                            .as_mut()
6174                                        {
6175                                            Some(settings::WorkingDirectory::Always { directory }) => *directory = value,
6176                                            _ => return,
6177                                        }
6178                                    },
6179                                }),
6180                                metadata: None,
6181                            }],
6182                        })
6183                        .collect(),
6184                }),
6185                SettingsPageItem::SettingItem(SettingItem {
6186                    title: "Environment Variables",
6187                    description: "Key-value pairs to add to the terminal's environment.",
6188                    field: Box::new(
6189                        SettingField {
6190                            json_path: Some("terminal.env"),
6191                            pick: |settings_content| settings_content.terminal.as_ref()?.project.env.as_ref(),
6192                            write: |settings_content, value| {
6193                                settings_content.terminal.get_or_insert_default().project.env = value;
6194                            },
6195                        }
6196                        .unimplemented(),
6197                    ),
6198                    metadata: None,
6199                    files: USER | PROJECT,
6200                }),
6201                SettingsPageItem::SettingItem(SettingItem {
6202                    title: "Detect Virtual Environment",
6203                    description: "Activates the Python virtual environment, if one is found, in the terminal's working directory.",
6204                    field: Box::new(
6205                        SettingField {
6206                            json_path: Some("terminal.detect_venv"),
6207                            pick: |settings_content| settings_content.terminal.as_ref()?.project.detect_venv.as_ref(),
6208                            write: |settings_content, value| {
6209                                settings_content
6210                                    .terminal
6211                                    .get_or_insert_default()
6212                                    .project
6213                                    .detect_venv = value;
6214                            },
6215                        }
6216                        .unimplemented(),
6217                    ),
6218                    metadata: None,
6219                    files: USER | PROJECT,
6220                }),
6221            ]
6222    }
6223
6224    fn font_section() -> [SettingsPageItem; 6] {
6225        [
6226            SettingsPageItem::SectionHeader("Font"),
6227            SettingsPageItem::SettingItem(SettingItem {
6228                title: "Font Size",
6229                description: "Font size for terminal text. If not set, defaults to buffer font size.",
6230                field: Box::new(SettingField {
6231                    json_path: Some("terminal.font_size"),
6232                    pick: |settings_content| {
6233                        settings_content
6234                            .terminal
6235                            .as_ref()
6236                            .and_then(|terminal| terminal.font_size.as_ref())
6237                            .or(settings_content.theme.buffer_font_size.as_ref())
6238                    },
6239                    write: |settings_content, value| {
6240                        settings_content.terminal.get_or_insert_default().font_size = value;
6241                    },
6242                }),
6243                metadata: None,
6244                files: USER,
6245            }),
6246            SettingsPageItem::SettingItem(SettingItem {
6247                title: "Font Family",
6248                description: "Font family for terminal text. If not set, defaults to buffer font family.",
6249                field: Box::new(SettingField {
6250                    json_path: Some("terminal.font_family"),
6251                    pick: |settings_content| {
6252                        settings_content
6253                            .terminal
6254                            .as_ref()
6255                            .and_then(|terminal| terminal.font_family.as_ref())
6256                            .or(settings_content.theme.buffer_font_family.as_ref())
6257                    },
6258                    write: |settings_content, value| {
6259                        settings_content
6260                            .terminal
6261                            .get_or_insert_default()
6262                            .font_family = value;
6263                    },
6264                }),
6265                metadata: None,
6266                files: USER,
6267            }),
6268            SettingsPageItem::SettingItem(SettingItem {
6269                title: "Font Fallbacks",
6270                description: "Font fallbacks for terminal text. If not set, defaults to buffer font fallbacks.",
6271                field: Box::new(
6272                    SettingField {
6273                        json_path: Some("terminal.font_fallbacks"),
6274                        pick: |settings_content| {
6275                            settings_content
6276                                .terminal
6277                                .as_ref()
6278                                .and_then(|terminal| terminal.font_fallbacks.as_ref())
6279                                .or(settings_content.theme.buffer_font_fallbacks.as_ref())
6280                        },
6281                        write: |settings_content, value| {
6282                            settings_content
6283                                .terminal
6284                                .get_or_insert_default()
6285                                .font_fallbacks = value;
6286                        },
6287                    }
6288                    .unimplemented(),
6289                ),
6290                metadata: None,
6291                files: USER,
6292            }),
6293            SettingsPageItem::SettingItem(SettingItem {
6294                title: "Font Weight",
6295                description: "Font weight for terminal text in CSS weight units (100-900).",
6296                field: Box::new(SettingField {
6297                    json_path: Some("terminal.font_weight"),
6298                    pick: |settings_content| {
6299                        settings_content.terminal.as_ref()?.font_weight.as_ref()
6300                    },
6301                    write: |settings_content, value| {
6302                        settings_content
6303                            .terminal
6304                            .get_or_insert_default()
6305                            .font_weight = value;
6306                    },
6307                }),
6308                metadata: None,
6309                files: USER,
6310            }),
6311            SettingsPageItem::SettingItem(SettingItem {
6312                title: "Font Features",
6313                description: "Font features for terminal text.",
6314                field: Box::new(
6315                    SettingField {
6316                        json_path: Some("terminal.font_features"),
6317                        pick: |settings_content| {
6318                            settings_content
6319                                .terminal
6320                                .as_ref()
6321                                .and_then(|terminal| terminal.font_features.as_ref())
6322                                .or(settings_content.theme.buffer_font_features.as_ref())
6323                        },
6324                        write: |settings_content, value| {
6325                            settings_content
6326                                .terminal
6327                                .get_or_insert_default()
6328                                .font_features = value;
6329                        },
6330                    }
6331                    .unimplemented(),
6332                ),
6333                metadata: None,
6334                files: USER,
6335            }),
6336        ]
6337    }
6338
6339    fn display_settings_section() -> [SettingsPageItem; 6] {
6340        [
6341            SettingsPageItem::SectionHeader("Display Settings"),
6342            SettingsPageItem::SettingItem(SettingItem {
6343                title: "Line Height",
6344                description: "Line height for terminal text.",
6345                field: Box::new(
6346                    SettingField {
6347                        json_path: Some("terminal.line_height"),
6348                        pick: |settings_content| {
6349                            settings_content.terminal.as_ref()?.line_height.as_ref()
6350                        },
6351                        write: |settings_content, value| {
6352                            settings_content
6353                                .terminal
6354                                .get_or_insert_default()
6355                                .line_height = value;
6356                        },
6357                    }
6358                    .unimplemented(),
6359                ),
6360                metadata: None,
6361                files: USER,
6362            }),
6363            SettingsPageItem::SettingItem(SettingItem {
6364                title: "Cursor Shape",
6365                description: "Default cursor shape for the terminal (bar, block, underline, or hollow).",
6366                field: Box::new(SettingField {
6367                    json_path: Some("terminal.cursor_shape"),
6368                    pick: |settings_content| {
6369                        settings_content.terminal.as_ref()?.cursor_shape.as_ref()
6370                    },
6371                    write: |settings_content, value| {
6372                        settings_content
6373                            .terminal
6374                            .get_or_insert_default()
6375                            .cursor_shape = value;
6376                    },
6377                }),
6378                metadata: None,
6379                files: USER,
6380            }),
6381            SettingsPageItem::SettingItem(SettingItem {
6382                title: "Cursor Blinking",
6383                description: "Sets the cursor blinking behavior in the terminal.",
6384                field: Box::new(SettingField {
6385                    json_path: Some("terminal.blinking"),
6386                    pick: |settings_content| settings_content.terminal.as_ref()?.blinking.as_ref(),
6387                    write: |settings_content, value| {
6388                        settings_content.terminal.get_or_insert_default().blinking = value;
6389                    },
6390                }),
6391                metadata: None,
6392                files: USER,
6393            }),
6394            SettingsPageItem::SettingItem(SettingItem {
6395                title: "Alternate Scroll",
6396                description: "Whether alternate scroll mode is active by default (converts mouse scroll to arrow keys in apps like Vim).",
6397                field: Box::new(SettingField {
6398                    json_path: Some("terminal.alternate_scroll"),
6399                    pick: |settings_content| {
6400                        settings_content
6401                            .terminal
6402                            .as_ref()?
6403                            .alternate_scroll
6404                            .as_ref()
6405                    },
6406                    write: |settings_content, value| {
6407                        settings_content
6408                            .terminal
6409                            .get_or_insert_default()
6410                            .alternate_scroll = value;
6411                    },
6412                }),
6413                metadata: None,
6414                files: USER,
6415            }),
6416            SettingsPageItem::SettingItem(SettingItem {
6417                title: "Minimum Contrast",
6418                description: "The minimum APCA perceptual contrast between foreground and background colors (0-106).",
6419                field: Box::new(SettingField {
6420                    json_path: Some("terminal.minimum_contrast"),
6421                    pick: |settings_content| {
6422                        settings_content
6423                            .terminal
6424                            .as_ref()?
6425                            .minimum_contrast
6426                            .as_ref()
6427                    },
6428                    write: |settings_content, value| {
6429                        settings_content
6430                            .terminal
6431                            .get_or_insert_default()
6432                            .minimum_contrast = value;
6433                    },
6434                }),
6435                metadata: None,
6436                files: USER,
6437            }),
6438        ]
6439    }
6440
6441    fn behavior_settings_section() -> [SettingsPageItem; 4] {
6442        [
6443            SettingsPageItem::SectionHeader("Behavior Settings"),
6444            SettingsPageItem::SettingItem(SettingItem {
6445                title: "Option As Meta",
6446                description: "Whether the option key behaves as the meta key.",
6447                field: Box::new(SettingField {
6448                    json_path: Some("terminal.option_as_meta"),
6449                    pick: |settings_content| {
6450                        settings_content.terminal.as_ref()?.option_as_meta.as_ref()
6451                    },
6452                    write: |settings_content, value| {
6453                        settings_content
6454                            .terminal
6455                            .get_or_insert_default()
6456                            .option_as_meta = value;
6457                    },
6458                }),
6459                metadata: None,
6460                files: USER,
6461            }),
6462            SettingsPageItem::SettingItem(SettingItem {
6463                title: "Copy On Select",
6464                description: "Whether selecting text in the terminal automatically copies to the system clipboard.",
6465                field: Box::new(SettingField {
6466                    json_path: Some("terminal.copy_on_select"),
6467                    pick: |settings_content| {
6468                        settings_content.terminal.as_ref()?.copy_on_select.as_ref()
6469                    },
6470                    write: |settings_content, value| {
6471                        settings_content
6472                            .terminal
6473                            .get_or_insert_default()
6474                            .copy_on_select = value;
6475                    },
6476                }),
6477                metadata: None,
6478                files: USER,
6479            }),
6480            SettingsPageItem::SettingItem(SettingItem {
6481                title: "Keep Selection On Copy",
6482                description: "Whether to keep the text selection after copying it to the clipboard.",
6483                field: Box::new(SettingField {
6484                    json_path: Some("terminal.keep_selection_on_copy"),
6485                    pick: |settings_content| {
6486                        settings_content
6487                            .terminal
6488                            .as_ref()?
6489                            .keep_selection_on_copy
6490                            .as_ref()
6491                    },
6492                    write: |settings_content, value| {
6493                        settings_content
6494                            .terminal
6495                            .get_or_insert_default()
6496                            .keep_selection_on_copy = value;
6497                    },
6498                }),
6499                metadata: None,
6500                files: USER,
6501            }),
6502        ]
6503    }
6504
6505    fn layout_settings_section() -> [SettingsPageItem; 3] {
6506        [
6507            SettingsPageItem::SectionHeader("Layout Settings"),
6508            SettingsPageItem::SettingItem(SettingItem {
6509                title: "Default Width",
6510                description: "Default width when the terminal is docked to the left or right (in pixels).",
6511                field: Box::new(SettingField {
6512                    json_path: Some("terminal.default_width"),
6513                    pick: |settings_content| {
6514                        settings_content.terminal.as_ref()?.default_width.as_ref()
6515                    },
6516                    write: |settings_content, value| {
6517                        settings_content
6518                            .terminal
6519                            .get_or_insert_default()
6520                            .default_width = value;
6521                    },
6522                }),
6523                metadata: None,
6524                files: USER,
6525            }),
6526            SettingsPageItem::SettingItem(SettingItem {
6527                title: "Default Height",
6528                description: "Default height when the terminal is docked to the bottom (in pixels).",
6529                field: Box::new(SettingField {
6530                    json_path: Some("terminal.default_height"),
6531                    pick: |settings_content| {
6532                        settings_content.terminal.as_ref()?.default_height.as_ref()
6533                    },
6534                    write: |settings_content, value| {
6535                        settings_content
6536                            .terminal
6537                            .get_or_insert_default()
6538                            .default_height = value;
6539                    },
6540                }),
6541                metadata: None,
6542                files: USER,
6543            }),
6544        ]
6545    }
6546
6547    fn advanced_settings_section() -> [SettingsPageItem; 3] {
6548        [
6549            SettingsPageItem::SectionHeader("Advanced Settings"),
6550            SettingsPageItem::SettingItem(SettingItem {
6551                title: "Max Scroll History Lines",
6552                description: "Maximum number of lines to keep in scrollback history (max: 100,000; 0 disables scrolling).",
6553                field: Box::new(SettingField {
6554                    json_path: Some("terminal.max_scroll_history_lines"),
6555                    pick: |settings_content| {
6556                        settings_content
6557                            .terminal
6558                            .as_ref()?
6559                            .max_scroll_history_lines
6560                            .as_ref()
6561                    },
6562                    write: |settings_content, value| {
6563                        settings_content
6564                            .terminal
6565                            .get_or_insert_default()
6566                            .max_scroll_history_lines = value;
6567                    },
6568                }),
6569                metadata: None,
6570                files: USER,
6571            }),
6572            SettingsPageItem::SettingItem(SettingItem {
6573                title: "Scroll Multiplier",
6574                description: "The multiplier for scrolling in the terminal with the mouse wheel",
6575                field: Box::new(SettingField {
6576                    json_path: Some("terminal.scroll_multiplier"),
6577                    pick: |settings_content| {
6578                        settings_content
6579                            .terminal
6580                            .as_ref()?
6581                            .scroll_multiplier
6582                            .as_ref()
6583                    },
6584                    write: |settings_content, value| {
6585                        settings_content
6586                            .terminal
6587                            .get_or_insert_default()
6588                            .scroll_multiplier = value;
6589                    },
6590                }),
6591                metadata: None,
6592                files: USER,
6593            }),
6594        ]
6595    }
6596
6597    fn toolbar_section() -> [SettingsPageItem; 2] {
6598        [
6599            SettingsPageItem::SectionHeader("Toolbar"),
6600            SettingsPageItem::SettingItem(SettingItem {
6601                title: "Breadcrumbs",
6602                description: "Display the terminal title in breadcrumbs inside the terminal pane.",
6603                field: Box::new(SettingField {
6604                    json_path: Some("terminal.toolbar.breadcrumbs"),
6605                    pick: |settings_content| {
6606                        settings_content
6607                            .terminal
6608                            .as_ref()?
6609                            .toolbar
6610                            .as_ref()?
6611                            .breadcrumbs
6612                            .as_ref()
6613                    },
6614                    write: |settings_content, value| {
6615                        settings_content
6616                            .terminal
6617                            .get_or_insert_default()
6618                            .toolbar
6619                            .get_or_insert_default()
6620                            .breadcrumbs = value;
6621                    },
6622                }),
6623                metadata: None,
6624                files: USER,
6625            }),
6626        ]
6627    }
6628
6629    fn scrollbar_section() -> [SettingsPageItem; 2] {
6630        [
6631            SettingsPageItem::SectionHeader("Scrollbar"),
6632            SettingsPageItem::SettingItem(SettingItem {
6633                title: "Show Scrollbar",
6634                description: "When to show the scrollbar in the terminal.",
6635                field: Box::new(SettingField {
6636                    json_path: Some("terminal.scrollbar.show"),
6637                    pick: |settings_content| {
6638                        show_scrollbar_or_editor(settings_content, |settings_content| {
6639                            settings_content
6640                                .terminal
6641                                .as_ref()?
6642                                .scrollbar
6643                                .as_ref()?
6644                                .show
6645                                .as_ref()
6646                        })
6647                    },
6648                    write: |settings_content, value| {
6649                        settings_content
6650                            .terminal
6651                            .get_or_insert_default()
6652                            .scrollbar
6653                            .get_or_insert_default()
6654                            .show = value;
6655                    },
6656                }),
6657                metadata: None,
6658                files: USER,
6659            }),
6660        ]
6661    }
6662
6663    SettingsPage {
6664        title: "Terminal",
6665        items: concat_sections![
6666            environment_section(),
6667            font_section(),
6668            display_settings_section(),
6669            behavior_settings_section(),
6670            layout_settings_section(),
6671            advanced_settings_section(),
6672            toolbar_section(),
6673            scrollbar_section(),
6674        ],
6675    }
6676}
6677
6678fn version_control_page() -> SettingsPage {
6679    fn git_integration_section() -> [SettingsPageItem; 2] {
6680        [
6681            SettingsPageItem::SectionHeader("Git Integration"),
6682            SettingsPageItem::DynamicItem(DynamicItem {
6683                discriminant: SettingItem {
6684                    files: USER,
6685                    title: "Disable Git Integration",
6686                    description: "Disable all Git integration features in Zed.",
6687                    field: Box::new(SettingField::<bool> {
6688                        json_path: Some("git.disable_git"),
6689                        pick: |settings_content| {
6690                            settings_content
6691                                .git
6692                                .as_ref()?
6693                                .enabled
6694                                .as_ref()?
6695                                .disable_git
6696                                .as_ref()
6697                        },
6698                        write: |settings_content, value| {
6699                            settings_content
6700                                .git
6701                                .get_or_insert_default()
6702                                .enabled
6703                                .get_or_insert_default()
6704                                .disable_git = value;
6705                        },
6706                    }),
6707                    metadata: None,
6708                },
6709                pick_discriminant: |settings_content| {
6710                    let disabled = settings_content
6711                        .git
6712                        .as_ref()?
6713                        .enabled
6714                        .as_ref()?
6715                        .disable_git
6716                        .unwrap_or(false);
6717                    Some(if disabled { 0 } else { 1 })
6718                },
6719                fields: vec![
6720                    vec![],
6721                    vec![
6722                        SettingItem {
6723                            files: USER,
6724                            title: "Enable Git Status",
6725                            description: "Show Git status information in the editor.",
6726                            field: Box::new(SettingField::<bool> {
6727                                json_path: Some("git.enable_status"),
6728                                pick: |settings_content| {
6729                                    settings_content
6730                                        .git
6731                                        .as_ref()?
6732                                        .enabled
6733                                        .as_ref()?
6734                                        .enable_status
6735                                        .as_ref()
6736                                },
6737                                write: |settings_content, value| {
6738                                    settings_content
6739                                        .git
6740                                        .get_or_insert_default()
6741                                        .enabled
6742                                        .get_or_insert_default()
6743                                        .enable_status = value;
6744                                },
6745                            }),
6746                            metadata: None,
6747                        },
6748                        SettingItem {
6749                            files: USER,
6750                            title: "Enable Git Diff",
6751                            description: "Show Git diff information in the editor.",
6752                            field: Box::new(SettingField::<bool> {
6753                                json_path: Some("git.enable_diff"),
6754                                pick: |settings_content| {
6755                                    settings_content
6756                                        .git
6757                                        .as_ref()?
6758                                        .enabled
6759                                        .as_ref()?
6760                                        .enable_diff
6761                                        .as_ref()
6762                                },
6763                                write: |settings_content, value| {
6764                                    settings_content
6765                                        .git
6766                                        .get_or_insert_default()
6767                                        .enabled
6768                                        .get_or_insert_default()
6769                                        .enable_diff = value;
6770                                },
6771                            }),
6772                            metadata: None,
6773                        },
6774                    ],
6775                ],
6776            }),
6777        ]
6778    }
6779
6780    fn git_gutter_section() -> [SettingsPageItem; 3] {
6781        [
6782            SettingsPageItem::SectionHeader("Git Gutter"),
6783            SettingsPageItem::SettingItem(SettingItem {
6784                title: "Visibility",
6785                description: "Control whether Git status is shown in the editor's gutter.",
6786                field: Box::new(SettingField {
6787                    json_path: Some("git.git_gutter"),
6788                    pick: |settings_content| settings_content.git.as_ref()?.git_gutter.as_ref(),
6789                    write: |settings_content, value| {
6790                        settings_content.git.get_or_insert_default().git_gutter = value;
6791                    },
6792                }),
6793                metadata: None,
6794                files: USER,
6795            }),
6796            // todo(settings_ui): Figure out the right default for this value in default.json
6797            SettingsPageItem::SettingItem(SettingItem {
6798                title: "Debounce",
6799                description: "Debounce threshold in milliseconds after which changes are reflected in the Git gutter.",
6800                field: Box::new(SettingField {
6801                    json_path: Some("git.gutter_debounce"),
6802                    pick: |settings_content| {
6803                        settings_content.git.as_ref()?.gutter_debounce.as_ref()
6804                    },
6805                    write: |settings_content, value| {
6806                        settings_content.git.get_or_insert_default().gutter_debounce = value;
6807                    },
6808                }),
6809                metadata: None,
6810                files: USER,
6811            }),
6812        ]
6813    }
6814
6815    fn inline_git_blame_section() -> [SettingsPageItem; 6] {
6816        [
6817            SettingsPageItem::SectionHeader("Inline Git Blame"),
6818            SettingsPageItem::SettingItem(SettingItem {
6819                title: "Enabled",
6820                description: "Whether or not to show Git blame data inline in the currently focused line.",
6821                field: Box::new(SettingField {
6822                    json_path: Some("git.inline_blame.enabled"),
6823                    pick: |settings_content| {
6824                        settings_content
6825                            .git
6826                            .as_ref()?
6827                            .inline_blame
6828                            .as_ref()?
6829                            .enabled
6830                            .as_ref()
6831                    },
6832                    write: |settings_content, value| {
6833                        settings_content
6834                            .git
6835                            .get_or_insert_default()
6836                            .inline_blame
6837                            .get_or_insert_default()
6838                            .enabled = value;
6839                    },
6840                }),
6841                metadata: None,
6842                files: USER,
6843            }),
6844            SettingsPageItem::SettingItem(SettingItem {
6845                title: "Delay",
6846                description: "The delay after which the inline blame information is shown.",
6847                field: Box::new(SettingField {
6848                    json_path: Some("git.inline_blame.delay_ms"),
6849                    pick: |settings_content| {
6850                        settings_content
6851                            .git
6852                            .as_ref()?
6853                            .inline_blame
6854                            .as_ref()?
6855                            .delay_ms
6856                            .as_ref()
6857                    },
6858                    write: |settings_content, value| {
6859                        settings_content
6860                            .git
6861                            .get_or_insert_default()
6862                            .inline_blame
6863                            .get_or_insert_default()
6864                            .delay_ms = value;
6865                    },
6866                }),
6867                metadata: None,
6868                files: USER,
6869            }),
6870            SettingsPageItem::SettingItem(SettingItem {
6871                title: "Padding",
6872                description: "Padding between the end of the source line and the start of the inline blame in columns.",
6873                field: Box::new(SettingField {
6874                    json_path: Some("git.inline_blame.padding"),
6875                    pick: |settings_content| {
6876                        settings_content
6877                            .git
6878                            .as_ref()?
6879                            .inline_blame
6880                            .as_ref()?
6881                            .padding
6882                            .as_ref()
6883                    },
6884                    write: |settings_content, value| {
6885                        settings_content
6886                            .git
6887                            .get_or_insert_default()
6888                            .inline_blame
6889                            .get_or_insert_default()
6890                            .padding = value;
6891                    },
6892                }),
6893                metadata: None,
6894                files: USER,
6895            }),
6896            SettingsPageItem::SettingItem(SettingItem {
6897                title: "Minimum Column",
6898                description: "The minimum column number at which to show the inline blame information.",
6899                field: Box::new(SettingField {
6900                    json_path: Some("git.inline_blame.min_column"),
6901                    pick: |settings_content| {
6902                        settings_content
6903                            .git
6904                            .as_ref()?
6905                            .inline_blame
6906                            .as_ref()?
6907                            .min_column
6908                            .as_ref()
6909                    },
6910                    write: |settings_content, value| {
6911                        settings_content
6912                            .git
6913                            .get_or_insert_default()
6914                            .inline_blame
6915                            .get_or_insert_default()
6916                            .min_column = value;
6917                    },
6918                }),
6919                metadata: None,
6920                files: USER,
6921            }),
6922            SettingsPageItem::SettingItem(SettingItem {
6923                title: "Show Commit Summary",
6924                description: "Show commit summary as part of the inline blame.",
6925                field: Box::new(SettingField {
6926                    json_path: Some("git.inline_blame.show_commit_summary"),
6927                    pick: |settings_content| {
6928                        settings_content
6929                            .git
6930                            .as_ref()?
6931                            .inline_blame
6932                            .as_ref()?
6933                            .show_commit_summary
6934                            .as_ref()
6935                    },
6936                    write: |settings_content, value| {
6937                        settings_content
6938                            .git
6939                            .get_or_insert_default()
6940                            .inline_blame
6941                            .get_or_insert_default()
6942                            .show_commit_summary = value;
6943                    },
6944                }),
6945                metadata: None,
6946                files: USER,
6947            }),
6948        ]
6949    }
6950
6951    fn git_blame_view_section() -> [SettingsPageItem; 2] {
6952        [
6953            SettingsPageItem::SectionHeader("Git Blame View"),
6954            SettingsPageItem::SettingItem(SettingItem {
6955                title: "Show Avatar",
6956                description: "Show the avatar of the author of the commit.",
6957                field: Box::new(SettingField {
6958                    json_path: Some("git.blame.show_avatar"),
6959                    pick: |settings_content| {
6960                        settings_content
6961                            .git
6962                            .as_ref()?
6963                            .blame
6964                            .as_ref()?
6965                            .show_avatar
6966                            .as_ref()
6967                    },
6968                    write: |settings_content, value| {
6969                        settings_content
6970                            .git
6971                            .get_or_insert_default()
6972                            .blame
6973                            .get_or_insert_default()
6974                            .show_avatar = value;
6975                    },
6976                }),
6977                metadata: None,
6978                files: USER,
6979            }),
6980        ]
6981    }
6982
6983    fn branch_picker_section() -> [SettingsPageItem; 2] {
6984        [
6985            SettingsPageItem::SectionHeader("Branch Picker"),
6986            SettingsPageItem::SettingItem(SettingItem {
6987                title: "Show Author Name",
6988                description: "Show author name as part of the commit information in branch picker.",
6989                field: Box::new(SettingField {
6990                    json_path: Some("git.branch_picker.show_author_name"),
6991                    pick: |settings_content| {
6992                        settings_content
6993                            .git
6994                            .as_ref()?
6995                            .branch_picker
6996                            .as_ref()?
6997                            .show_author_name
6998                            .as_ref()
6999                    },
7000                    write: |settings_content, value| {
7001                        settings_content
7002                            .git
7003                            .get_or_insert_default()
7004                            .branch_picker
7005                            .get_or_insert_default()
7006                            .show_author_name = value;
7007                    },
7008                }),
7009                metadata: None,
7010                files: USER,
7011            }),
7012        ]
7013    }
7014
7015    fn git_hunks_section() -> [SettingsPageItem; 3] {
7016        [
7017            SettingsPageItem::SectionHeader("Git Hunks"),
7018            SettingsPageItem::SettingItem(SettingItem {
7019                title: "Hunk Style",
7020                description: "How Git hunks are displayed visually in the editor.",
7021                field: Box::new(SettingField {
7022                    json_path: Some("git.hunk_style"),
7023                    pick: |settings_content| settings_content.git.as_ref()?.hunk_style.as_ref(),
7024                    write: |settings_content, value| {
7025                        settings_content.git.get_or_insert_default().hunk_style = value;
7026                    },
7027                }),
7028                metadata: None,
7029                files: USER,
7030            }),
7031            SettingsPageItem::SettingItem(SettingItem {
7032                title: "Path Style",
7033                description: "Should the name or path be displayed first in the git view.",
7034                field: Box::new(SettingField {
7035                    json_path: Some("git.path_style"),
7036                    pick: |settings_content| settings_content.git.as_ref()?.path_style.as_ref(),
7037                    write: |settings_content, value| {
7038                        settings_content.git.get_or_insert_default().path_style = value;
7039                    },
7040                }),
7041                metadata: None,
7042                files: USER,
7043            }),
7044        ]
7045    }
7046
7047    SettingsPage {
7048        title: "Version Control",
7049        items: concat_sections![
7050            git_integration_section(),
7051            git_gutter_section(),
7052            inline_git_blame_section(),
7053            git_blame_view_section(),
7054            branch_picker_section(),
7055            git_hunks_section(),
7056        ],
7057    }
7058}
7059
7060fn collaboration_page() -> SettingsPage {
7061    fn calls_section() -> [SettingsPageItem; 3] {
7062        [
7063            SettingsPageItem::SectionHeader("Calls"),
7064            SettingsPageItem::SettingItem(SettingItem {
7065                title: "Mute On Join",
7066                description: "Whether the microphone should be muted when joining a channel or a call.",
7067                field: Box::new(SettingField {
7068                    json_path: Some("calls.mute_on_join"),
7069                    pick: |settings_content| settings_content.calls.as_ref()?.mute_on_join.as_ref(),
7070                    write: |settings_content, value| {
7071                        settings_content.calls.get_or_insert_default().mute_on_join = value;
7072                    },
7073                }),
7074                metadata: None,
7075                files: USER,
7076            }),
7077            SettingsPageItem::SettingItem(SettingItem {
7078                title: "Share On Join",
7079                description: "Whether your current project should be shared when joining an empty channel.",
7080                field: Box::new(SettingField {
7081                    json_path: Some("calls.share_on_join"),
7082                    pick: |settings_content| {
7083                        settings_content.calls.as_ref()?.share_on_join.as_ref()
7084                    },
7085                    write: |settings_content, value| {
7086                        settings_content.calls.get_or_insert_default().share_on_join = value;
7087                    },
7088                }),
7089                metadata: None,
7090                files: USER,
7091            }),
7092        ]
7093    }
7094
7095    fn audio_settings() -> [SettingsPageItem; 3] {
7096        [
7097            SettingsPageItem::ActionLink(ActionLink {
7098                title: "Test Audio".into(),
7099                description: Some("Test your microphone and speaker setup".into()),
7100                button_text: "Test Audio".into(),
7101                on_click: Arc::new(|_settings_window, window, cx| {
7102                    open_audio_test_window(window, cx);
7103                }),
7104                files: USER,
7105            }),
7106            SettingsPageItem::SettingItem(SettingItem {
7107                title: "Output Audio Device",
7108                description: "Select output audio device",
7109                field: Box::new(SettingField {
7110                    json_path: Some("audio.experimental.output_audio_device"),
7111                    pick: |settings_content| {
7112                        settings_content
7113                            .audio
7114                            .as_ref()?
7115                            .output_audio_device
7116                            .as_ref()
7117                            .or(DEFAULT_EMPTY_AUDIO_OUTPUT)
7118                    },
7119                    write: |settings_content, value| {
7120                        settings_content
7121                            .audio
7122                            .get_or_insert_default()
7123                            .output_audio_device = value;
7124                    },
7125                }),
7126                metadata: None,
7127                files: USER,
7128            }),
7129            SettingsPageItem::SettingItem(SettingItem {
7130                title: "Input Audio Device",
7131                description: "Select input audio device",
7132                field: Box::new(SettingField {
7133                    json_path: Some("audio.experimental.input_audio_device"),
7134                    pick: |settings_content| {
7135                        settings_content
7136                            .audio
7137                            .as_ref()?
7138                            .input_audio_device
7139                            .as_ref()
7140                            .or(DEFAULT_EMPTY_AUDIO_INPUT)
7141                    },
7142                    write: |settings_content, value| {
7143                        settings_content
7144                            .audio
7145                            .get_or_insert_default()
7146                            .input_audio_device = value;
7147                    },
7148                }),
7149                metadata: None,
7150                files: USER,
7151            }),
7152        ]
7153    }
7154
7155    SettingsPage {
7156        title: "Collaboration",
7157        items: concat_sections![calls_section(), audio_settings()],
7158    }
7159}
7160
7161fn ai_page(cx: &App) -> SettingsPage {
7162    fn general_section() -> [SettingsPageItem; 2] {
7163        [
7164            SettingsPageItem::SectionHeader("General"),
7165            SettingsPageItem::SettingItem(SettingItem {
7166                title: "Disable AI",
7167                description: "Whether to disable all AI features in Zed.",
7168                field: Box::new(SettingField {
7169                    json_path: Some("disable_ai"),
7170                    pick: |settings_content| settings_content.project.disable_ai.as_ref(),
7171                    write: |settings_content, value| {
7172                        settings_content.project.disable_ai = value;
7173                    },
7174                }),
7175                metadata: None,
7176                files: USER | PROJECT,
7177            }),
7178        ]
7179    }
7180
7181    fn agent_configuration_section(cx: &App) -> Box<[SettingsPageItem]> {
7182        let mut items = vec![
7183            SettingsPageItem::SectionHeader("Agent Configuration"),
7184            SettingsPageItem::SubPageLink(SubPageLink {
7185                title: "Tool Permissions".into(),
7186                r#type: Default::default(),
7187                json_path: Some("agent.tool_permissions"),
7188                description: Some("Set up regex patterns to auto-allow, auto-deny, or always request confirmation, for specific tool inputs.".into()),
7189                in_json: true,
7190                files: USER,
7191                render: render_tool_permissions_setup_page,
7192            }),
7193        ];
7194
7195        if cx.has_flag::<AgentV2FeatureFlag>() {
7196            items.push(SettingsPageItem::SettingItem(SettingItem {
7197                title: "New Thread Location",
7198                description: "Whether to start a new thread in the current local project or in a new Git worktree.",
7199                field: Box::new(SettingField {
7200                    json_path: Some("agent.new_thread_location"),
7201                    pick: |settings_content| {
7202                        settings_content
7203                            .agent
7204                            .as_ref()?
7205                            .new_thread_location
7206                            .as_ref()
7207                    },
7208                    write: |settings_content, value| {
7209                        settings_content
7210                            .agent
7211                            .get_or_insert_default()
7212                            .new_thread_location = value;
7213                    },
7214                }),
7215                metadata: None,
7216                files: USER,
7217            }));
7218        }
7219
7220        items.extend([
7221            SettingsPageItem::SettingItem(SettingItem {
7222                title: "Single File Review",
7223                description: "When enabled, agent edits will also be displayed in single-file buffers for review.",
7224                field: Box::new(SettingField {
7225                    json_path: Some("agent.single_file_review"),
7226                    pick: |settings_content| {
7227                        settings_content.agent.as_ref()?.single_file_review.as_ref()
7228                    },
7229                    write: |settings_content, value| {
7230                        settings_content
7231                            .agent
7232                            .get_or_insert_default()
7233                            .single_file_review = value;
7234                    },
7235                }),
7236                metadata: None,
7237                files: USER,
7238            }),
7239            SettingsPageItem::SettingItem(SettingItem {
7240                title: "Enable Feedback",
7241                description: "Show voting thumbs up/down icon buttons for feedback on agent edits.",
7242                field: Box::new(SettingField {
7243                    json_path: Some("agent.enable_feedback"),
7244                    pick: |settings_content| {
7245                        settings_content.agent.as_ref()?.enable_feedback.as_ref()
7246                    },
7247                    write: |settings_content, value| {
7248                        settings_content
7249                            .agent
7250                            .get_or_insert_default()
7251                            .enable_feedback = value;
7252                    },
7253                }),
7254                metadata: None,
7255                files: USER,
7256            }),
7257            SettingsPageItem::SettingItem(SettingItem {
7258                title: "Notify When Agent Waiting",
7259                description: "Where to show notifications when the agent has completed its response or needs confirmation before running a tool action.",
7260                field: Box::new(SettingField {
7261                    json_path: Some("agent.notify_when_agent_waiting"),
7262                    pick: |settings_content| {
7263                        settings_content
7264                            .agent
7265                            .as_ref()?
7266                            .notify_when_agent_waiting
7267                            .as_ref()
7268                    },
7269                    write: |settings_content, value| {
7270                        settings_content
7271                            .agent
7272                            .get_or_insert_default()
7273                            .notify_when_agent_waiting = value;
7274                    },
7275                }),
7276                metadata: None,
7277                files: USER,
7278            }),
7279            SettingsPageItem::SettingItem(SettingItem {
7280                title: "Play Sound When Agent Done",
7281                description: "Whether to play a sound when the agent has either completed its response, or needs user input.",
7282                field: Box::new(SettingField {
7283                    json_path: Some("agent.play_sound_when_agent_done"),
7284                    pick: |settings_content| {
7285                        settings_content
7286                            .agent
7287                            .as_ref()?
7288                            .play_sound_when_agent_done
7289                            .as_ref()
7290                    },
7291                    write: |settings_content, value| {
7292                        settings_content
7293                            .agent
7294                            .get_or_insert_default()
7295                            .play_sound_when_agent_done = value;
7296                    },
7297                }),
7298                metadata: None,
7299                files: USER,
7300            }),
7301            SettingsPageItem::SettingItem(SettingItem {
7302                title: "Expand Edit Card",
7303                description: "Whether to have edit cards in the agent panel expanded, showing a Preview of the diff.",
7304                field: Box::new(SettingField {
7305                    json_path: Some("agent.expand_edit_card"),
7306                    pick: |settings_content| {
7307                        settings_content.agent.as_ref()?.expand_edit_card.as_ref()
7308                    },
7309                    write: |settings_content, value| {
7310                        settings_content
7311                            .agent
7312                            .get_or_insert_default()
7313                            .expand_edit_card = value;
7314                    },
7315                }),
7316                metadata: None,
7317                files: USER,
7318            }),
7319            SettingsPageItem::SettingItem(SettingItem {
7320                title: "Expand Terminal Card",
7321                description: "Whether to have terminal cards in the agent panel expanded, showing the whole command output.",
7322                field: Box::new(SettingField {
7323                    json_path: Some("agent.expand_terminal_card"),
7324                    pick: |settings_content| {
7325                        settings_content
7326                            .agent
7327                            .as_ref()?
7328                            .expand_terminal_card
7329                            .as_ref()
7330                    },
7331                    write: |settings_content, value| {
7332                        settings_content
7333                            .agent
7334                            .get_or_insert_default()
7335                            .expand_terminal_card = value;
7336                    },
7337                }),
7338                metadata: None,
7339                files: USER,
7340            }),
7341            SettingsPageItem::SettingItem(SettingItem {
7342                title: "Thinking Display",
7343                description: "How thinking blocks should be displayed by default. 'Auto' fully expands during streaming, then auto-collapses when done. 'Preview' auto-expands with a height constraint during streaming. 'Always Expanded' shows full content. 'Always Collapsed' keeps them collapsed.",
7344                field: Box::new(SettingField {
7345                    json_path: Some("agent.thinking_display"),
7346                    pick: |settings_content| {
7347                        settings_content
7348                            .agent
7349                            .as_ref()?
7350                            .thinking_display
7351                            .as_ref()
7352                    },
7353                    write: |settings_content, value| {
7354                        settings_content
7355                            .agent
7356                            .get_or_insert_default()
7357                            .thinking_display = value;
7358                    },
7359                }),
7360                metadata: None,
7361                files: USER,
7362            }),
7363            SettingsPageItem::SettingItem(SettingItem {
7364                title: "Cancel Generation On Terminal Stop",
7365                description: "Whether clicking the stop button on a running terminal tool should also cancel the agent's generation. Note that this only applies to the stop button, not to ctrl+c inside the terminal.",
7366                field: Box::new(SettingField {
7367                    json_path: Some("agent.cancel_generation_on_terminal_stop"),
7368                    pick: |settings_content| {
7369                        settings_content
7370                            .agent
7371                            .as_ref()?
7372                            .cancel_generation_on_terminal_stop
7373                            .as_ref()
7374                    },
7375                    write: |settings_content, value| {
7376                        settings_content
7377                            .agent
7378                            .get_or_insert_default()
7379                            .cancel_generation_on_terminal_stop = value;
7380                    },
7381                }),
7382                metadata: None,
7383                files: USER,
7384            }),
7385            SettingsPageItem::SettingItem(SettingItem {
7386                title: "Use Modifier To Send",
7387                description: "Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages.",
7388                field: Box::new(SettingField {
7389                    json_path: Some("agent.use_modifier_to_send"),
7390                    pick: |settings_content| {
7391                        settings_content
7392                            .agent
7393                            .as_ref()?
7394                            .use_modifier_to_send
7395                            .as_ref()
7396                    },
7397                    write: |settings_content, value| {
7398                        settings_content
7399                            .agent
7400                            .get_or_insert_default()
7401                            .use_modifier_to_send = value;
7402                    },
7403                }),
7404                metadata: None,
7405                files: USER,
7406            }),
7407            SettingsPageItem::SettingItem(SettingItem {
7408                title: "Message Editor Min Lines",
7409                description: "Minimum number of lines to display in the agent message editor.",
7410                field: Box::new(SettingField {
7411                    json_path: Some("agent.message_editor_min_lines"),
7412                    pick: |settings_content| {
7413                        settings_content
7414                            .agent
7415                            .as_ref()?
7416                            .message_editor_min_lines
7417                            .as_ref()
7418                    },
7419                    write: |settings_content, value| {
7420                        settings_content
7421                            .agent
7422                            .get_or_insert_default()
7423                            .message_editor_min_lines = value;
7424                    },
7425                }),
7426                metadata: None,
7427                files: USER,
7428            }),
7429            SettingsPageItem::SettingItem(SettingItem {
7430                title: "Show Turn Stats",
7431                description: "Whether to show turn statistics like elapsed time during generation and final turn duration.",
7432                field: Box::new(SettingField {
7433                    json_path: Some("agent.show_turn_stats"),
7434                    pick: |settings_content| {
7435                        settings_content.agent.as_ref()?.show_turn_stats.as_ref()
7436                    },
7437                    write: |settings_content, value| {
7438                        settings_content
7439                            .agent
7440                            .get_or_insert_default()
7441                            .show_turn_stats = value;
7442                    },
7443                }),
7444                metadata: None,
7445                files: USER,
7446            }),
7447        ]);
7448
7449        items.into_boxed_slice()
7450    }
7451
7452    fn context_servers_section() -> [SettingsPageItem; 2] {
7453        [
7454            SettingsPageItem::SectionHeader("Context Servers"),
7455            SettingsPageItem::SettingItem(SettingItem {
7456                title: "Context Server Timeout",
7457                description: "Default timeout in seconds for context server tool calls. Can be overridden per-server in context_servers configuration.",
7458                field: Box::new(SettingField {
7459                    json_path: Some("context_server_timeout"),
7460                    pick: |settings_content| {
7461                        settings_content.project.context_server_timeout.as_ref()
7462                    },
7463                    write: |settings_content, value| {
7464                        settings_content.project.context_server_timeout = value;
7465                    },
7466                }),
7467                metadata: None,
7468                files: USER | PROJECT,
7469            }),
7470        ]
7471    }
7472
7473    fn edit_prediction_display_sub_section() -> [SettingsPageItem; 1] {
7474        [SettingsPageItem::SettingItem(SettingItem {
7475            title: "Display Mode",
7476            description: "When to show edit predictions previews in buffer. The eager mode displays them inline, while the subtle mode displays them only when holding a modifier key.",
7477            field: Box::new(SettingField {
7478                json_path: Some("edit_prediction.display_mode"),
7479                pick: |settings_content| {
7480                    settings_content
7481                        .project
7482                        .all_languages
7483                        .edit_predictions
7484                        .as_ref()?
7485                        .mode
7486                        .as_ref()
7487                },
7488                write: |settings_content, value| {
7489                    settings_content
7490                        .project
7491                        .all_languages
7492                        .edit_predictions
7493                        .get_or_insert_default()
7494                        .mode = value;
7495                },
7496            }),
7497            metadata: None,
7498            files: USER,
7499        })]
7500    }
7501
7502    SettingsPage {
7503        title: "AI",
7504        items: concat_sections![
7505            general_section(),
7506            agent_configuration_section(cx),
7507            context_servers_section(),
7508            edit_prediction_language_settings_section(),
7509            edit_prediction_display_sub_section()
7510        ],
7511    }
7512}
7513
7514fn network_page() -> SettingsPage {
7515    fn network_section() -> [SettingsPageItem; 3] {
7516        [
7517            SettingsPageItem::SectionHeader("Network"),
7518            SettingsPageItem::SettingItem(SettingItem {
7519                title: "Proxy",
7520                description: "The proxy to use for network requests.",
7521                field: Box::new(SettingField {
7522                    json_path: Some("proxy"),
7523                    pick: |settings_content| settings_content.proxy.as_ref(),
7524                    write: |settings_content, value| {
7525                        settings_content.proxy = value;
7526                    },
7527                }),
7528                metadata: Some(Box::new(SettingsFieldMetadata {
7529                    placeholder: Some("socks5h://localhost:10808"),
7530                    ..Default::default()
7531                })),
7532                files: USER,
7533            }),
7534            SettingsPageItem::SettingItem(SettingItem {
7535                title: "Server URL",
7536                description: "The URL of the Zed server to connect to.",
7537                field: Box::new(SettingField {
7538                    json_path: Some("server_url"),
7539                    pick: |settings_content| settings_content.server_url.as_ref(),
7540                    write: |settings_content, value| {
7541                        settings_content.server_url = value;
7542                    },
7543                }),
7544                metadata: Some(Box::new(SettingsFieldMetadata {
7545                    placeholder: Some("https://zed.dev"),
7546                    ..Default::default()
7547                })),
7548                files: USER,
7549            }),
7550        ]
7551    }
7552
7553    SettingsPage {
7554        title: "Network",
7555        items: concat_sections![network_section()],
7556    }
7557}
7558
7559fn language_settings_field<T>(
7560    settings_content: &SettingsContent,
7561    get_language_setting_field: fn(&LanguageSettingsContent) -> Option<&T>,
7562) -> Option<&T> {
7563    let all_languages = &settings_content.project.all_languages;
7564
7565    active_language()
7566        .and_then(|current_language_name| {
7567            all_languages
7568                .languages
7569                .0
7570                .get(current_language_name.as_ref())
7571        })
7572        .and_then(get_language_setting_field)
7573        .or_else(|| get_language_setting_field(&all_languages.defaults))
7574}
7575
7576fn language_settings_field_mut<T>(
7577    settings_content: &mut SettingsContent,
7578    value: Option<T>,
7579    write: fn(&mut LanguageSettingsContent, Option<T>),
7580) {
7581    let all_languages = &mut settings_content.project.all_languages;
7582    let language_content = if let Some(current_language) = active_language() {
7583        all_languages
7584            .languages
7585            .0
7586            .entry(current_language.to_string())
7587            .or_default()
7588    } else {
7589        &mut all_languages.defaults
7590    };
7591    write(language_content, value);
7592}
7593
7594fn language_settings_data() -> Box<[SettingsPageItem]> {
7595    fn indentation_section() -> [SettingsPageItem; 5] {
7596        [
7597            SettingsPageItem::SectionHeader("Indentation"),
7598            SettingsPageItem::SettingItem(SettingItem {
7599                title: "Tab Size",
7600                description: "How many columns a tab should occupy.",
7601                field: Box::new(SettingField {
7602                    json_path: Some("languages.$(language).tab_size"), // TODO(cameron): not JQ syntax because not URL-safe
7603                    pick: |settings_content| {
7604                        language_settings_field(settings_content, |language| {
7605                            language.tab_size.as_ref()
7606                        })
7607                    },
7608                    write: |settings_content, value| {
7609                        language_settings_field_mut(settings_content, value, |language, value| {
7610                            language.tab_size = value;
7611                        })
7612                    },
7613                }),
7614                metadata: None,
7615                files: USER | PROJECT,
7616            }),
7617            SettingsPageItem::SettingItem(SettingItem {
7618                title: "Hard Tabs",
7619                description: "Whether to indent lines using tab characters, as opposed to multiple spaces.",
7620                field: Box::new(SettingField {
7621                    json_path: Some("languages.$(language).hard_tabs"),
7622                    pick: |settings_content| {
7623                        language_settings_field(settings_content, |language| {
7624                            language.hard_tabs.as_ref()
7625                        })
7626                    },
7627                    write: |settings_content, value| {
7628                        language_settings_field_mut(settings_content, value, |language, value| {
7629                            language.hard_tabs = value;
7630                        })
7631                    },
7632                }),
7633                metadata: None,
7634                files: USER | PROJECT,
7635            }),
7636            SettingsPageItem::SettingItem(SettingItem {
7637                title: "Auto Indent",
7638                description: "Controls automatic indentation behavior when typing.",
7639                field: Box::new(SettingField {
7640                    json_path: Some("languages.$(language).auto_indent"),
7641                    pick: |settings_content| {
7642                        language_settings_field(settings_content, |language| {
7643                            language.auto_indent.as_ref()
7644                        })
7645                    },
7646                    write: |settings_content, value| {
7647                        language_settings_field_mut(settings_content, value, |language, value| {
7648                            language.auto_indent = value;
7649                        })
7650                    },
7651                }),
7652                metadata: None,
7653                files: USER | PROJECT,
7654            }),
7655            SettingsPageItem::SettingItem(SettingItem {
7656                title: "Auto Indent On Paste",
7657                description: "Whether indentation of pasted content should be adjusted based on the context.",
7658                field: Box::new(SettingField {
7659                    json_path: Some("languages.$(language).auto_indent_on_paste"),
7660                    pick: |settings_content| {
7661                        language_settings_field(settings_content, |language| {
7662                            language.auto_indent_on_paste.as_ref()
7663                        })
7664                    },
7665                    write: |settings_content, value| {
7666                        language_settings_field_mut(settings_content, value, |language, value| {
7667                            language.auto_indent_on_paste = value;
7668                        })
7669                    },
7670                }),
7671                metadata: None,
7672                files: USER | PROJECT,
7673            }),
7674        ]
7675    }
7676
7677    fn wrapping_section() -> [SettingsPageItem; 6] {
7678        [
7679            SettingsPageItem::SectionHeader("Wrapping"),
7680            SettingsPageItem::SettingItem(SettingItem {
7681                title: "Soft Wrap",
7682                description: "How to soft-wrap long lines of text.",
7683                field: Box::new(SettingField {
7684                    json_path: Some("languages.$(language).soft_wrap"),
7685                    pick: |settings_content| {
7686                        language_settings_field(settings_content, |language| {
7687                            language.soft_wrap.as_ref()
7688                        })
7689                    },
7690                    write: |settings_content, value| {
7691                        language_settings_field_mut(settings_content, value, |language, value| {
7692                            language.soft_wrap = value;
7693                        })
7694                    },
7695                }),
7696                metadata: None,
7697                files: USER | PROJECT,
7698            }),
7699            SettingsPageItem::SettingItem(SettingItem {
7700                title: "Show Wrap Guides",
7701                description: "Show wrap guides in the editor.",
7702                field: Box::new(SettingField {
7703                    json_path: Some("languages.$(language).show_wrap_guides"),
7704                    pick: |settings_content| {
7705                        language_settings_field(settings_content, |language| {
7706                            language.show_wrap_guides.as_ref()
7707                        })
7708                    },
7709                    write: |settings_content, value| {
7710                        language_settings_field_mut(settings_content, value, |language, value| {
7711                            language.show_wrap_guides = value;
7712                        })
7713                    },
7714                }),
7715                metadata: None,
7716                files: USER | PROJECT,
7717            }),
7718            SettingsPageItem::SettingItem(SettingItem {
7719                title: "Preferred Line Length",
7720                description: "The column at which to soft-wrap lines, for buffers where soft-wrap is enabled.",
7721                field: Box::new(SettingField {
7722                    json_path: Some("languages.$(language).preferred_line_length"),
7723                    pick: |settings_content| {
7724                        language_settings_field(settings_content, |language| {
7725                            language.preferred_line_length.as_ref()
7726                        })
7727                    },
7728                    write: |settings_content, value| {
7729                        language_settings_field_mut(settings_content, value, |language, value| {
7730                            language.preferred_line_length = value;
7731                        })
7732                    },
7733                }),
7734                metadata: None,
7735                files: USER | PROJECT,
7736            }),
7737            SettingsPageItem::SettingItem(SettingItem {
7738                title: "Wrap Guides",
7739                description: "Character counts at which to show wrap guides in the editor.",
7740                field: Box::new(
7741                    SettingField {
7742                        json_path: Some("languages.$(language).wrap_guides"),
7743                        pick: |settings_content| {
7744                            language_settings_field(settings_content, |language| {
7745                                language.wrap_guides.as_ref()
7746                            })
7747                        },
7748                        write: |settings_content, value| {
7749                            language_settings_field_mut(
7750                                settings_content,
7751                                value,
7752                                |language, value| {
7753                                    language.wrap_guides = value;
7754                                },
7755                            )
7756                        },
7757                    }
7758                    .unimplemented(),
7759                ),
7760                metadata: None,
7761                files: USER | PROJECT,
7762            }),
7763            SettingsPageItem::SettingItem(SettingItem {
7764                title: "Allow Rewrap",
7765                description: "Controls where the `editor::rewrap` action is allowed for this language.",
7766                field: Box::new(SettingField {
7767                    json_path: Some("languages.$(language).allow_rewrap"),
7768                    pick: |settings_content| {
7769                        language_settings_field(settings_content, |language| {
7770                            language.allow_rewrap.as_ref()
7771                        })
7772                    },
7773                    write: |settings_content, value| {
7774                        language_settings_field_mut(settings_content, value, |language, value| {
7775                            language.allow_rewrap = value;
7776                        })
7777                    },
7778                }),
7779                metadata: None,
7780                files: USER | PROJECT,
7781            }),
7782        ]
7783    }
7784
7785    fn indent_guides_section() -> [SettingsPageItem; 6] {
7786        [
7787            SettingsPageItem::SectionHeader("Indent Guides"),
7788            SettingsPageItem::SettingItem(SettingItem {
7789                title: "Enabled",
7790                description: "Display indent guides in the editor.",
7791                field: Box::new(SettingField {
7792                    json_path: Some("languages.$(language).indent_guides.enabled"),
7793                    pick: |settings_content| {
7794                        language_settings_field(settings_content, |language| {
7795                            language
7796                                .indent_guides
7797                                .as_ref()
7798                                .and_then(|indent_guides| indent_guides.enabled.as_ref())
7799                        })
7800                    },
7801                    write: |settings_content, value| {
7802                        language_settings_field_mut(settings_content, value, |language, value| {
7803                            language.indent_guides.get_or_insert_default().enabled = value;
7804                        })
7805                    },
7806                }),
7807                metadata: None,
7808                files: USER | PROJECT,
7809            }),
7810            SettingsPageItem::SettingItem(SettingItem {
7811                title: "Line Width",
7812                description: "The width of the indent guides in pixels, between 1 and 10.",
7813                field: Box::new(SettingField {
7814                    json_path: Some("languages.$(language).indent_guides.line_width"),
7815                    pick: |settings_content| {
7816                        language_settings_field(settings_content, |language| {
7817                            language
7818                                .indent_guides
7819                                .as_ref()
7820                                .and_then(|indent_guides| indent_guides.line_width.as_ref())
7821                        })
7822                    },
7823                    write: |settings_content, value| {
7824                        language_settings_field_mut(settings_content, value, |language, value| {
7825                            language.indent_guides.get_or_insert_default().line_width = value;
7826                        })
7827                    },
7828                }),
7829                metadata: None,
7830                files: USER | PROJECT,
7831            }),
7832            SettingsPageItem::SettingItem(SettingItem {
7833                title: "Active Line Width",
7834                description: "The width of the active indent guide in pixels, between 1 and 10.",
7835                field: Box::new(SettingField {
7836                    json_path: Some("languages.$(language).indent_guides.active_line_width"),
7837                    pick: |settings_content| {
7838                        language_settings_field(settings_content, |language| {
7839                            language
7840                                .indent_guides
7841                                .as_ref()
7842                                .and_then(|indent_guides| indent_guides.active_line_width.as_ref())
7843                        })
7844                    },
7845                    write: |settings_content, value| {
7846                        language_settings_field_mut(settings_content, value, |language, value| {
7847                            language
7848                                .indent_guides
7849                                .get_or_insert_default()
7850                                .active_line_width = value;
7851                        })
7852                    },
7853                }),
7854                metadata: None,
7855                files: USER | PROJECT,
7856            }),
7857            SettingsPageItem::SettingItem(SettingItem {
7858                title: "Coloring",
7859                description: "Determines how indent guides are colored.",
7860                field: Box::new(SettingField {
7861                    json_path: Some("languages.$(language).indent_guides.coloring"),
7862                    pick: |settings_content| {
7863                        language_settings_field(settings_content, |language| {
7864                            language
7865                                .indent_guides
7866                                .as_ref()
7867                                .and_then(|indent_guides| indent_guides.coloring.as_ref())
7868                        })
7869                    },
7870                    write: |settings_content, value| {
7871                        language_settings_field_mut(settings_content, value, |language, value| {
7872                            language.indent_guides.get_or_insert_default().coloring = value;
7873                        })
7874                    },
7875                }),
7876                metadata: None,
7877                files: USER | PROJECT,
7878            }),
7879            SettingsPageItem::SettingItem(SettingItem {
7880                title: "Background Coloring",
7881                description: "Determines how indent guide backgrounds are colored.",
7882                field: Box::new(SettingField {
7883                    json_path: Some("languages.$(language).indent_guides.background_coloring"),
7884                    pick: |settings_content| {
7885                        language_settings_field(settings_content, |language| {
7886                            language.indent_guides.as_ref().and_then(|indent_guides| {
7887                                indent_guides.background_coloring.as_ref()
7888                            })
7889                        })
7890                    },
7891                    write: |settings_content, value| {
7892                        language_settings_field_mut(settings_content, value, |language, value| {
7893                            language
7894                                .indent_guides
7895                                .get_or_insert_default()
7896                                .background_coloring = value;
7897                        })
7898                    },
7899                }),
7900                metadata: None,
7901                files: USER | PROJECT,
7902            }),
7903        ]
7904    }
7905
7906    fn formatting_section() -> [SettingsPageItem; 7] {
7907        [
7908            SettingsPageItem::SectionHeader("Formatting"),
7909            SettingsPageItem::SettingItem(SettingItem {
7910                title: "Format On Save",
7911                description: "Whether or not to perform a buffer format before saving.",
7912                field: Box::new(
7913                    // TODO(settings_ui): this setting should just be a bool
7914                    SettingField {
7915                        json_path: Some("languages.$(language).format_on_save"),
7916                        pick: |settings_content| {
7917                            language_settings_field(settings_content, |language| {
7918                                language.format_on_save.as_ref()
7919                            })
7920                        },
7921                        write: |settings_content, value| {
7922                            language_settings_field_mut(
7923                                settings_content,
7924                                value,
7925                                |language, value| {
7926                                    language.format_on_save = value;
7927                                },
7928                            )
7929                        },
7930                    },
7931                ),
7932                metadata: None,
7933                files: USER | PROJECT,
7934            }),
7935            SettingsPageItem::SettingItem(SettingItem {
7936                title: "Remove Trailing Whitespace On Save",
7937                description: "Whether or not to remove any trailing whitespace from lines of a buffer before saving it.",
7938                field: Box::new(SettingField {
7939                    json_path: Some("languages.$(language).remove_trailing_whitespace_on_save"),
7940                    pick: |settings_content| {
7941                        language_settings_field(settings_content, |language| {
7942                            language.remove_trailing_whitespace_on_save.as_ref()
7943                        })
7944                    },
7945                    write: |settings_content, value| {
7946                        language_settings_field_mut(settings_content, value, |language, value| {
7947                            language.remove_trailing_whitespace_on_save = value;
7948                        })
7949                    },
7950                }),
7951                metadata: None,
7952                files: USER | PROJECT,
7953            }),
7954            SettingsPageItem::SettingItem(SettingItem {
7955                title: "Ensure Final Newline On Save",
7956                description: "Whether or not to ensure there's a single newline at the end of a buffer when saving it.",
7957                field: Box::new(SettingField {
7958                    json_path: Some("languages.$(language).ensure_final_newline_on_save"),
7959                    pick: |settings_content| {
7960                        language_settings_field(settings_content, |language| {
7961                            language.ensure_final_newline_on_save.as_ref()
7962                        })
7963                    },
7964                    write: |settings_content, value| {
7965                        language_settings_field_mut(settings_content, value, |language, value| {
7966                            language.ensure_final_newline_on_save = value;
7967                        })
7968                    },
7969                }),
7970                metadata: None,
7971                files: USER | PROJECT,
7972            }),
7973            SettingsPageItem::SettingItem(SettingItem {
7974                title: "Formatter",
7975                description: "How to perform a buffer format.",
7976                field: Box::new(
7977                    SettingField {
7978                        json_path: Some("languages.$(language).formatter"),
7979                        pick: |settings_content| {
7980                            language_settings_field(settings_content, |language| {
7981                                language.formatter.as_ref()
7982                            })
7983                        },
7984                        write: |settings_content, value| {
7985                            language_settings_field_mut(
7986                                settings_content,
7987                                value,
7988                                |language, value| {
7989                                    language.formatter = value;
7990                                },
7991                            )
7992                        },
7993                    }
7994                    .unimplemented(),
7995                ),
7996                metadata: None,
7997                files: USER | PROJECT,
7998            }),
7999            SettingsPageItem::SettingItem(SettingItem {
8000                title: "Use On Type Format",
8001                description: "Whether to use additional LSP queries to format (and amend) the code after every \"trigger\" symbol input, defined by LSP server capabilities",
8002                field: Box::new(SettingField {
8003                    json_path: Some("languages.$(language).use_on_type_format"),
8004                    pick: |settings_content| {
8005                        language_settings_field(settings_content, |language| {
8006                            language.use_on_type_format.as_ref()
8007                        })
8008                    },
8009                    write: |settings_content, value| {
8010                        language_settings_field_mut(settings_content, value, |language, value| {
8011                            language.use_on_type_format = value;
8012                        })
8013                    },
8014                }),
8015                metadata: None,
8016                files: USER | PROJECT,
8017            }),
8018            SettingsPageItem::SettingItem(SettingItem {
8019                title: "Code Actions On Format",
8020                description: "Additional code actions to run when formatting.",
8021                field: Box::new(
8022                    SettingField {
8023                        json_path: Some("languages.$(language).code_actions_on_format"),
8024                        pick: |settings_content| {
8025                            language_settings_field(settings_content, |language| {
8026                                language.code_actions_on_format.as_ref()
8027                            })
8028                        },
8029                        write: |settings_content, value| {
8030                            language_settings_field_mut(
8031                                settings_content,
8032                                value,
8033                                |language, value| {
8034                                    language.code_actions_on_format = value;
8035                                },
8036                            )
8037                        },
8038                    }
8039                    .unimplemented(),
8040                ),
8041                metadata: None,
8042                files: USER | PROJECT,
8043            }),
8044        ]
8045    }
8046
8047    fn autoclose_section() -> [SettingsPageItem; 5] {
8048        [
8049            SettingsPageItem::SectionHeader("Autoclose"),
8050            SettingsPageItem::SettingItem(SettingItem {
8051                title: "Use Autoclose",
8052                description: "Whether to automatically type closing characters for you. For example, when you type '(', Zed will automatically add a closing ')' at the correct position.",
8053                field: Box::new(SettingField {
8054                    json_path: Some("languages.$(language).use_autoclose"),
8055                    pick: |settings_content| {
8056                        language_settings_field(settings_content, |language| {
8057                            language.use_autoclose.as_ref()
8058                        })
8059                    },
8060                    write: |settings_content, value| {
8061                        language_settings_field_mut(settings_content, value, |language, value| {
8062                            language.use_autoclose = value;
8063                        })
8064                    },
8065                }),
8066                metadata: None,
8067                files: USER | PROJECT,
8068            }),
8069            SettingsPageItem::SettingItem(SettingItem {
8070                title: "Use Auto Surround",
8071                description: "Whether to automatically surround text with characters for you. For example, when you select text and type '(', Zed will automatically surround text with ().",
8072                field: Box::new(SettingField {
8073                    json_path: Some("languages.$(language).use_auto_surround"),
8074                    pick: |settings_content| {
8075                        language_settings_field(settings_content, |language| {
8076                            language.use_auto_surround.as_ref()
8077                        })
8078                    },
8079                    write: |settings_content, value| {
8080                        language_settings_field_mut(settings_content, value, |language, value| {
8081                            language.use_auto_surround = value;
8082                        })
8083                    },
8084                }),
8085                metadata: None,
8086                files: USER | PROJECT,
8087            }),
8088            SettingsPageItem::SettingItem(SettingItem {
8089                title: "Always Treat Brackets As Autoclosed",
8090                description: "Controls whether the closing characters are always skipped over and auto-removed no matter how they were inserted.",
8091                field: Box::new(SettingField {
8092                    json_path: Some("languages.$(language).always_treat_brackets_as_autoclosed"),
8093                    pick: |settings_content| {
8094                        language_settings_field(settings_content, |language| {
8095                            language.always_treat_brackets_as_autoclosed.as_ref()
8096                        })
8097                    },
8098                    write: |settings_content, value| {
8099                        language_settings_field_mut(settings_content, value, |language, value| {
8100                            language.always_treat_brackets_as_autoclosed = value;
8101                        })
8102                    },
8103                }),
8104                metadata: None,
8105                files: USER | PROJECT,
8106            }),
8107            SettingsPageItem::SettingItem(SettingItem {
8108                title: "JSX Tag Auto Close",
8109                description: "Whether to automatically close JSX tags.",
8110                field: Box::new(SettingField {
8111                    json_path: Some("languages.$(language).jsx_tag_auto_close"),
8112                    // TODO(settings_ui): this setting should just be a bool
8113                    pick: |settings_content| {
8114                        language_settings_field(settings_content, |language| {
8115                            language.jsx_tag_auto_close.as_ref()?.enabled.as_ref()
8116                        })
8117                    },
8118                    write: |settings_content, value| {
8119                        language_settings_field_mut(settings_content, value, |language, value| {
8120                            language.jsx_tag_auto_close.get_or_insert_default().enabled = value;
8121                        })
8122                    },
8123                }),
8124                metadata: None,
8125                files: USER | PROJECT,
8126            }),
8127        ]
8128    }
8129
8130    fn whitespace_section() -> [SettingsPageItem; 4] {
8131        [
8132            SettingsPageItem::SectionHeader("Whitespace"),
8133            SettingsPageItem::SettingItem(SettingItem {
8134                title: "Show Whitespaces",
8135                description: "Whether to show tabs and spaces in the editor.",
8136                field: Box::new(SettingField {
8137                    json_path: Some("languages.$(language).show_whitespaces"),
8138                    pick: |settings_content| {
8139                        language_settings_field(settings_content, |language| {
8140                            language.show_whitespaces.as_ref()
8141                        })
8142                    },
8143                    write: |settings_content, value| {
8144                        language_settings_field_mut(settings_content, value, |language, value| {
8145                            language.show_whitespaces = value;
8146                        })
8147                    },
8148                }),
8149                metadata: None,
8150                files: USER | PROJECT,
8151            }),
8152            SettingsPageItem::SettingItem(SettingItem {
8153                title: "Space Whitespace Indicator",
8154                description: "Visible character used to render space characters when show_whitespaces is enabled (default: \"\")",
8155                field: Box::new(
8156                    SettingField {
8157                        json_path: Some("languages.$(language).whitespace_map.space"),
8158                        pick: |settings_content| {
8159                            language_settings_field(settings_content, |language| {
8160                                language.whitespace_map.as_ref()?.space.as_ref()
8161                            })
8162                        },
8163                        write: |settings_content, value| {
8164                            language_settings_field_mut(
8165                                settings_content,
8166                                value,
8167                                |language, value| {
8168                                    language.whitespace_map.get_or_insert_default().space = value;
8169                                },
8170                            )
8171                        },
8172                    }
8173                    .unimplemented(),
8174                ),
8175                metadata: None,
8176                files: USER | PROJECT,
8177            }),
8178            SettingsPageItem::SettingItem(SettingItem {
8179                title: "Tab Whitespace Indicator",
8180                description: "Visible character used to render tab characters when show_whitespaces is enabled (default: \"\")",
8181                field: Box::new(
8182                    SettingField {
8183                        json_path: Some("languages.$(language).whitespace_map.tab"),
8184                        pick: |settings_content| {
8185                            language_settings_field(settings_content, |language| {
8186                                language.whitespace_map.as_ref()?.tab.as_ref()
8187                            })
8188                        },
8189                        write: |settings_content, value| {
8190                            language_settings_field_mut(
8191                                settings_content,
8192                                value,
8193                                |language, value| {
8194                                    language.whitespace_map.get_or_insert_default().tab = value;
8195                                },
8196                            )
8197                        },
8198                    }
8199                    .unimplemented(),
8200                ),
8201                metadata: None,
8202                files: USER | PROJECT,
8203            }),
8204        ]
8205    }
8206
8207    fn completions_section() -> [SettingsPageItem; 7] {
8208        [
8209            SettingsPageItem::SectionHeader("Completions"),
8210            SettingsPageItem::SettingItem(SettingItem {
8211                title: "Show Completions On Input",
8212                description: "Whether to pop the completions menu while typing in an editor without explicitly requesting it.",
8213                field: Box::new(SettingField {
8214                    json_path: Some("languages.$(language).show_completions_on_input"),
8215                    pick: |settings_content| {
8216                        language_settings_field(settings_content, |language| {
8217                            language.show_completions_on_input.as_ref()
8218                        })
8219                    },
8220                    write: |settings_content, value| {
8221                        language_settings_field_mut(settings_content, value, |language, value| {
8222                            language.show_completions_on_input = value;
8223                        })
8224                    },
8225                }),
8226                metadata: None,
8227                files: USER | PROJECT,
8228            }),
8229            SettingsPageItem::SettingItem(SettingItem {
8230                title: "Show Completion Documentation",
8231                description: "Whether to display inline and alongside documentation for items in the completions menu.",
8232                field: Box::new(SettingField {
8233                    json_path: Some("languages.$(language).show_completion_documentation"),
8234                    pick: |settings_content| {
8235                        language_settings_field(settings_content, |language| {
8236                            language.show_completion_documentation.as_ref()
8237                        })
8238                    },
8239                    write: |settings_content, value| {
8240                        language_settings_field_mut(settings_content, value, |language, value| {
8241                            language.show_completion_documentation = value;
8242                        })
8243                    },
8244                }),
8245                metadata: None,
8246                files: USER | PROJECT,
8247            }),
8248            SettingsPageItem::SettingItem(SettingItem {
8249                title: "Words",
8250                description: "Controls how words are completed.",
8251                field: Box::new(SettingField {
8252                    json_path: Some("languages.$(language).completions.words"),
8253                    pick: |settings_content| {
8254                        language_settings_field(settings_content, |language| {
8255                            language.completions.as_ref()?.words.as_ref()
8256                        })
8257                    },
8258                    write: |settings_content, value| {
8259                        language_settings_field_mut(settings_content, value, |language, value| {
8260                            language.completions.get_or_insert_default().words = value;
8261                        })
8262                    },
8263                }),
8264                metadata: None,
8265                files: USER | PROJECT,
8266            }),
8267            SettingsPageItem::SettingItem(SettingItem {
8268                title: "Words Min Length",
8269                description: "How many characters has to be in the completions query to automatically show the words-based completions.",
8270                field: Box::new(SettingField {
8271                    json_path: Some("languages.$(language).completions.words_min_length"),
8272                    pick: |settings_content| {
8273                        language_settings_field(settings_content, |language| {
8274                            language.completions.as_ref()?.words_min_length.as_ref()
8275                        })
8276                    },
8277                    write: |settings_content, value| {
8278                        language_settings_field_mut(settings_content, value, |language, value| {
8279                            language
8280                                .completions
8281                                .get_or_insert_default()
8282                                .words_min_length = value;
8283                        })
8284                    },
8285                }),
8286                metadata: None,
8287                files: USER | PROJECT,
8288            }),
8289            SettingsPageItem::SettingItem(SettingItem {
8290                title: "Completion Menu Scrollbar",
8291                description: "When to show the scrollbar in the completion menu.",
8292                field: Box::new(SettingField {
8293                    json_path: Some("editor.completion_menu_scrollbar"),
8294                    pick: |settings_content| {
8295                        settings_content.editor.completion_menu_scrollbar.as_ref()
8296                    },
8297                    write: |settings_content, value| {
8298                        settings_content.editor.completion_menu_scrollbar = value;
8299                    },
8300                }),
8301                metadata: None,
8302                files: USER,
8303            }),
8304            SettingsPageItem::SettingItem(SettingItem {
8305                title: "Completion Detail Alignment",
8306                description: "Whether to align detail text in code completions context menus left or right.",
8307                field: Box::new(SettingField {
8308                    json_path: Some("editor.completion_detail_alignment"),
8309                    pick: |settings_content| {
8310                        settings_content.editor.completion_detail_alignment.as_ref()
8311                    },
8312                    write: |settings_content, value| {
8313                        settings_content.editor.completion_detail_alignment = value;
8314                    },
8315                }),
8316                metadata: None,
8317                files: USER,
8318            }),
8319        ]
8320    }
8321
8322    fn inlay_hints_section() -> [SettingsPageItem; 10] {
8323        [
8324            SettingsPageItem::SectionHeader("Inlay Hints"),
8325            SettingsPageItem::SettingItem(SettingItem {
8326                title: "Enabled",
8327                description: "Global switch to toggle hints on and off.",
8328                field: Box::new(SettingField {
8329                    json_path: Some("languages.$(language).inlay_hints.enabled"),
8330                    pick: |settings_content| {
8331                        language_settings_field(settings_content, |language| {
8332                            language.inlay_hints.as_ref()?.enabled.as_ref()
8333                        })
8334                    },
8335                    write: |settings_content, value| {
8336                        language_settings_field_mut(settings_content, value, |language, value| {
8337                            language.inlay_hints.get_or_insert_default().enabled = value;
8338                        })
8339                    },
8340                }),
8341                metadata: None,
8342                files: USER | PROJECT,
8343            }),
8344            SettingsPageItem::SettingItem(SettingItem {
8345                title: "Show Value Hints",
8346                description: "Global switch to toggle inline values on and off when debugging.",
8347                field: Box::new(SettingField {
8348                    json_path: Some("languages.$(language).inlay_hints.show_value_hints"),
8349                    pick: |settings_content| {
8350                        language_settings_field(settings_content, |language| {
8351                            language.inlay_hints.as_ref()?.show_value_hints.as_ref()
8352                        })
8353                    },
8354                    write: |settings_content, value| {
8355                        language_settings_field_mut(settings_content, value, |language, value| {
8356                            language
8357                                .inlay_hints
8358                                .get_or_insert_default()
8359                                .show_value_hints = value;
8360                        })
8361                    },
8362                }),
8363                metadata: None,
8364                files: USER | PROJECT,
8365            }),
8366            SettingsPageItem::SettingItem(SettingItem {
8367                title: "Show Type Hints",
8368                description: "Whether type hints should be shown.",
8369                field: Box::new(SettingField {
8370                    json_path: Some("languages.$(language).inlay_hints.show_type_hints"),
8371                    pick: |settings_content| {
8372                        language_settings_field(settings_content, |language| {
8373                            language.inlay_hints.as_ref()?.show_type_hints.as_ref()
8374                        })
8375                    },
8376                    write: |settings_content, value| {
8377                        language_settings_field_mut(settings_content, value, |language, value| {
8378                            language.inlay_hints.get_or_insert_default().show_type_hints = value;
8379                        })
8380                    },
8381                }),
8382                metadata: None,
8383                files: USER | PROJECT,
8384            }),
8385            SettingsPageItem::SettingItem(SettingItem {
8386                title: "Show Parameter Hints",
8387                description: "Whether parameter hints should be shown.",
8388                field: Box::new(SettingField {
8389                    json_path: Some("languages.$(language).inlay_hints.show_parameter_hints"),
8390                    pick: |settings_content| {
8391                        language_settings_field(settings_content, |language| {
8392                            language.inlay_hints.as_ref()?.show_parameter_hints.as_ref()
8393                        })
8394                    },
8395                    write: |settings_content, value| {
8396                        language_settings_field_mut(settings_content, value, |language, value| {
8397                            language
8398                                .inlay_hints
8399                                .get_or_insert_default()
8400                                .show_parameter_hints = value;
8401                        })
8402                    },
8403                }),
8404                metadata: None,
8405                files: USER | PROJECT,
8406            }),
8407            SettingsPageItem::SettingItem(SettingItem {
8408                title: "Show Other Hints",
8409                description: "Whether other hints should be shown.",
8410                field: Box::new(SettingField {
8411                    json_path: Some("languages.$(language).inlay_hints.show_other_hints"),
8412                    pick: |settings_content| {
8413                        language_settings_field(settings_content, |language| {
8414                            language.inlay_hints.as_ref()?.show_other_hints.as_ref()
8415                        })
8416                    },
8417                    write: |settings_content, value| {
8418                        language_settings_field_mut(settings_content, value, |language, value| {
8419                            language
8420                                .inlay_hints
8421                                .get_or_insert_default()
8422                                .show_other_hints = value;
8423                        })
8424                    },
8425                }),
8426                metadata: None,
8427                files: USER | PROJECT,
8428            }),
8429            SettingsPageItem::SettingItem(SettingItem {
8430                title: "Show Background",
8431                description: "Show a background for inlay hints.",
8432                field: Box::new(SettingField {
8433                    json_path: Some("languages.$(language).inlay_hints.show_background"),
8434                    pick: |settings_content| {
8435                        language_settings_field(settings_content, |language| {
8436                            language.inlay_hints.as_ref()?.show_background.as_ref()
8437                        })
8438                    },
8439                    write: |settings_content, value| {
8440                        language_settings_field_mut(settings_content, value, |language, value| {
8441                            language.inlay_hints.get_or_insert_default().show_background = value;
8442                        })
8443                    },
8444                }),
8445                metadata: None,
8446                files: USER | PROJECT,
8447            }),
8448            SettingsPageItem::SettingItem(SettingItem {
8449                title: "Edit Debounce Ms",
8450                description: "Whether or not to debounce inlay hints updates after buffer edits (set to 0 to disable debouncing).",
8451                field: Box::new(SettingField {
8452                    json_path: Some("languages.$(language).inlay_hints.edit_debounce_ms"),
8453                    pick: |settings_content| {
8454                        language_settings_field(settings_content, |language| {
8455                            language.inlay_hints.as_ref()?.edit_debounce_ms.as_ref()
8456                        })
8457                    },
8458                    write: |settings_content, value| {
8459                        language_settings_field_mut(settings_content, value, |language, value| {
8460                            language
8461                                .inlay_hints
8462                                .get_or_insert_default()
8463                                .edit_debounce_ms = value;
8464                        })
8465                    },
8466                }),
8467                metadata: None,
8468                files: USER | PROJECT,
8469            }),
8470            SettingsPageItem::SettingItem(SettingItem {
8471                title: "Scroll Debounce Ms",
8472                description: "Whether or not to debounce inlay hints updates after buffer scrolls (set to 0 to disable debouncing).",
8473                field: Box::new(SettingField {
8474                    json_path: Some("languages.$(language).inlay_hints.scroll_debounce_ms"),
8475                    pick: |settings_content| {
8476                        language_settings_field(settings_content, |language| {
8477                            language.inlay_hints.as_ref()?.scroll_debounce_ms.as_ref()
8478                        })
8479                    },
8480                    write: |settings_content, value| {
8481                        language_settings_field_mut(settings_content, value, |language, value| {
8482                            language
8483                                .inlay_hints
8484                                .get_or_insert_default()
8485                                .scroll_debounce_ms = value;
8486                        })
8487                    },
8488                }),
8489                metadata: None,
8490                files: USER | PROJECT,
8491            }),
8492            SettingsPageItem::SettingItem(SettingItem {
8493                title: "Toggle On Modifiers Press",
8494                description: "Toggles inlay hints (hides or shows) when the user presses the modifiers specified.",
8495                field: Box::new(
8496                    SettingField {
8497                        json_path: Some(
8498                            "languages.$(language).inlay_hints.toggle_on_modifiers_press",
8499                        ),
8500                        pick: |settings_content| {
8501                            language_settings_field(settings_content, |language| {
8502                                language
8503                                    .inlay_hints
8504                                    .as_ref()?
8505                                    .toggle_on_modifiers_press
8506                                    .as_ref()
8507                            })
8508                        },
8509                        write: |settings_content, value| {
8510                            language_settings_field_mut(
8511                                settings_content,
8512                                value,
8513                                |language, value| {
8514                                    language
8515                                        .inlay_hints
8516                                        .get_or_insert_default()
8517                                        .toggle_on_modifiers_press = value;
8518                                },
8519                            )
8520                        },
8521                    }
8522                    .unimplemented(),
8523                ),
8524                metadata: None,
8525                files: USER | PROJECT,
8526            }),
8527        ]
8528    }
8529
8530    fn tasks_section() -> [SettingsPageItem; 4] {
8531        [
8532            SettingsPageItem::SectionHeader("Tasks"),
8533            SettingsPageItem::SettingItem(SettingItem {
8534                title: "Enabled",
8535                description: "Whether tasks are enabled for this language.",
8536                field: Box::new(SettingField {
8537                    json_path: Some("languages.$(language).tasks.enabled"),
8538                    pick: |settings_content| {
8539                        language_settings_field(settings_content, |language| {
8540                            language.tasks.as_ref()?.enabled.as_ref()
8541                        })
8542                    },
8543                    write: |settings_content, value| {
8544                        language_settings_field_mut(settings_content, value, |language, value| {
8545                            language.tasks.get_or_insert_default().enabled = value;
8546                        })
8547                    },
8548                }),
8549                metadata: None,
8550                files: USER | PROJECT,
8551            }),
8552            SettingsPageItem::SettingItem(SettingItem {
8553                title: "Variables",
8554                description: "Extra task variables to set for a particular language.",
8555                field: Box::new(
8556                    SettingField {
8557                        json_path: Some("languages.$(language).tasks.variables"),
8558                        pick: |settings_content| {
8559                            language_settings_field(settings_content, |language| {
8560                                language.tasks.as_ref()?.variables.as_ref()
8561                            })
8562                        },
8563                        write: |settings_content, value| {
8564                            language_settings_field_mut(
8565                                settings_content,
8566                                value,
8567                                |language, value| {
8568                                    language.tasks.get_or_insert_default().variables = value;
8569                                },
8570                            )
8571                        },
8572                    }
8573                    .unimplemented(),
8574                ),
8575                metadata: None,
8576                files: USER | PROJECT,
8577            }),
8578            SettingsPageItem::SettingItem(SettingItem {
8579                title: "Prefer LSP",
8580                description: "Use LSP tasks over Zed language extension tasks.",
8581                field: Box::new(SettingField {
8582                    json_path: Some("languages.$(language).tasks.prefer_lsp"),
8583                    pick: |settings_content| {
8584                        language_settings_field(settings_content, |language| {
8585                            language.tasks.as_ref()?.prefer_lsp.as_ref()
8586                        })
8587                    },
8588                    write: |settings_content, value| {
8589                        language_settings_field_mut(settings_content, value, |language, value| {
8590                            language.tasks.get_or_insert_default().prefer_lsp = value;
8591                        })
8592                    },
8593                }),
8594                metadata: None,
8595                files: USER | PROJECT,
8596            }),
8597        ]
8598    }
8599
8600    fn miscellaneous_section() -> [SettingsPageItem; 7] {
8601        [
8602            SettingsPageItem::SectionHeader("Miscellaneous"),
8603            SettingsPageItem::SettingItem(SettingItem {
8604                title: "Word Diff Enabled",
8605                description: "Whether to enable word diff highlighting in the editor. When enabled, changed words within modified lines are highlighted to show exactly what changed.",
8606                field: Box::new(SettingField {
8607                    json_path: Some("languages.$(language).word_diff_enabled"),
8608                    pick: |settings_content| {
8609                        language_settings_field(settings_content, |language| {
8610                            language.word_diff_enabled.as_ref()
8611                        })
8612                    },
8613                    write: |settings_content, value| {
8614                        language_settings_field_mut(settings_content, value, |language, value| {
8615                            language.word_diff_enabled = value;
8616                        })
8617                    },
8618                }),
8619                metadata: None,
8620                files: USER | PROJECT,
8621            }),
8622            SettingsPageItem::SettingItem(SettingItem {
8623                title: "Debuggers",
8624                description: "Preferred debuggers for this language.",
8625                field: Box::new(
8626                    SettingField {
8627                        json_path: Some("languages.$(language).debuggers"),
8628                        pick: |settings_content| {
8629                            language_settings_field(settings_content, |language| {
8630                                language.debuggers.as_ref()
8631                            })
8632                        },
8633                        write: |settings_content, value| {
8634                            language_settings_field_mut(
8635                                settings_content,
8636                                value,
8637                                |language, value| {
8638                                    language.debuggers = value;
8639                                },
8640                            )
8641                        },
8642                    }
8643                    .unimplemented(),
8644                ),
8645                metadata: None,
8646                files: USER | PROJECT,
8647            }),
8648            SettingsPageItem::SettingItem(SettingItem {
8649                title: "Middle Click Paste",
8650                description: "Enable middle-click paste on Linux.",
8651                field: Box::new(SettingField {
8652                    json_path: Some("languages.$(language).editor.middle_click_paste"),
8653                    pick: |settings_content| settings_content.editor.middle_click_paste.as_ref(),
8654                    write: |settings_content, value| {
8655                        settings_content.editor.middle_click_paste = value;
8656                    },
8657                }),
8658                metadata: None,
8659                files: USER,
8660            }),
8661            SettingsPageItem::SettingItem(SettingItem {
8662                title: "Extend Comment On Newline",
8663                description: "Whether to start a new line with a comment when a previous line is a comment as well.",
8664                field: Box::new(SettingField {
8665                    json_path: Some("languages.$(language).extend_comment_on_newline"),
8666                    pick: |settings_content| {
8667                        language_settings_field(settings_content, |language| {
8668                            language.extend_comment_on_newline.as_ref()
8669                        })
8670                    },
8671                    write: |settings_content, value| {
8672                        language_settings_field_mut(settings_content, value, |language, value| {
8673                            language.extend_comment_on_newline = value;
8674                        })
8675                    },
8676                }),
8677                metadata: None,
8678                files: USER | PROJECT,
8679            }),
8680            SettingsPageItem::SettingItem(SettingItem {
8681                title: "Colorize Brackets",
8682                description: "Whether to colorize brackets in the editor.",
8683                field: Box::new(SettingField {
8684                    json_path: Some("languages.$(language).colorize_brackets"),
8685                    pick: |settings_content| {
8686                        language_settings_field(settings_content, |language| {
8687                            language.colorize_brackets.as_ref()
8688                        })
8689                    },
8690                    write: |settings_content, value| {
8691                        language_settings_field_mut(settings_content, value, |language, value| {
8692                            language.colorize_brackets = value;
8693                        })
8694                    },
8695                }),
8696                metadata: None,
8697                files: USER | PROJECT,
8698            }),
8699            SettingsPageItem::SettingItem(SettingItem {
8700                title: "Vim/Emacs Modeline Support",
8701                description: "Number of lines to search for modelines (set to 0 to disable).",
8702                field: Box::new(SettingField {
8703                    json_path: Some("modeline_lines"),
8704                    pick: |settings_content| settings_content.modeline_lines.as_ref(),
8705                    write: |settings_content, value| {
8706                        settings_content.modeline_lines = value;
8707                    },
8708                }),
8709                metadata: None,
8710                files: USER | PROJECT,
8711            }),
8712        ]
8713    }
8714
8715    fn global_only_miscellaneous_sub_section() -> [SettingsPageItem; 3] {
8716        [
8717            SettingsPageItem::SettingItem(SettingItem {
8718                title: "Image Viewer",
8719                description: "The unit for image file sizes.",
8720                field: Box::new(SettingField {
8721                    json_path: Some("image_viewer.unit"),
8722                    pick: |settings_content| {
8723                        settings_content
8724                            .image_viewer
8725                            .as_ref()
8726                            .and_then(|image_viewer| image_viewer.unit.as_ref())
8727                    },
8728                    write: |settings_content, value| {
8729                        settings_content.image_viewer.get_or_insert_default().unit = value;
8730                    },
8731                }),
8732                metadata: None,
8733                files: USER,
8734            }),
8735            SettingsPageItem::SettingItem(SettingItem {
8736                title: "Auto Replace Emoji Shortcode",
8737                description: "Whether to automatically replace emoji shortcodes with emoji characters.",
8738                field: Box::new(SettingField {
8739                    json_path: Some("message_editor.auto_replace_emoji_shortcode"),
8740                    pick: |settings_content| {
8741                        settings_content
8742                            .message_editor
8743                            .as_ref()
8744                            .and_then(|message_editor| {
8745                                message_editor.auto_replace_emoji_shortcode.as_ref()
8746                            })
8747                    },
8748                    write: |settings_content, value| {
8749                        settings_content
8750                            .message_editor
8751                            .get_or_insert_default()
8752                            .auto_replace_emoji_shortcode = value;
8753                    },
8754                }),
8755                metadata: None,
8756                files: USER,
8757            }),
8758            SettingsPageItem::SettingItem(SettingItem {
8759                title: "Drop Size Target",
8760                description: "Relative size of the drop target in the editor that will open dropped file as a split pane.",
8761                field: Box::new(SettingField {
8762                    json_path: Some("drop_target_size"),
8763                    pick: |settings_content| settings_content.workspace.drop_target_size.as_ref(),
8764                    write: |settings_content, value| {
8765                        settings_content.workspace.drop_target_size = value;
8766                    },
8767                }),
8768                metadata: None,
8769                files: USER,
8770            }),
8771        ]
8772    }
8773
8774    let is_global = active_language().is_none();
8775
8776    let lsp_document_colors_item = [SettingsPageItem::SettingItem(SettingItem {
8777        title: "LSP Document Colors",
8778        description: "How to render LSP color previews in the editor.",
8779        field: Box::new(SettingField {
8780            json_path: Some("lsp_document_colors"),
8781            pick: |settings_content| settings_content.editor.lsp_document_colors.as_ref(),
8782            write: |settings_content, value| {
8783                settings_content.editor.lsp_document_colors = value;
8784            },
8785        }),
8786        metadata: None,
8787        files: USER,
8788    })];
8789
8790    if is_global {
8791        concat_sections!(
8792            indentation_section(),
8793            wrapping_section(),
8794            indent_guides_section(),
8795            formatting_section(),
8796            autoclose_section(),
8797            whitespace_section(),
8798            completions_section(),
8799            inlay_hints_section(),
8800            lsp_document_colors_item,
8801            tasks_section(),
8802            miscellaneous_section(),
8803            global_only_miscellaneous_sub_section(),
8804        )
8805    } else {
8806        concat_sections!(
8807            indentation_section(),
8808            wrapping_section(),
8809            indent_guides_section(),
8810            formatting_section(),
8811            autoclose_section(),
8812            whitespace_section(),
8813            completions_section(),
8814            inlay_hints_section(),
8815            tasks_section(),
8816            miscellaneous_section(),
8817        )
8818    }
8819}
8820
8821/// LanguageSettings items that should be included in the "Languages & Tools" page
8822/// not the "Editor" page
8823fn non_editor_language_settings_data() -> Box<[SettingsPageItem]> {
8824    fn lsp_section() -> [SettingsPageItem; 8] {
8825        [
8826            SettingsPageItem::SectionHeader("LSP"),
8827            SettingsPageItem::SettingItem(SettingItem {
8828                title: "Enable Language Server",
8829                description: "Whether to use language servers to provide code intelligence.",
8830                field: Box::new(SettingField {
8831                    json_path: Some("languages.$(language).enable_language_server"),
8832                    pick: |settings_content| {
8833                        language_settings_field(settings_content, |language| {
8834                            language.enable_language_server.as_ref()
8835                        })
8836                    },
8837                    write: |settings_content, value| {
8838                        language_settings_field_mut(settings_content, value, |language, value| {
8839                            language.enable_language_server = value;
8840                        })
8841                    },
8842                }),
8843                metadata: None,
8844                files: USER | PROJECT,
8845            }),
8846            SettingsPageItem::SettingItem(SettingItem {
8847                title: "Language Servers",
8848                description: "The list of language servers to use (or disable) for this language.",
8849                field: Box::new(
8850                    SettingField {
8851                        json_path: Some("languages.$(language).language_servers"),
8852                        pick: |settings_content| {
8853                            language_settings_field(settings_content, |language| {
8854                                language.language_servers.as_ref()
8855                            })
8856                        },
8857                        write: |settings_content, value| {
8858                            language_settings_field_mut(
8859                                settings_content,
8860                                value,
8861                                |language, value| {
8862                                    language.language_servers = value;
8863                                },
8864                            )
8865                        },
8866                    }
8867                    .unimplemented(),
8868                ),
8869                metadata: None,
8870                files: USER | PROJECT,
8871            }),
8872            SettingsPageItem::SettingItem(SettingItem {
8873                title: "Linked Edits",
8874                description: "Whether to perform linked edits of associated ranges, if the LS supports it. For example, when editing opening <html> tag, the contents of the closing </html> tag will be edited as well.",
8875                field: Box::new(SettingField {
8876                    json_path: Some("languages.$(language).linked_edits"),
8877                    pick: |settings_content| {
8878                        language_settings_field(settings_content, |language| {
8879                            language.linked_edits.as_ref()
8880                        })
8881                    },
8882                    write: |settings_content, value| {
8883                        language_settings_field_mut(settings_content, value, |language, value| {
8884                            language.linked_edits = value;
8885                        })
8886                    },
8887                }),
8888                metadata: None,
8889                files: USER | PROJECT,
8890            }),
8891            SettingsPageItem::SettingItem(SettingItem {
8892                title: "Go To Definition Fallback",
8893                description: "Whether to follow-up empty Go to definition responses from the language server.",
8894                field: Box::new(SettingField {
8895                    json_path: Some("go_to_definition_fallback"),
8896                    pick: |settings_content| {
8897                        settings_content.editor.go_to_definition_fallback.as_ref()
8898                    },
8899                    write: |settings_content, value| {
8900                        settings_content.editor.go_to_definition_fallback = value;
8901                    },
8902                }),
8903                metadata: None,
8904                files: USER,
8905            }),
8906            SettingsPageItem::SettingItem(SettingItem {
8907                title: "Semantic Tokens",
8908                description: {
8909                    static DESCRIPTION: OnceLock<&'static str> = OnceLock::new();
8910                    DESCRIPTION.get_or_init(|| {
8911                        SemanticTokens::VARIANTS
8912                            .iter()
8913                            .filter_map(|v| {
8914                                v.get_documentation().map(|doc| format!("{v:?}: {doc}"))
8915                            })
8916                            .join("\n")
8917                            .leak()
8918                    })
8919                },
8920                field: Box::new(SettingField {
8921                    json_path: Some("languages.$(language).semantic_tokens"),
8922                    pick: |settings_content| {
8923                        settings_content
8924                            .project
8925                            .all_languages
8926                            .defaults
8927                            .semantic_tokens
8928                            .as_ref()
8929                    },
8930                    write: |settings_content, value| {
8931                        settings_content
8932                            .project
8933                            .all_languages
8934                            .defaults
8935                            .semantic_tokens = value;
8936                    },
8937                }),
8938                metadata: None,
8939                files: USER | PROJECT,
8940            }),
8941            SettingsPageItem::SettingItem(SettingItem {
8942                title: "LSP Folding Ranges",
8943                description: "When enabled, use folding ranges from the language server instead of indent-based folding.",
8944                field: Box::new(SettingField {
8945                    json_path: Some("languages.$(language).document_folding_ranges"),
8946                    pick: |settings_content| {
8947                        language_settings_field(settings_content, |language| {
8948                            language.document_folding_ranges.as_ref()
8949                        })
8950                    },
8951                    write: |settings_content, value| {
8952                        language_settings_field_mut(settings_content, value, |language, value| {
8953                            language.document_folding_ranges = value;
8954                        })
8955                    },
8956                }),
8957                metadata: None,
8958                files: USER | PROJECT,
8959            }),
8960            SettingsPageItem::SettingItem(SettingItem {
8961                title: "LSP Document Symbols",
8962                description: "When enabled, use the language server's document symbols for outlines and breadcrumbs instead of tree-sitter.",
8963                field: Box::new(SettingField {
8964                    json_path: Some("languages.$(language).document_symbols"),
8965                    pick: |settings_content| {
8966                        language_settings_field(settings_content, |language| {
8967                            language.document_symbols.as_ref()
8968                        })
8969                    },
8970                    write: |settings_content, value| {
8971                        language_settings_field_mut(settings_content, value, |language, value| {
8972                            language.document_symbols = value;
8973                        })
8974                    },
8975                }),
8976                metadata: None,
8977                files: USER | PROJECT,
8978            }),
8979        ]
8980    }
8981
8982    fn lsp_completions_section() -> [SettingsPageItem; 4] {
8983        [
8984            SettingsPageItem::SectionHeader("LSP Completions"),
8985            SettingsPageItem::SettingItem(SettingItem {
8986                title: "Enabled",
8987                description: "Whether to fetch LSP completions or not.",
8988                field: Box::new(SettingField {
8989                    json_path: Some("languages.$(language).completions.lsp"),
8990                    pick: |settings_content| {
8991                        language_settings_field(settings_content, |language| {
8992                            language.completions.as_ref()?.lsp.as_ref()
8993                        })
8994                    },
8995                    write: |settings_content, value| {
8996                        language_settings_field_mut(settings_content, value, |language, value| {
8997                            language.completions.get_or_insert_default().lsp = value;
8998                        })
8999                    },
9000                }),
9001                metadata: None,
9002                files: USER | PROJECT,
9003            }),
9004            SettingsPageItem::SettingItem(SettingItem {
9005                title: "Fetch Timeout (milliseconds)",
9006                description: "When fetching LSP completions, determines how long to wait for a response of a particular server (set to 0 to wait indefinitely).",
9007                field: Box::new(SettingField {
9008                    json_path: Some("languages.$(language).completions.lsp_fetch_timeout_ms"),
9009                    pick: |settings_content| {
9010                        language_settings_field(settings_content, |language| {
9011                            language.completions.as_ref()?.lsp_fetch_timeout_ms.as_ref()
9012                        })
9013                    },
9014                    write: |settings_content, value| {
9015                        language_settings_field_mut(settings_content, value, |language, value| {
9016                            language
9017                                .completions
9018                                .get_or_insert_default()
9019                                .lsp_fetch_timeout_ms = value;
9020                        })
9021                    },
9022                }),
9023                metadata: None,
9024                files: USER | PROJECT,
9025            }),
9026            SettingsPageItem::SettingItem(SettingItem {
9027                title: "Insert Mode",
9028                description: "Controls how LSP completions are inserted.",
9029                field: Box::new(SettingField {
9030                    json_path: Some("languages.$(language).completions.lsp_insert_mode"),
9031                    pick: |settings_content| {
9032                        language_settings_field(settings_content, |language| {
9033                            language.completions.as_ref()?.lsp_insert_mode.as_ref()
9034                        })
9035                    },
9036                    write: |settings_content, value| {
9037                        language_settings_field_mut(settings_content, value, |language, value| {
9038                            language.completions.get_or_insert_default().lsp_insert_mode = value;
9039                        })
9040                    },
9041                }),
9042                metadata: None,
9043                files: USER | PROJECT,
9044            }),
9045        ]
9046    }
9047
9048    fn debugger_section() -> [SettingsPageItem; 2] {
9049        [
9050            SettingsPageItem::SectionHeader("Debuggers"),
9051            SettingsPageItem::SettingItem(SettingItem {
9052                title: "Debuggers",
9053                description: "Preferred debuggers for this language.",
9054                field: Box::new(
9055                    SettingField {
9056                        json_path: Some("languages.$(language).debuggers"),
9057                        pick: |settings_content| {
9058                            language_settings_field(settings_content, |language| {
9059                                language.debuggers.as_ref()
9060                            })
9061                        },
9062                        write: |settings_content, value| {
9063                            language_settings_field_mut(
9064                                settings_content,
9065                                value,
9066                                |language, value| {
9067                                    language.debuggers = value;
9068                                },
9069                            )
9070                        },
9071                    }
9072                    .unimplemented(),
9073                ),
9074                metadata: None,
9075                files: USER | PROJECT,
9076            }),
9077        ]
9078    }
9079
9080    fn prettier_section() -> [SettingsPageItem; 5] {
9081        [
9082            SettingsPageItem::SectionHeader("Prettier"),
9083            SettingsPageItem::SettingItem(SettingItem {
9084                title: "Allowed",
9085                description: "Enables or disables formatting with Prettier for a given language.",
9086                field: Box::new(SettingField {
9087                    json_path: Some("languages.$(language).prettier.allowed"),
9088                    pick: |settings_content| {
9089                        language_settings_field(settings_content, |language| {
9090                            language.prettier.as_ref()?.allowed.as_ref()
9091                        })
9092                    },
9093                    write: |settings_content, value| {
9094                        language_settings_field_mut(settings_content, value, |language, value| {
9095                            language.prettier.get_or_insert_default().allowed = value;
9096                        })
9097                    },
9098                }),
9099                metadata: None,
9100                files: USER | PROJECT,
9101            }),
9102            SettingsPageItem::SettingItem(SettingItem {
9103                title: "Parser",
9104                description: "Forces Prettier integration to use a specific parser name when formatting files with the language.",
9105                field: Box::new(SettingField {
9106                    json_path: Some("languages.$(language).prettier.parser"),
9107                    pick: |settings_content| {
9108                        language_settings_field(settings_content, |language| {
9109                            language.prettier.as_ref()?.parser.as_ref()
9110                        })
9111                    },
9112                    write: |settings_content, value| {
9113                        language_settings_field_mut(settings_content, value, |language, value| {
9114                            language.prettier.get_or_insert_default().parser = value;
9115                        })
9116                    },
9117                }),
9118                metadata: None,
9119                files: USER | PROJECT,
9120            }),
9121            SettingsPageItem::SettingItem(SettingItem {
9122                title: "Plugins",
9123                description: "Forces Prettier integration to use specific plugins when formatting files with the language.",
9124                field: Box::new(
9125                    SettingField {
9126                        json_path: Some("languages.$(language).prettier.plugins"),
9127                        pick: |settings_content| {
9128                            language_settings_field(settings_content, |language| {
9129                                language.prettier.as_ref()?.plugins.as_ref()
9130                            })
9131                        },
9132                        write: |settings_content, value| {
9133                            language_settings_field_mut(
9134                                settings_content,
9135                                value,
9136                                |language, value| {
9137                                    language.prettier.get_or_insert_default().plugins = value;
9138                                },
9139                            )
9140                        },
9141                    }
9142                    .unimplemented(),
9143                ),
9144                metadata: None,
9145                files: USER | PROJECT,
9146            }),
9147            SettingsPageItem::SettingItem(SettingItem {
9148                title: "Options",
9149                description: "Default Prettier options, in the format as in package.json section for Prettier.",
9150                field: Box::new(
9151                    SettingField {
9152                        json_path: Some("languages.$(language).prettier.options"),
9153                        pick: |settings_content| {
9154                            language_settings_field(settings_content, |language| {
9155                                language.prettier.as_ref()?.options.as_ref()
9156                            })
9157                        },
9158                        write: |settings_content, value| {
9159                            language_settings_field_mut(
9160                                settings_content,
9161                                value,
9162                                |language, value| {
9163                                    language.prettier.get_or_insert_default().options = value;
9164                                },
9165                            )
9166                        },
9167                    }
9168                    .unimplemented(),
9169                ),
9170                metadata: None,
9171                files: USER | PROJECT,
9172            }),
9173        ]
9174    }
9175
9176    concat_sections!(
9177        lsp_section(),
9178        lsp_completions_section(),
9179        debugger_section(),
9180        prettier_section(),
9181    )
9182}
9183
9184fn edit_prediction_language_settings_section() -> [SettingsPageItem; 4] {
9185    [
9186        SettingsPageItem::SectionHeader("Edit Predictions"),
9187        SettingsPageItem::SubPageLink(SubPageLink {
9188            title: "Configure Providers".into(),
9189            r#type: Default::default(),
9190            json_path: Some("edit_predictions.providers"),
9191            description: Some("Set up different edit prediction providers in complement to Zed's built-in Zeta model.".into()),
9192            in_json: false,
9193            files: USER,
9194            render: render_edit_prediction_setup_page
9195        }),
9196        SettingsPageItem::SettingItem(SettingItem {
9197            title: "Show Edit Predictions",
9198            description: "Controls whether edit predictions are shown immediately or manually.",
9199            field: Box::new(SettingField {
9200                json_path: Some("languages.$(language).show_edit_predictions"),
9201                pick: |settings_content| {
9202                    language_settings_field(settings_content, |language| {
9203                        language.show_edit_predictions.as_ref()
9204                    })
9205                },
9206                write: |settings_content, value| {
9207                    language_settings_field_mut(settings_content, value, |language, value| {
9208                        language.show_edit_predictions = value;
9209                    })
9210                },
9211            }),
9212            metadata: None,
9213            files: USER | PROJECT,
9214        }),
9215        SettingsPageItem::SettingItem(SettingItem {
9216            title: "Disable in Language Scopes",
9217            description: "Controls whether edit predictions are shown in the given language scopes.",
9218            field: Box::new(
9219                SettingField {
9220                    json_path: Some("languages.$(language).edit_predictions_disabled_in"),
9221                    pick: |settings_content| {
9222                        language_settings_field(settings_content, |language| {
9223                            language.edit_predictions_disabled_in.as_ref()
9224                        })
9225                    },
9226                    write: |settings_content, value| {
9227                        language_settings_field_mut(settings_content, value, |language, value| {
9228                            language.edit_predictions_disabled_in = value;
9229                        })
9230                    },
9231                }
9232                .unimplemented(),
9233            ),
9234            metadata: None,
9235            files: USER | PROJECT,
9236        }),
9237    ]
9238}
9239
9240fn show_scrollbar_or_editor(
9241    settings_content: &SettingsContent,
9242    show: fn(&SettingsContent) -> Option<&settings::ShowScrollbar>,
9243) -> Option<&settings::ShowScrollbar> {
9244    show(settings_content).or(settings_content
9245        .editor
9246        .scrollbar
9247        .as_ref()
9248        .and_then(|scrollbar| scrollbar.show.as_ref()))
9249}
9250
9251fn dynamic_variants<T>() -> &'static [T::Discriminant]
9252where
9253    T: strum::IntoDiscriminant,
9254    T::Discriminant: strum::VariantArray,
9255{
9256    <<T as strum::IntoDiscriminant>::Discriminant as strum::VariantArray>::VARIANTS
9257}
9258
9259/// Updates the `vim_mode` setting, disabling `helix_mode` if present and
9260/// `vim_mode` is being enabled.
9261fn write_vim_mode(settings: &mut SettingsContent, value: Option<bool>) {
9262    if value == Some(true) && settings.helix_mode == Some(true) {
9263        settings.helix_mode = Some(false);
9264    }
9265    settings.vim_mode = value;
9266}
9267
9268/// Updates the `helix_mode` setting, disabling `vim_mode` if present and
9269/// `helix_mode` is being enabled.
9270fn write_helix_mode(settings: &mut SettingsContent, value: Option<bool>) {
9271    if value == Some(true) && settings.vim_mode == Some(true) {
9272        settings.vim_mode = Some(false);
9273    }
9274    settings.helix_mode = value;
9275}
9276
9277#[cfg(test)]
9278mod tests {
9279    use super::*;
9280
9281    #[test]
9282    fn test_write_vim_helix_mode() {
9283        // Enabling vim mode while `vim_mode` and `helix_mode` are not yet set
9284        // should only update the `vim_mode` setting.
9285        let mut settings = SettingsContent::default();
9286        write_vim_mode(&mut settings, Some(true));
9287        assert_eq!(settings.vim_mode, Some(true));
9288        assert_eq!(settings.helix_mode, None);
9289
9290        // Enabling helix mode while `vim_mode` and `helix_mode` are not yet set
9291        // should only update the `helix_mode` setting.
9292        let mut settings = SettingsContent::default();
9293        write_helix_mode(&mut settings, Some(true));
9294        assert_eq!(settings.helix_mode, Some(true));
9295        assert_eq!(settings.vim_mode, None);
9296
9297        // Disabling helix mode should only touch `helix_mode` setting when
9298        // `vim_mode` is not set.
9299        write_helix_mode(&mut settings, Some(false));
9300        assert_eq!(settings.helix_mode, Some(false));
9301        assert_eq!(settings.vim_mode, None);
9302
9303        // Enabling vim mode should update `vim_mode` but leave `helix_mode`
9304        // untouched.
9305        write_vim_mode(&mut settings, Some(true));
9306        assert_eq!(settings.vim_mode, Some(true));
9307        assert_eq!(settings.helix_mode, Some(false));
9308
9309        // Enabling helix mode should update `helix_mode` and disable
9310        // `vim_mode`.
9311        write_helix_mode(&mut settings, Some(true));
9312        assert_eq!(settings.helix_mode, Some(true));
9313        assert_eq!(settings.vim_mode, Some(false));
9314
9315        // Enabling vim mode should update `vim_mode` and disable
9316        // `helix_mode`.
9317        write_vim_mode(&mut settings, Some(true));
9318        assert_eq!(settings.vim_mode, Some(true));
9319        assert_eq!(settings.helix_mode, Some(false));
9320    }
9321}