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