settings_ui.rs

   1//! # settings_ui
   2mod components;
   3use editor::{Editor, EditorEvent};
   4use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
   5use fuzzy::StringMatchCandidate;
   6use gpui::{
   7    App, AppContext as _, Context, Div, Entity, Global, IntoElement, ReadGlobal as _, Render,
   8    ScrollHandle, Stateful, Task, TitlebarOptions, UniformListScrollHandle, Window, WindowHandle,
   9    WindowOptions, actions, div, point, px, size, uniform_list,
  10};
  11use project::WorktreeId;
  12use settings::{
  13    BottomDockLayout, CloseWindowWhenNoItems, CursorShape, OnLastWindowClosed,
  14    RestoreOnStartupBehavior, SaturatingBool, SettingsContent, SettingsStore,
  15};
  16use std::{
  17    any::{Any, TypeId, type_name},
  18    cell::RefCell,
  19    collections::HashMap,
  20    ops::Range,
  21    rc::Rc,
  22    sync::{Arc, atomic::AtomicBool},
  23};
  24use ui::{
  25    ContextMenu, Divider, DropdownMenu, DropdownStyle, Switch, SwitchColor, TreeViewItem,
  26    prelude::*,
  27};
  28use util::{paths::PathStyle, rel_path::RelPath};
  29
  30use crate::components::SettingsEditor;
  31
  32#[derive(Clone, Copy)]
  33struct SettingField<T: 'static> {
  34    pick: fn(&SettingsContent) -> &Option<T>,
  35    pick_mut: fn(&mut SettingsContent) -> &mut Option<T>,
  36}
  37
  38trait AnySettingField {
  39    fn as_any(&self) -> &dyn Any;
  40    fn type_name(&self) -> &'static str;
  41    fn type_id(&self) -> TypeId;
  42    fn file_set_in(&self, file: SettingsUiFile, cx: &App) -> settings::SettingsFile;
  43}
  44
  45impl<T> AnySettingField for SettingField<T> {
  46    fn as_any(&self) -> &dyn Any {
  47        self
  48    }
  49
  50    fn type_name(&self) -> &'static str {
  51        type_name::<T>()
  52    }
  53
  54    fn type_id(&self) -> TypeId {
  55        TypeId::of::<T>()
  56    }
  57
  58    fn file_set_in(&self, file: SettingsUiFile, cx: &App) -> settings::SettingsFile {
  59        let (file, _) = cx
  60            .global::<SettingsStore>()
  61            .get_value_from_file(file.to_settings(), self.pick);
  62        return file;
  63    }
  64}
  65
  66#[derive(Default, Clone)]
  67struct SettingFieldRenderer {
  68    renderers: Rc<
  69        RefCell<
  70            HashMap<
  71                TypeId,
  72                Box<
  73                    dyn Fn(
  74                        &dyn AnySettingField,
  75                        SettingsUiFile,
  76                        Option<&SettingsFieldMetadata>,
  77                        &mut Window,
  78                        &mut App,
  79                    ) -> AnyElement,
  80                >,
  81            >,
  82        >,
  83    >,
  84}
  85
  86impl Global for SettingFieldRenderer {}
  87
  88impl SettingFieldRenderer {
  89    fn add_renderer<T: 'static>(
  90        &mut self,
  91        renderer: impl Fn(
  92            &SettingField<T>,
  93            SettingsUiFile,
  94            Option<&SettingsFieldMetadata>,
  95            &mut Window,
  96            &mut App,
  97        ) -> AnyElement
  98        + 'static,
  99    ) -> &mut Self {
 100        let key = TypeId::of::<T>();
 101        let renderer = Box::new(
 102            move |any_setting_field: &dyn AnySettingField,
 103                  settings_file: SettingsUiFile,
 104                  metadata: Option<&SettingsFieldMetadata>,
 105                  window: &mut Window,
 106                  cx: &mut App| {
 107                let field = any_setting_field
 108                    .as_any()
 109                    .downcast_ref::<SettingField<T>>()
 110                    .unwrap();
 111                renderer(field, settings_file, metadata, window, cx)
 112            },
 113        );
 114        self.renderers.borrow_mut().insert(key, renderer);
 115        self
 116    }
 117
 118    fn render(
 119        &self,
 120        any_setting_field: &dyn AnySettingField,
 121        settings_file: SettingsUiFile,
 122        metadata: Option<&SettingsFieldMetadata>,
 123        window: &mut Window,
 124        cx: &mut App,
 125    ) -> AnyElement {
 126        let key = any_setting_field.type_id();
 127        if let Some(renderer) = self.renderers.borrow().get(&key) {
 128            renderer(any_setting_field, settings_file, metadata, window, cx)
 129        } else {
 130            panic!(
 131                "No renderer found for type: {}",
 132                any_setting_field.type_name()
 133            )
 134        }
 135    }
 136}
 137
 138struct SettingsFieldMetadata {
 139    placeholder: Option<&'static str>,
 140}
 141
 142fn user_settings_data() -> Vec<SettingsPage> {
 143    vec![
 144        SettingsPage {
 145            title: "General Page",
 146            expanded: false,
 147            items: vec![
 148                SettingsPageItem::SectionHeader("General"),
 149                SettingsPageItem::SettingItem(SettingItem {
 150                    title: "Confirm Quit",
 151                    description: "Whether to confirm before quitting Zed",
 152                    field: Box::new(SettingField {
 153                        pick: |settings_content| &settings_content.workspace.confirm_quit,
 154                        pick_mut: |settings_content| &mut settings_content.workspace.confirm_quit,
 155                    }),
 156                    metadata: None,
 157                }),
 158                SettingsPageItem::SettingItem(SettingItem {
 159                    title: "Restore On Startup",
 160                    description: "Whether to restore previous session when opening Zed",
 161                    field: Box::new(SettingField {
 162                        pick: |settings_content| &settings_content.workspace.restore_on_startup,
 163                        pick_mut: |settings_content| {
 164                            &mut settings_content.workspace.restore_on_startup
 165                        },
 166                    }),
 167                    metadata: None,
 168                }),
 169                SettingsPageItem::SettingItem(SettingItem {
 170                    title: "Restore File State",
 171                    description: "Whether to restore previous file state when reopening",
 172                    field: Box::new(SettingField {
 173                        pick: |settings_content| &settings_content.workspace.restore_on_file_reopen,
 174                        pick_mut: |settings_content| {
 175                            &mut settings_content.workspace.restore_on_file_reopen
 176                        },
 177                    }),
 178                    metadata: None,
 179                }),
 180                SettingsPageItem::SettingItem(SettingItem {
 181                    title: "Close on File Delete",
 182                    description: "Whether to automatically close files that have been deleted",
 183                    field: Box::new(SettingField {
 184                        pick: |settings_content| &settings_content.workspace.close_on_file_delete,
 185                        pick_mut: |settings_content| {
 186                            &mut settings_content.workspace.close_on_file_delete
 187                        },
 188                    }),
 189                    metadata: None,
 190                }),
 191                SettingsPageItem::SettingItem(SettingItem {
 192                    title: "When Closing With No Tabs",
 193                    description: "What to do when using 'close active item' with no tabs",
 194                    field: Box::new(SettingField {
 195                        pick: |settings_content| {
 196                            &settings_content.workspace.when_closing_with_no_tabs
 197                        },
 198                        pick_mut: |settings_content| {
 199                            &mut settings_content.workspace.when_closing_with_no_tabs
 200                        },
 201                    }),
 202                    metadata: None,
 203                }),
 204                SettingsPageItem::SettingItem(SettingItem {
 205                    title: "On Last Window Closed",
 206                    description: "What to do when the last window is closed",
 207                    field: Box::new(SettingField {
 208                        pick: |settings_content| &settings_content.workspace.on_last_window_closed,
 209                        pick_mut: |settings_content| {
 210                            &mut settings_content.workspace.on_last_window_closed
 211                        },
 212                    }),
 213                    metadata: None,
 214                }),
 215                SettingsPageItem::SettingItem(SettingItem {
 216                    title: "Use System Path Prompts",
 217                    description: "Whether to use system dialogs for Open and Save As",
 218                    field: Box::new(SettingField {
 219                        pick: |settings_content| {
 220                            &settings_content.workspace.use_system_path_prompts
 221                        },
 222                        pick_mut: |settings_content| {
 223                            &mut settings_content.workspace.use_system_path_prompts
 224                        },
 225                    }),
 226                    metadata: None,
 227                }),
 228                SettingsPageItem::SettingItem(SettingItem {
 229                    title: "Use System Prompts",
 230                    description: "Whether to use system prompts for confirmations",
 231                    field: Box::new(SettingField {
 232                        pick: |settings_content| &settings_content.workspace.use_system_prompts,
 233                        pick_mut: |settings_content| {
 234                            &mut settings_content.workspace.use_system_prompts
 235                        },
 236                    }),
 237                    metadata: None,
 238                }),
 239                SettingsPageItem::SectionHeader("Scoped Settings"),
 240                // todo(settings_ui): Implement another setting item type that just shows an edit in settings.json
 241                // SettingsPageItem::SettingItem(SettingItem {
 242                //     title: "Preview Channel",
 243                //     description: "Which settings should be activated only in Preview build of Zed",
 244                //     field: Box::new(SettingField {
 245                //         pick: |settings_content| &settings_content.workspace.use_system_prompts,
 246                //         pick_mut: |settings_content| {
 247                //             &mut settings_content.workspace.use_system_prompts
 248                //         },
 249                //     }),
 250                //     metadata: None,
 251                // }),
 252                // SettingsPageItem::SettingItem(SettingItem {
 253                //     title: "Settings Profiles",
 254                //     description: "Any number of settings profiles that are temporarily applied on top of your existing user settings.",
 255                //     field: Box::new(SettingField {
 256                //         pick: |settings_content| &settings_content.workspace.use_system_prompts,
 257                //         pick_mut: |settings_content| {
 258                //             &mut settings_content.workspace.use_system_prompts
 259                //         },
 260                //     }),
 261                //     metadata: None,
 262                // }),
 263                SettingsPageItem::SectionHeader("Privacy"),
 264                SettingsPageItem::SettingItem(SettingItem {
 265                    title: "Telemetry Diagnostics",
 266                    description: "Send debug info like crash reports.",
 267                    field: Box::new(SettingField {
 268                        pick: |settings_content| {
 269                            if let Some(telemetry) = &settings_content.telemetry {
 270                                &telemetry.diagnostics
 271                            } else {
 272                                &None
 273                            }
 274                        },
 275                        pick_mut: |settings_content| {
 276                            &mut settings_content
 277                                .telemetry
 278                                .get_or_insert_default()
 279                                .diagnostics
 280                        },
 281                    }),
 282                    metadata: None,
 283                }),
 284                SettingsPageItem::SettingItem(SettingItem {
 285                    title: "Telemetry Metrics",
 286                    description: "Send anonymized usage data like what languages you're using Zed with.",
 287                    field: Box::new(SettingField {
 288                        pick: |settings_content| {
 289                            if let Some(telemetry) = &settings_content.telemetry {
 290                                &telemetry.metrics
 291                            } else {
 292                                &None
 293                            }
 294                        },
 295                        pick_mut: |settings_content| {
 296                            &mut settings_content.telemetry.get_or_insert_default().metrics
 297                        },
 298                    }),
 299                    metadata: None,
 300                }),
 301            ],
 302        },
 303        SettingsPage {
 304            title: "Appearance & Behavior",
 305            expanded: false,
 306            items: vec![
 307                SettingsPageItem::SectionHeader("Theme"),
 308                // todo(settings_ui): Figure out how we want to add these
 309                // SettingsPageItem::SettingItem(SettingItem {
 310                //     title: "Theme Mode",
 311                //     description: "How to select the theme",
 312                //     field: Box::new(SettingField {
 313                //         pick: |settings_content| &settings_content.theme.theme,
 314                //         pick_mut: |settings_content| &mut settings_content.theme.theme,
 315                //     }),
 316                //     metadata: None,
 317                // }),
 318                // SettingsPageItem::SettingItem(SettingItem {
 319                //     title: "Icon Theme",
 320                //     // todo(settings_ui)
 321                //     // This description is misleading because the icon theme is used in more places than the file explorer)
 322                //     description: "Choose the icon theme for file explorer",
 323                //     field: Box::new(SettingField {
 324                //         pick: |settings_content| &settings_content.theme.icon_theme,
 325                //         pick_mut: |settings_content| &mut settings_content.theme.icon_theme,
 326                //     }),
 327                //     metadata: None,
 328                // }),
 329                SettingsPageItem::SectionHeader("Layout"),
 330                SettingsPageItem::SettingItem(SettingItem {
 331                    title: "Bottom Dock Layout",
 332                    description: "Layout mode for the bottom dock",
 333                    field: Box::new(SettingField {
 334                        pick: |settings_content| &settings_content.workspace.bottom_dock_layout,
 335                        pick_mut: |settings_content| {
 336                            &mut settings_content.workspace.bottom_dock_layout
 337                        },
 338                    }),
 339                    metadata: None,
 340                }),
 341                SettingsPageItem::SettingItem(SettingItem {
 342                    title: "Zoomed Padding",
 343                    description: "Whether to show padding for zoomed panels",
 344                    field: Box::new(SettingField {
 345                        pick: |settings_content| &settings_content.workspace.zoomed_padding,
 346                        pick_mut: |settings_content| &mut settings_content.workspace.zoomed_padding,
 347                    }),
 348                    metadata: None,
 349                }),
 350                SettingsPageItem::SettingItem(SettingItem {
 351                    title: "Use System Window Tabs",
 352                    description: "Whether to allow windows to tab together based on the user's tabbing preference (macOS only)",
 353                    field: Box::new(SettingField {
 354                        pick: |settings_content| &settings_content.workspace.use_system_window_tabs,
 355                        pick_mut: |settings_content| {
 356                            &mut settings_content.workspace.use_system_window_tabs
 357                        },
 358                    }),
 359                    metadata: None,
 360                }),
 361                SettingsPageItem::SectionHeader("Fonts"),
 362                SettingsPageItem::SettingItem(SettingItem {
 363                    title: "Buffer Font Family",
 364                    description: "Font family for editor text",
 365                    field: Box::new(SettingField {
 366                        pick: |settings_content| &settings_content.theme.buffer_font_family,
 367                        pick_mut: |settings_content| &mut settings_content.theme.buffer_font_family,
 368                    }),
 369                    metadata: None,
 370                }),
 371                // todo(settings_ui): We need to implement a numeric stepper for these
 372                // SettingsPageItem::SettingItem(SettingItem {
 373                //     title: "Buffer Font Size",
 374                //     description: "Font size for editor text",
 375                //     field: Box::new(SettingField {
 376                //         pick: |settings_content| &settings_content.theme.buffer_font_size,
 377                //         pick_mut: |settings_content| &mut settings_content.theme.buffer_font_size,
 378                //     }),
 379                //     metadata: None,
 380                // }),
 381                // SettingsPageItem::SettingItem(SettingItem {
 382                //     title: "Buffer Font Weight",
 383                //     description: "Font weight for editor text (100-900)",
 384                //     field: Box::new(SettingField {
 385                //         pick: |settings_content| &settings_content.theme.buffer_font_weight,
 386                //         pick_mut: |settings_content| &mut settings_content.theme.buffer_font_weight,
 387                //     }),
 388                //     metadata: None,
 389                // }),
 390                SettingsPageItem::SettingItem(SettingItem {
 391                    title: "Buffer Line Height",
 392                    description: "Line height for editor text",
 393                    field: Box::new(SettingField {
 394                        pick: |settings_content| &settings_content.theme.buffer_line_height,
 395                        pick_mut: |settings_content| &mut settings_content.theme.buffer_line_height,
 396                    }),
 397                    metadata: None,
 398                }),
 399                SettingsPageItem::SettingItem(SettingItem {
 400                    title: "UI Font Family",
 401                    description: "Font family for UI elements",
 402                    field: Box::new(SettingField {
 403                        pick: |settings_content| &settings_content.theme.ui_font_family,
 404                        pick_mut: |settings_content| &mut settings_content.theme.ui_font_family,
 405                    }),
 406                    metadata: None,
 407                }),
 408                // todo(settings_ui): We need to implement a numeric stepper for these
 409                // SettingsPageItem::SettingItem(SettingItem {
 410                //     title: "UI Font Size",
 411                //     description: "Font size for UI elements",
 412                //     field: Box::new(SettingField {
 413                //         pick: |settings_content| &settings_content.theme.ui_font_size,
 414                //         pick_mut: |settings_content| &mut settings_content.theme.ui_font_size,
 415                //     }),
 416                //     metadata: None,
 417                // }),
 418                // SettingsPageItem::SettingItem(SettingItem {
 419                //     title: "UI Font Weight",
 420                //     description: "Font weight for UI elements (100-900)",
 421                //     field: Box::new(SettingField {
 422                //         pick: |settings_content| &settings_content.theme.ui_font_weight,
 423                //         pick_mut: |settings_content| &mut settings_content.theme.ui_font_weight,
 424                //     }),
 425                //     metadata: None,
 426                // }),
 427                SettingsPageItem::SectionHeader("Keymap"),
 428                SettingsPageItem::SettingItem(SettingItem {
 429                    title: "Base Keymap",
 430                    description: "The name of a base set of key bindings to use",
 431                    field: Box::new(SettingField {
 432                        pick: |settings_content| &settings_content.base_keymap,
 433                        pick_mut: |settings_content| &mut settings_content.base_keymap,
 434                    }),
 435                    metadata: None,
 436                }),
 437                // todo(settings_ui): Vim/Helix Mode should be apart of one type because it's undefined
 438                // behavior to have them both enabled at the same time
 439                SettingsPageItem::SettingItem(SettingItem {
 440                    title: "Vim Mode",
 441                    description: "Whether to enable vim modes and key bindings",
 442                    field: Box::new(SettingField {
 443                        pick: |settings_content| &settings_content.vim_mode,
 444                        pick_mut: |settings_content| &mut settings_content.vim_mode,
 445                    }),
 446                    metadata: None,
 447                }),
 448                SettingsPageItem::SettingItem(SettingItem {
 449                    title: "Helix Mode",
 450                    description: "Whether to enable helix modes and key bindings",
 451                    field: Box::new(SettingField {
 452                        pick: |settings_content| &settings_content.helix_mode,
 453                        pick_mut: |settings_content| &mut settings_content.helix_mode,
 454                    }),
 455                    metadata: None,
 456                }),
 457                SettingsPageItem::SettingItem(SettingItem {
 458                    title: "Multi Cursor Modifier",
 459                    description: "Modifier key for adding multiple cursors",
 460                    field: Box::new(SettingField {
 461                        pick: |settings_content| &settings_content.editor.multi_cursor_modifier,
 462                        pick_mut: |settings_content| {
 463                            &mut settings_content.editor.multi_cursor_modifier
 464                        },
 465                    }),
 466                    metadata: None,
 467                }),
 468                SettingsPageItem::SectionHeader("Cursor"),
 469                SettingsPageItem::SettingItem(SettingItem {
 470                    title: "Cursor Blink",
 471                    description: "Whether the cursor blinks in the editor",
 472                    field: Box::new(SettingField {
 473                        pick: |settings_content| &settings_content.editor.cursor_blink,
 474                        pick_mut: |settings_content| &mut settings_content.editor.cursor_blink,
 475                    }),
 476                    metadata: None,
 477                }),
 478                SettingsPageItem::SettingItem(SettingItem {
 479                    title: "Cursor Shape",
 480                    description: "Cursor shape for the editor",
 481                    field: Box::new(SettingField {
 482                        pick: |settings_content| &settings_content.editor.cursor_shape,
 483                        pick_mut: |settings_content| &mut settings_content.editor.cursor_shape,
 484                    }),
 485                    metadata: None,
 486                }),
 487                SettingsPageItem::SettingItem(SettingItem {
 488                    title: "Hide Mouse",
 489                    description: "When to hide the mouse cursor",
 490                    field: Box::new(SettingField {
 491                        pick: |settings_content| &settings_content.editor.hide_mouse,
 492                        pick_mut: |settings_content| &mut settings_content.editor.hide_mouse,
 493                    }),
 494                    metadata: None,
 495                }),
 496                SettingsPageItem::SectionHeader("Highlighting"),
 497                // todo(settings_ui): numeric stepper and validator is needed for this
 498                // SettingsPageItem::SettingItem(SettingItem {
 499                //     title: "Unnecessary Code Fade",
 500                //     description: "How much to fade out unused code (0.0 - 0.9)",
 501                //     field: Box::new(SettingField {
 502                //         pick: |settings_content| &settings_content.theme.unnecessary_code_fade,
 503                //         pick_mut: |settings_content| &mut settings_content.theme.unnecessary_code_fade,
 504                //     }),
 505                //     metadata: None,
 506                // }),
 507                SettingsPageItem::SettingItem(SettingItem {
 508                    title: "Current Line Highlight",
 509                    description: "How to highlight the current line",
 510                    field: Box::new(SettingField {
 511                        pick: |settings_content| &settings_content.editor.current_line_highlight,
 512                        pick_mut: |settings_content| {
 513                            &mut settings_content.editor.current_line_highlight
 514                        },
 515                    }),
 516                    metadata: None,
 517                }),
 518                SettingsPageItem::SettingItem(SettingItem {
 519                    title: "Selection Highlight",
 520                    description: "Whether to highlight all occurrences of selected text",
 521                    field: Box::new(SettingField {
 522                        pick: |settings_content| &settings_content.editor.selection_highlight,
 523                        pick_mut: |settings_content| {
 524                            &mut settings_content.editor.selection_highlight
 525                        },
 526                    }),
 527                    metadata: None,
 528                }),
 529                SettingsPageItem::SettingItem(SettingItem {
 530                    title: "Rounded Selection",
 531                    description: "Whether the text selection should have rounded corners",
 532                    field: Box::new(SettingField {
 533                        pick: |settings_content| &settings_content.editor.rounded_selection,
 534                        pick_mut: |settings_content| &mut settings_content.editor.rounded_selection,
 535                    }),
 536                    metadata: None,
 537                }),
 538                SettingsPageItem::SectionHeader("Guides"),
 539                SettingsPageItem::SettingItem(SettingItem {
 540                    title: "Show Wrap Guides",
 541                    description: "Whether to show wrap guides (vertical rulers)",
 542                    field: Box::new(SettingField {
 543                        pick: |settings_content| {
 544                            &settings_content
 545                                .project
 546                                .all_languages
 547                                .defaults
 548                                .show_wrap_guides
 549                        },
 550                        pick_mut: |settings_content| {
 551                            &mut settings_content
 552                                .project
 553                                .all_languages
 554                                .defaults
 555                                .show_wrap_guides
 556                        },
 557                    }),
 558                    metadata: None,
 559                }),
 560                // todo(settings_ui): This needs a custom component
 561                // SettingsPageItem::SettingItem(SettingItem {
 562                //     title: "Wrap Guides",
 563                //     description: "Character counts at which to show wrap guides",
 564                //     field: Box::new(SettingField {
 565                //         pick: |settings_content| {
 566                //             &settings_content
 567                //                 .project
 568                //                 .all_languages
 569                //                 .defaults
 570                //                 .wrap_guides
 571                //         },
 572                //         pick_mut: |settings_content| {
 573                //             &mut settings_content
 574                //                 .project
 575                //                 .all_languages
 576                //                 .defaults
 577                //                 .wrap_guides
 578                //         },
 579                //     }),
 580                //     metadata: None,
 581                // }),
 582                SettingsPageItem::SectionHeader("Whitespace"),
 583                SettingsPageItem::SettingItem(SettingItem {
 584                    title: "Show Whitespace",
 585                    description: "Whether to show tabs and spaces",
 586                    field: Box::new(SettingField {
 587                        pick: |settings_content| {
 588                            &settings_content
 589                                .project
 590                                .all_languages
 591                                .defaults
 592                                .show_whitespaces
 593                        },
 594                        pick_mut: |settings_content| {
 595                            &mut settings_content
 596                                .project
 597                                .all_languages
 598                                .defaults
 599                                .show_whitespaces
 600                        },
 601                    }),
 602                    metadata: None,
 603                }),
 604                SettingsPageItem::SectionHeader("Window"),
 605                // todo(settings_ui): Should we filter by platform?
 606                SettingsPageItem::SettingItem(SettingItem {
 607                    title: "Use System Window Tabs",
 608                    description: "Whether to allow windows to tab together (macOS only)",
 609                    field: Box::new(SettingField {
 610                        pick: |settings_content| &settings_content.workspace.use_system_window_tabs,
 611                        pick_mut: |settings_content| {
 612                            &mut settings_content.workspace.use_system_window_tabs
 613                        },
 614                    }),
 615                    metadata: None,
 616                }),
 617                SettingsPageItem::SectionHeader("Layout"),
 618                SettingsPageItem::SettingItem(SettingItem {
 619                    title: "Zoomed Padding",
 620                    description: "Whether to show padding for zoomed panels",
 621                    field: Box::new(SettingField {
 622                        pick: |settings_content| &settings_content.workspace.zoomed_padding,
 623                        pick_mut: |settings_content| &mut settings_content.workspace.zoomed_padding,
 624                    }),
 625                    metadata: None,
 626                }),
 627                // todo(settings_ui): Needs numeric stepper
 628                // SettingsPageItem::SettingItem(SettingItem {
 629                //     title: "Centered Layout Left Padding",
 630                //     description: "Left padding for cenetered layout",
 631                //     field: Box::new(SettingField {
 632                //         pick: |settings_content| &settings_content.workspace.bottom_dock_layout,
 633                //         pick_mut: |settings_content| {
 634                //             &mut settings_content.workspace.bottom_dock_layout
 635                //         },
 636                //     }),
 637                //     metadata: None,
 638                // }),
 639                // SettingsPageItem::SettingItem(SettingItem {
 640                //     title: "Centered Layout Right Padding",
 641                //     description: "Right padding for cenetered layout",
 642                //     field: Box::new(SettingField {
 643                //         pick: |settings_content| &settings_content.workspace.bottom_dock_layout,
 644                //         pick_mut: |settings_content| {
 645                //             &mut settings_content.workspace.bottom_dock_layout
 646                //         },
 647                //     }),
 648                //     metadata: None,
 649                // }),
 650                SettingsPageItem::SettingItem(SettingItem {
 651                    title: "Bottom Dock Layout",
 652                    description: "Layout mode of the bottom dock",
 653                    field: Box::new(SettingField {
 654                        pick: |settings_content| &settings_content.workspace.bottom_dock_layout,
 655                        pick_mut: |settings_content| {
 656                            &mut settings_content.workspace.bottom_dock_layout
 657                        },
 658                    }),
 659                    metadata: None,
 660                }),
 661            ],
 662        },
 663        SettingsPage {
 664            title: "Editor",
 665            expanded: false,
 666            items: vec![
 667                SettingsPageItem::SectionHeader("Indentation"),
 668                // todo(settings_ui): Needs numeric stepper
 669                // SettingsPageItem::SettingItem(SettingItem {
 670                //     title: "Tab Size",
 671                //     description: "How many columns a tab should occupy",
 672                //     field: Box::new(SettingField {
 673                //         pick: |settings_content| &settings_content.project.all_languages.defaults.tab_size,
 674                //         pick_mut: |settings_content| &mut settings_content.project.all_languages.defaults.tab_size,
 675                //     }),
 676                //     metadata: None,
 677                // }),
 678                SettingsPageItem::SettingItem(SettingItem {
 679                    title: "Hard Tabs",
 680                    description: "Whether to indent lines using tab characters, as opposed to multiple spaces",
 681                    field: Box::new(SettingField {
 682                        pick: |settings_content| {
 683                            &settings_content.project.all_languages.defaults.hard_tabs
 684                        },
 685                        pick_mut: |settings_content| {
 686                            &mut settings_content.project.all_languages.defaults.hard_tabs
 687                        },
 688                    }),
 689                    metadata: None,
 690                }),
 691                SettingsPageItem::SettingItem(SettingItem {
 692                    title: "Auto Indent",
 693                    description: "Whether indentation should be adjusted based on the context whilst typing",
 694                    field: Box::new(SettingField {
 695                        pick: |settings_content| {
 696                            &settings_content.project.all_languages.defaults.auto_indent
 697                        },
 698                        pick_mut: |settings_content| {
 699                            &mut settings_content.project.all_languages.defaults.auto_indent
 700                        },
 701                    }),
 702                    metadata: None,
 703                }),
 704                SettingsPageItem::SettingItem(SettingItem {
 705                    title: "Auto Indent On Paste",
 706                    description: "Whether indentation of pasted content should be adjusted based on the context",
 707                    field: Box::new(SettingField {
 708                        pick: |settings_content| {
 709                            &settings_content
 710                                .project
 711                                .all_languages
 712                                .defaults
 713                                .auto_indent_on_paste
 714                        },
 715                        pick_mut: |settings_content| {
 716                            &mut settings_content
 717                                .project
 718                                .all_languages
 719                                .defaults
 720                                .auto_indent_on_paste
 721                        },
 722                    }),
 723                    metadata: None,
 724                }),
 725                SettingsPageItem::SectionHeader("Wrapping"),
 726                // todo(settings_ui): Needs numeric stepper
 727                // SettingsPageItem::SettingItem(SettingItem {
 728                //     title: "Preferred Line Length",
 729                //     description: "The column at which to soft-wrap lines, for buffers where soft-wrap is enabled",
 730                //     field: Box::new(SettingField {
 731                //         pick: |settings_content| &settings_content.project.all_languages.defaults.preferred_line_length,
 732                //         pick_mut: |settings_content| &mut settings_content.project.all_languages.defaults.preferred_line_length,
 733                //     }),
 734                //     metadata: None,
 735                // }),
 736                SettingsPageItem::SettingItem(SettingItem {
 737                    title: "Soft Wrap",
 738                    description: "How to soft-wrap long lines of text",
 739                    field: Box::new(SettingField {
 740                        pick: |settings_content| {
 741                            &settings_content.project.all_languages.defaults.soft_wrap
 742                        },
 743                        pick_mut: |settings_content| {
 744                            &mut settings_content.project.all_languages.defaults.soft_wrap
 745                        },
 746                    }),
 747                    metadata: None,
 748                }),
 749                SettingsPageItem::SectionHeader("Search"),
 750                SettingsPageItem::SettingItem(SettingItem {
 751                    title: "Search Wrap",
 752                    description: "Whether the editor search results will loop",
 753                    field: Box::new(SettingField {
 754                        pick: |settings_content| &settings_content.editor.search_wrap,
 755                        pick_mut: |settings_content| &mut settings_content.editor.search_wrap,
 756                    }),
 757                    metadata: None,
 758                }),
 759                SettingsPageItem::SettingItem(SettingItem {
 760                    title: "Seed Search Query From Cursor",
 761                    description: "When to populate a new search's query based on the text under the cursor",
 762                    field: Box::new(SettingField {
 763                        pick: |settings_content| {
 764                            &settings_content.editor.seed_search_query_from_cursor
 765                        },
 766                        pick_mut: |settings_content| {
 767                            &mut settings_content.editor.seed_search_query_from_cursor
 768                        },
 769                    }),
 770                    metadata: None,
 771                }),
 772                SettingsPageItem::SettingItem(SettingItem {
 773                    title: "Use Smartcase Search",
 774                    description: "Whether to use smartcase search",
 775                    field: Box::new(SettingField {
 776                        pick: |settings_content| &settings_content.editor.use_smartcase_search,
 777                        pick_mut: |settings_content| {
 778                            &mut settings_content.editor.use_smartcase_search
 779                        },
 780                    }),
 781                    metadata: None,
 782                }),
 783                SettingsPageItem::SectionHeader("Editor Behavior"),
 784                SettingsPageItem::SettingItem(SettingItem {
 785                    title: "Redact Private Values",
 786                    description: "Hide the values of variables in private files",
 787                    field: Box::new(SettingField {
 788                        pick: |settings_content| &settings_content.editor.redact_private_values,
 789                        pick_mut: |settings_content| {
 790                            &mut settings_content.editor.redact_private_values
 791                        },
 792                    }),
 793                    metadata: None,
 794                }),
 795                SettingsPageItem::SettingItem(SettingItem {
 796                    title: "Middle Click Paste",
 797                    description: "Whether to enable middle-click paste on Linux",
 798                    field: Box::new(SettingField {
 799                        pick: |settings_content| &settings_content.editor.middle_click_paste,
 800                        pick_mut: |settings_content| {
 801                            &mut settings_content.editor.middle_click_paste
 802                        },
 803                    }),
 804                    metadata: None,
 805                }),
 806                SettingsPageItem::SettingItem(SettingItem {
 807                    title: "Double Click In Multibuffer",
 808                    description: "What to do when multibuffer is double clicked in some of its excerpts",
 809                    field: Box::new(SettingField {
 810                        pick: |settings_content| {
 811                            &settings_content.editor.double_click_in_multibuffer
 812                        },
 813                        pick_mut: |settings_content| {
 814                            &mut settings_content.editor.double_click_in_multibuffer
 815                        },
 816                    }),
 817                    metadata: None,
 818                }),
 819                SettingsPageItem::SettingItem(SettingItem {
 820                    title: "Go To Definition Fallback",
 821                    description: "Whether to follow-up empty go to definition responses from the language server",
 822                    field: Box::new(SettingField {
 823                        pick: |settings_content| &settings_content.editor.go_to_definition_fallback,
 824                        pick_mut: |settings_content| {
 825                            &mut settings_content.editor.go_to_definition_fallback
 826                        },
 827                    }),
 828                    metadata: None,
 829                }),
 830                SettingsPageItem::SectionHeader("Scrolling"),
 831                SettingsPageItem::SettingItem(SettingItem {
 832                    title: "Scroll Beyond Last Line",
 833                    description: "Whether the editor will scroll beyond the last line",
 834                    field: Box::new(SettingField {
 835                        pick: |settings_content| &settings_content.editor.scroll_beyond_last_line,
 836                        pick_mut: |settings_content| {
 837                            &mut settings_content.editor.scroll_beyond_last_line
 838                        },
 839                    }),
 840                    metadata: None,
 841                }),
 842                // todo(settings_ui): Needs numeric stepper
 843                // SettingsPageItem::SettingItem(SettingItem {
 844                //     title: "Vertical Scroll Margin",
 845                //     description: "The number of lines to keep above/below the cursor when auto-scrolling",
 846                //     field: Box::new(SettingField {
 847                //         pick: |settings_content| &settings_content.editor.vertical_scroll_margin,
 848                //         pick_mut: |settings_content| &mut settings_content.editor.vertical_scroll_margin,
 849                //     }),
 850                //     metadata: None,
 851                // }),
 852                // todo(settings_ui): Needs numeric stepper
 853                // SettingsPageItem::SettingItem(SettingItem {
 854                //     title: "Horizontal Scroll Margin",
 855                //     description: "The number of characters to keep on either side when scrolling with the mouse",
 856                //     field: Box::new(SettingField {
 857                //         pick: |settings_content| &settings_content.editor.horizontal_scroll_margin,
 858                //         pick_mut: |settings_content| &mut settings_content.editor.horizontal_scroll_margin,
 859                //     }),
 860                //     metadata: None,
 861                // }),
 862                // todo(settings_ui): Needs numeric stepper
 863                // SettingsPageItem::SettingItem(SettingItem {
 864                //     title: "Scroll Sensitivity",
 865                //     description: "Scroll sensitivity multiplier",
 866                //     field: Box::new(SettingField {
 867                //         pick: |settings_content| &settings_content.editor.scroll_sensitivity,
 868                //         pick_mut: |settings_content| &mut settings_content.editor.scroll_sensitivity,
 869                //     }),
 870                //     metadata: None,
 871                // }),
 872                SettingsPageItem::SettingItem(SettingItem {
 873                    title: "Autoscroll On Clicks",
 874                    description: "Whether to scroll when clicking near the edge of the visible text area",
 875                    field: Box::new(SettingField {
 876                        pick: |settings_content| &settings_content.editor.autoscroll_on_clicks,
 877                        pick_mut: |settings_content| {
 878                            &mut settings_content.editor.autoscroll_on_clicks
 879                        },
 880                    }),
 881                    metadata: None,
 882                }),
 883                SettingsPageItem::SectionHeader("Auto Actions"),
 884                SettingsPageItem::SettingItem(SettingItem {
 885                    title: "Use Autoclose",
 886                    description: "Whether to automatically type closing characters for you",
 887                    field: Box::new(SettingField {
 888                        pick: |settings_content| {
 889                            &settings_content
 890                                .project
 891                                .all_languages
 892                                .defaults
 893                                .use_autoclose
 894                        },
 895                        pick_mut: |settings_content| {
 896                            &mut settings_content
 897                                .project
 898                                .all_languages
 899                                .defaults
 900                                .use_autoclose
 901                        },
 902                    }),
 903                    metadata: None,
 904                }),
 905                SettingsPageItem::SettingItem(SettingItem {
 906                    title: "Use Auto Surround",
 907                    description: "Whether to automatically surround text with characters for you",
 908                    field: Box::new(SettingField {
 909                        pick: |settings_content| {
 910                            &settings_content
 911                                .project
 912                                .all_languages
 913                                .defaults
 914                                .use_auto_surround
 915                        },
 916                        pick_mut: |settings_content| {
 917                            &mut settings_content
 918                                .project
 919                                .all_languages
 920                                .defaults
 921                                .use_auto_surround
 922                        },
 923                    }),
 924                    metadata: None,
 925                }),
 926                SettingsPageItem::SettingItem(SettingItem {
 927                    title: "Use On Type Format",
 928                    description: "Whether to use additional LSP queries to format the code after every trigger symbol input",
 929                    field: Box::new(SettingField {
 930                        pick: |settings_content| {
 931                            &settings_content
 932                                .project
 933                                .all_languages
 934                                .defaults
 935                                .use_on_type_format
 936                        },
 937                        pick_mut: |settings_content| {
 938                            &mut settings_content
 939                                .project
 940                                .all_languages
 941                                .defaults
 942                                .use_on_type_format
 943                        },
 944                    }),
 945                    metadata: None,
 946                }),
 947                SettingsPageItem::SettingItem(SettingItem {
 948                    title: "Always Treat Brackets As Autoclosed",
 949                    description: "Controls how the editor handles the autoclosed characters",
 950                    field: Box::new(SettingField {
 951                        pick: |settings_content| {
 952                            &settings_content
 953                                .project
 954                                .all_languages
 955                                .defaults
 956                                .always_treat_brackets_as_autoclosed
 957                        },
 958                        pick_mut: |settings_content| {
 959                            &mut settings_content
 960                                .project
 961                                .all_languages
 962                                .defaults
 963                                .always_treat_brackets_as_autoclosed
 964                        },
 965                    }),
 966                    metadata: None,
 967                }),
 968                SettingsPageItem::SectionHeader("Formatting"),
 969                SettingsPageItem::SettingItem(SettingItem {
 970                    title: "Remove Trailing Whitespace On Save",
 971                    description: "Whether or not to remove any trailing whitespace from lines of a buffer before saving it",
 972                    field: Box::new(SettingField {
 973                        pick: |settings_content| {
 974                            &settings_content
 975                                .project
 976                                .all_languages
 977                                .defaults
 978                                .remove_trailing_whitespace_on_save
 979                        },
 980                        pick_mut: |settings_content| {
 981                            &mut settings_content
 982                                .project
 983                                .all_languages
 984                                .defaults
 985                                .remove_trailing_whitespace_on_save
 986                        },
 987                    }),
 988                    metadata: None,
 989                }),
 990                SettingsPageItem::SettingItem(SettingItem {
 991                    title: "Ensure Final Newline On Save",
 992                    description: "Whether or not to ensure there's a single newline at the end of a buffer when saving it",
 993                    field: Box::new(SettingField {
 994                        pick: |settings_content| {
 995                            &settings_content
 996                                .project
 997                                .all_languages
 998                                .defaults
 999                                .ensure_final_newline_on_save
1000                        },
1001                        pick_mut: |settings_content| {
1002                            &mut settings_content
1003                                .project
1004                                .all_languages
1005                                .defaults
1006                                .ensure_final_newline_on_save
1007                        },
1008                    }),
1009                    metadata: None,
1010                }),
1011                SettingsPageItem::SettingItem(SettingItem {
1012                    title: "Extend Comment On Newline",
1013                    description: "Whether to start a new line with a comment when a previous line is a comment as well",
1014                    field: Box::new(SettingField {
1015                        pick: |settings_content| {
1016                            &settings_content
1017                                .project
1018                                .all_languages
1019                                .defaults
1020                                .extend_comment_on_newline
1021                        },
1022                        pick_mut: |settings_content| {
1023                            &mut settings_content
1024                                .project
1025                                .all_languages
1026                                .defaults
1027                                .extend_comment_on_newline
1028                        },
1029                    }),
1030                    metadata: None,
1031                }),
1032                SettingsPageItem::SectionHeader("Completions"),
1033                SettingsPageItem::SettingItem(SettingItem {
1034                    title: "Show Completions On Input",
1035                    description: "Whether to pop the completions menu while typing in an editor without explicitly requesting it",
1036                    field: Box::new(SettingField {
1037                        pick: |settings_content| {
1038                            &settings_content
1039                                .project
1040                                .all_languages
1041                                .defaults
1042                                .show_completions_on_input
1043                        },
1044                        pick_mut: |settings_content| {
1045                            &mut settings_content
1046                                .project
1047                                .all_languages
1048                                .defaults
1049                                .show_completions_on_input
1050                        },
1051                    }),
1052                    metadata: None,
1053                }),
1054                SettingsPageItem::SettingItem(SettingItem {
1055                    title: "Show Completion Documentation",
1056                    description: "Whether to display inline and alongside documentation for items in the completions menu",
1057                    field: Box::new(SettingField {
1058                        pick: |settings_content| {
1059                            &settings_content
1060                                .project
1061                                .all_languages
1062                                .defaults
1063                                .show_completion_documentation
1064                        },
1065                        pick_mut: |settings_content| {
1066                            &mut settings_content
1067                                .project
1068                                .all_languages
1069                                .defaults
1070                                .show_completion_documentation
1071                        },
1072                    }),
1073                    metadata: None,
1074                }),
1075                SettingsPageItem::SettingItem(SettingItem {
1076                    title: "Auto Signature Help",
1077                    description: "Whether to automatically show a signature help pop-up or not",
1078                    field: Box::new(SettingField {
1079                        pick: |settings_content| &settings_content.editor.auto_signature_help,
1080                        pick_mut: |settings_content| {
1081                            &mut settings_content.editor.auto_signature_help
1082                        },
1083                    }),
1084                    metadata: None,
1085                }),
1086                SettingsPageItem::SettingItem(SettingItem {
1087                    title: "Show Signature Help After Edits",
1088                    description: "Whether to show the signature help pop-up after completions or bracket pairs inserted",
1089                    field: Box::new(SettingField {
1090                        pick: |settings_content| {
1091                            &settings_content.editor.show_signature_help_after_edits
1092                        },
1093                        pick_mut: |settings_content| {
1094                            &mut settings_content.editor.show_signature_help_after_edits
1095                        },
1096                    }),
1097                    metadata: None,
1098                }),
1099                SettingsPageItem::SettingItem(SettingItem {
1100                    title: "Snippet Sort Order",
1101                    description: "Determines how snippets are sorted relative to other completion items",
1102                    field: Box::new(SettingField {
1103                        pick: |settings_content| &settings_content.editor.snippet_sort_order,
1104                        pick_mut: |settings_content| {
1105                            &mut settings_content.editor.snippet_sort_order
1106                        },
1107                    }),
1108                    metadata: None,
1109                }),
1110                SettingsPageItem::SectionHeader("Hover"),
1111                SettingsPageItem::SettingItem(SettingItem {
1112                    title: "Hover Popover Enabled",
1113                    description: "Whether to show the informational hover box when moving the mouse over symbols in the editor",
1114                    field: Box::new(SettingField {
1115                        pick: |settings_content| &settings_content.editor.hover_popover_enabled,
1116                        pick_mut: |settings_content| {
1117                            &mut settings_content.editor.hover_popover_enabled
1118                        },
1119                    }),
1120                    metadata: None,
1121                }),
1122                // todo(settings_ui): Needs numeric stepper
1123                // SettingsPageItem::SettingItem(SettingItem {
1124                //     title: "Hover Popover Delay",
1125                //     description: "Time to wait in milliseconds before showing the informational hover box",
1126                //     field: Box::new(SettingField {
1127                //         pick: |settings_content| &settings_content.editor.hover_popover_delay,
1128                //         pick_mut: |settings_content| &mut settings_content.editor.hover_popover_delay,
1129                //     }),
1130                //     metadata: None,
1131                // }),
1132                SettingsPageItem::SectionHeader("Code Actions"),
1133                SettingsPageItem::SettingItem(SettingItem {
1134                    title: "Inline Code Actions",
1135                    description: "Whether to show code action button at start of buffer line",
1136                    field: Box::new(SettingField {
1137                        pick: |settings_content| &settings_content.editor.inline_code_actions,
1138                        pick_mut: |settings_content| {
1139                            &mut settings_content.editor.inline_code_actions
1140                        },
1141                    }),
1142                    metadata: None,
1143                }),
1144                SettingsPageItem::SectionHeader("Selection"),
1145                SettingsPageItem::SettingItem(SettingItem {
1146                    title: "Drag And Drop Selection",
1147                    description: "Whether to enable drag and drop selection",
1148                    field: Box::new(SettingField {
1149                        pick: |settings_content| {
1150                            if let Some(drag_and_drop) =
1151                                &settings_content.editor.drag_and_drop_selection
1152                            {
1153                                &drag_and_drop.enabled
1154                            } else {
1155                                &None
1156                            }
1157                        },
1158                        pick_mut: |settings_content| {
1159                            &mut settings_content
1160                                .editor
1161                                .drag_and_drop_selection
1162                                .get_or_insert_default()
1163                                .enabled
1164                        },
1165                    }),
1166                    metadata: None,
1167                }),
1168                // todo(settings_ui): Needs numeric stepper
1169                // SettingsPageItem::SettingItem(SettingItem {
1170                //     title: "Drag And Drop Selection Delay",
1171                //     description: "Delay in milliseconds before drag and drop selection starts",
1172                //     field: Box::new(SettingField {
1173                //         pick: |settings_content| {
1174                //             if let Some(drag_and_drop) = &settings_content.editor.drag_and_drop_selection {
1175                //                 &drag_and_drop.delay
1176                //             } else {
1177                //                 &None
1178                //             }
1179                //         },
1180                //         pick_mut: |settings_content| {
1181                //             &mut settings_content.editor.drag_and_drop_selection.get_or_insert_default().delay
1182                //         },
1183                //     }),
1184                //     metadata: None,
1185                // }),
1186                SettingsPageItem::SectionHeader("Line Numbers"),
1187                SettingsPageItem::SettingItem(SettingItem {
1188                    title: "Relative Line Numbers",
1189                    description: "Whether the line numbers on editors gutter are relative or not",
1190                    field: Box::new(SettingField {
1191                        pick: |settings_content| &settings_content.editor.relative_line_numbers,
1192                        pick_mut: |settings_content| {
1193                            &mut settings_content.editor.relative_line_numbers
1194                        },
1195                    }),
1196                    metadata: None,
1197                }),
1198                SettingsPageItem::SectionHeader("Gutter"),
1199                SettingsPageItem::SettingItem(SettingItem {
1200                    title: "Show Line Numbers",
1201                    description: "Whether to show line numbers in the gutter",
1202                    field: Box::new(SettingField {
1203                        pick: |settings_content| {
1204                            if let Some(gutter) = &settings_content.editor.gutter {
1205                                &gutter.line_numbers
1206                            } else {
1207                                &None
1208                            }
1209                        },
1210                        pick_mut: |settings_content| {
1211                            &mut settings_content
1212                                .editor
1213                                .gutter
1214                                .get_or_insert_default()
1215                                .line_numbers
1216                        },
1217                    }),
1218                    metadata: None,
1219                }),
1220                SettingsPageItem::SettingItem(SettingItem {
1221                    title: "Show Runnables",
1222                    description: "Whether to show runnable buttons in the gutter",
1223                    field: Box::new(SettingField {
1224                        pick: |settings_content| {
1225                            if let Some(gutter) = &settings_content.editor.gutter {
1226                                &gutter.runnables
1227                            } else {
1228                                &None
1229                            }
1230                        },
1231                        pick_mut: |settings_content| {
1232                            &mut settings_content
1233                                .editor
1234                                .gutter
1235                                .get_or_insert_default()
1236                                .runnables
1237                        },
1238                    }),
1239                    metadata: None,
1240                }),
1241                SettingsPageItem::SettingItem(SettingItem {
1242                    title: "Show Breakpoints",
1243                    description: "Whether to show breakpoints in the gutter",
1244                    field: Box::new(SettingField {
1245                        pick: |settings_content| {
1246                            if let Some(gutter) = &settings_content.editor.gutter {
1247                                &gutter.breakpoints
1248                            } else {
1249                                &None
1250                            }
1251                        },
1252                        pick_mut: |settings_content| {
1253                            &mut settings_content
1254                                .editor
1255                                .gutter
1256                                .get_or_insert_default()
1257                                .breakpoints
1258                        },
1259                    }),
1260                    metadata: None,
1261                }),
1262                SettingsPageItem::SettingItem(SettingItem {
1263                    title: "Show Folds",
1264                    description: "Whether to show code folding controls in the gutter",
1265                    field: Box::new(SettingField {
1266                        pick: |settings_content| {
1267                            if let Some(gutter) = &settings_content.editor.gutter {
1268                                &gutter.folds
1269                            } else {
1270                                &None
1271                            }
1272                        },
1273                        pick_mut: |settings_content| {
1274                            &mut settings_content.editor.gutter.get_or_insert_default().folds
1275                        },
1276                    }),
1277                    metadata: None,
1278                }),
1279                SettingsPageItem::SectionHeader("Tabs"),
1280                SettingsPageItem::SettingItem(SettingItem {
1281                    title: "Show Tab Bar",
1282                    description: "Whether or not to show the tab bar in the editor",
1283                    field: Box::new(SettingField {
1284                        pick: |settings_content| {
1285                            if let Some(tab_bar) = &settings_content.tab_bar {
1286                                &tab_bar.show
1287                            } else {
1288                                &None
1289                            }
1290                        },
1291                        pick_mut: |settings_content| {
1292                            &mut settings_content.tab_bar.get_or_insert_default().show
1293                        },
1294                    }),
1295                    metadata: None,
1296                }),
1297                SettingsPageItem::SettingItem(SettingItem {
1298                    title: "Show Git Status In Tabs",
1299                    description: "Whether to show the Git file status on a tab item",
1300                    field: Box::new(SettingField {
1301                        pick: |settings_content| {
1302                            if let Some(tabs) = &settings_content.tabs {
1303                                &tabs.git_status
1304                            } else {
1305                                &None
1306                            }
1307                        },
1308                        pick_mut: |settings_content| {
1309                            &mut settings_content.tabs.get_or_insert_default().git_status
1310                        },
1311                    }),
1312                    metadata: None,
1313                }),
1314                SettingsPageItem::SettingItem(SettingItem {
1315                    title: "Show File Icons In Tabs",
1316                    description: "Whether to show the file icon for a tab",
1317                    field: Box::new(SettingField {
1318                        pick: |settings_content| {
1319                            if let Some(tabs) = &settings_content.tabs {
1320                                &tabs.file_icons
1321                            } else {
1322                                &None
1323                            }
1324                        },
1325                        pick_mut: |settings_content| {
1326                            &mut settings_content.tabs.get_or_insert_default().file_icons
1327                        },
1328                    }),
1329                    metadata: None,
1330                }),
1331                SettingsPageItem::SettingItem(SettingItem {
1332                    title: "Tab Close Position",
1333                    description: "Position of the close button in a tab",
1334                    field: Box::new(SettingField {
1335                        pick: |settings_content| {
1336                            if let Some(tabs) = &settings_content.tabs {
1337                                &tabs.close_position
1338                            } else {
1339                                &None
1340                            }
1341                        },
1342                        pick_mut: |settings_content| {
1343                            &mut settings_content.tabs.get_or_insert_default().close_position
1344                        },
1345                    }),
1346                    metadata: None,
1347                }),
1348                // todo(settings_ui): Needs numeric stepper
1349                // SettingsPageItem::SettingItem(SettingItem {
1350                //     title: "Maximum Tabs",
1351                //     description: "Maximum open tabs in a pane. Will not close an unsaved tab",
1352                //     field: Box::new(SettingField {
1353                //         pick: |settings_content| &settings_content.workspace.max_tabs,
1354                //         pick_mut: |settings_content| &mut settings_content.workspace.max_tabs,
1355                //     }),
1356                //     metadata: None,
1357                // }),
1358            ],
1359        },
1360        SettingsPage {
1361            title: "Workbench & Window",
1362            expanded: false,
1363            items: vec![
1364                SettingsPageItem::SectionHeader("Workbench"),
1365                SettingsPageItem::SettingItem(SettingItem {
1366                    title: "Editor Tabs",
1367                    description: "Whether or not to show the tab bar in the editor",
1368                    field: Box::new(SettingField {
1369                        pick: |settings_content| {
1370                            if let Some(tab_bar) = &settings_content.tab_bar {
1371                                &tab_bar.show
1372                            } else {
1373                                &None
1374                            }
1375                        },
1376                        pick_mut: |settings_content| {
1377                            &mut settings_content.tab_bar.get_or_insert_default().show
1378                        },
1379                    }),
1380                    metadata: None,
1381                }),
1382                SettingsPageItem::SettingItem(SettingItem {
1383                    title: "Active language Button",
1384                    description: "Whether to show the active language button in the status bar",
1385                    field: Box::new(SettingField {
1386                        pick: |settings_content| {
1387                            if let Some(status_bar) = &settings_content.editor.status_bar {
1388                                &status_bar.active_language_button
1389                            } else {
1390                                &None
1391                            }
1392                        },
1393                        pick_mut: |settings_content| {
1394                            &mut settings_content
1395                                .editor
1396                                .status_bar
1397                                .get_or_insert_default()
1398                                .active_language_button
1399                        },
1400                    }),
1401                    metadata: None,
1402                }),
1403                SettingsPageItem::SettingItem(SettingItem {
1404                    title: "Cursor Position Button",
1405                    description: "Whether to show the cursor position button in the status bar",
1406                    field: Box::new(SettingField {
1407                        pick: |settings_content| {
1408                            if let Some(status_bar) = &settings_content.editor.status_bar {
1409                                &status_bar.cursor_position_button
1410                            } else {
1411                                &None
1412                            }
1413                        },
1414                        pick_mut: |settings_content| {
1415                            &mut settings_content
1416                                .editor
1417                                .status_bar
1418                                .get_or_insert_default()
1419                                .cursor_position_button
1420                        },
1421                    }),
1422                    metadata: None,
1423                }),
1424                SettingsPageItem::SectionHeader("Terminal"),
1425                SettingsPageItem::SettingItem(SettingItem {
1426                    title: "Terminal Button",
1427                    description: "Whether to show the terminal button in the status bar",
1428                    field: Box::new(SettingField {
1429                        pick: |settings_content| {
1430                            if let Some(terminal) = &settings_content.terminal {
1431                                &terminal.button
1432                            } else {
1433                                &None
1434                            }
1435                        },
1436                        pick_mut: |settings_content| {
1437                            &mut settings_content.terminal.get_or_insert_default().button
1438                        },
1439                    }),
1440                    metadata: None,
1441                }),
1442                SettingsPageItem::SettingItem(SettingItem {
1443                    title: "Show Navigation History Buttons",
1444                    description: "Whether or not to show the navigation history buttons in the tab bar",
1445                    field: Box::new(SettingField {
1446                        pick: |settings_content| {
1447                            if let Some(tab_bar) = &settings_content.tab_bar {
1448                                &tab_bar.show_nav_history_buttons
1449                            } else {
1450                                &None
1451                            }
1452                        },
1453                        pick_mut: |settings_content| {
1454                            &mut settings_content
1455                                .tab_bar
1456                                .get_or_insert_default()
1457                                .show_nav_history_buttons
1458                        },
1459                    }),
1460                    metadata: None,
1461                }),
1462            ],
1463        },
1464        SettingsPage {
1465            title: "Panels & Tools",
1466            expanded: false,
1467            items: vec![
1468                SettingsPageItem::SectionHeader("Project Panel"),
1469                SettingsPageItem::SettingItem(SettingItem {
1470                    title: "Project Panel Button",
1471                    description: "Whether to show the project panel button in the status bar",
1472                    field: Box::new(SettingField {
1473                        pick: |settings_content| {
1474                            if let Some(project_panel) = &settings_content.project_panel {
1475                                &project_panel.button
1476                            } else {
1477                                &None
1478                            }
1479                        },
1480                        pick_mut: |settings_content| {
1481                            &mut settings_content
1482                                .project_panel
1483                                .get_or_insert_default()
1484                                .button
1485                        },
1486                    }),
1487                    metadata: None,
1488                }),
1489                SettingsPageItem::SettingItem(SettingItem {
1490                    title: "Project Panel Dock",
1491                    description: "Where to dock the project panel",
1492                    field: Box::new(SettingField {
1493                        pick: |settings_content| {
1494                            if let Some(project_panel) = &settings_content.project_panel {
1495                                &project_panel.dock
1496                            } else {
1497                                &None
1498                            }
1499                        },
1500                        pick_mut: |settings_content| {
1501                            &mut settings_content.project_panel.get_or_insert_default().dock
1502                        },
1503                    }),
1504                    metadata: None,
1505                }),
1506                // todo(settings_ui): Needs numeric stepper
1507                // SettingsPageItem::SettingItem(SettingItem {
1508                //     title: "Project Panel Default Width",
1509                //     description: "Default width of the project panel in pixels",
1510                //     field: Box::new(SettingField {
1511                //         pick: |settings_content| {
1512                //             if let Some(project_panel) = &settings_content.project_panel {
1513                //                 &project_panel.default_width
1514                //             } else {
1515                //                 &None
1516                //             }
1517                //         },
1518                //         pick_mut: |settings_content| {
1519                //             &mut settings_content
1520                //                 .project_panel
1521                //                 .get_or_insert_default()
1522                //                 .default_width
1523                //         },
1524                //     }),
1525                //     metadata: None,
1526                // }),
1527                SettingsPageItem::SectionHeader("Terminal"),
1528                SettingsPageItem::SettingItem(SettingItem {
1529                    title: "Terminal Dock",
1530                    description: "Where to dock the terminal panel",
1531                    field: Box::new(SettingField {
1532                        pick: |settings_content| {
1533                            if let Some(terminal) = &settings_content.terminal {
1534                                &terminal.dock
1535                            } else {
1536                                &None
1537                            }
1538                        },
1539                        pick_mut: |settings_content| {
1540                            &mut settings_content.terminal.get_or_insert_default().dock
1541                        },
1542                    }),
1543                    metadata: None,
1544                }),
1545                SettingsPageItem::SectionHeader("Tab Settings"),
1546                SettingsPageItem::SettingItem(SettingItem {
1547                    title: "Activate On Close",
1548                    description: "What to do after closing the current tab",
1549                    field: Box::new(SettingField {
1550                        pick: |settings_content| {
1551                            if let Some(tabs) = &settings_content.tabs {
1552                                &tabs.activate_on_close
1553                            } else {
1554                                &None
1555                            }
1556                        },
1557                        pick_mut: |settings_content| {
1558                            &mut settings_content
1559                                .tabs
1560                                .get_or_insert_default()
1561                                .activate_on_close
1562                        },
1563                    }),
1564                    metadata: None,
1565                }),
1566                SettingsPageItem::SettingItem(SettingItem {
1567                    title: "Tab Show Diagnostics",
1568                    description: "Which files containing diagnostic errors/warnings to mark in the tabs",
1569                    field: Box::new(SettingField {
1570                        pick: |settings_content| {
1571                            if let Some(tabs) = &settings_content.tabs {
1572                                &tabs.show_diagnostics
1573                            } else {
1574                                &None
1575                            }
1576                        },
1577                        pick_mut: |settings_content| {
1578                            &mut settings_content
1579                                .tabs
1580                                .get_or_insert_default()
1581                                .show_diagnostics
1582                        },
1583                    }),
1584                    metadata: None,
1585                }),
1586                SettingsPageItem::SettingItem(SettingItem {
1587                    title: "Show Close Button",
1588                    description: "Controls the appearance behavior of the tab's close button",
1589                    field: Box::new(SettingField {
1590                        pick: |settings_content| {
1591                            if let Some(tabs) = &settings_content.tabs {
1592                                &tabs.show_close_button
1593                            } else {
1594                                &None
1595                            }
1596                        },
1597                        pick_mut: |settings_content| {
1598                            &mut settings_content
1599                                .tabs
1600                                .get_or_insert_default()
1601                                .show_close_button
1602                        },
1603                    }),
1604                    metadata: None,
1605                }),
1606                SettingsPageItem::SectionHeader("Preview Tabs"),
1607                SettingsPageItem::SettingItem(SettingItem {
1608                    title: "Preview Tabs Enabled",
1609                    description: "Whether to show opened editors as preview tabs",
1610                    field: Box::new(SettingField {
1611                        pick: |settings_content| {
1612                            if let Some(preview_tabs) = &settings_content.preview_tabs {
1613                                &preview_tabs.enabled
1614                            } else {
1615                                &None
1616                            }
1617                        },
1618                        pick_mut: |settings_content| {
1619                            &mut settings_content
1620                                .preview_tabs
1621                                .get_or_insert_default()
1622                                .enabled
1623                        },
1624                    }),
1625                    metadata: None,
1626                }),
1627                SettingsPageItem::SettingItem(SettingItem {
1628                    title: "Enable Preview From File Finder",
1629                    description: "Whether to open tabs in preview mode when selected from the file finder",
1630                    field: Box::new(SettingField {
1631                        pick: |settings_content| {
1632                            if let Some(preview_tabs) = &settings_content.preview_tabs {
1633                                &preview_tabs.enable_preview_from_file_finder
1634                            } else {
1635                                &None
1636                            }
1637                        },
1638                        pick_mut: |settings_content| {
1639                            &mut settings_content
1640                                .preview_tabs
1641                                .get_or_insert_default()
1642                                .enable_preview_from_file_finder
1643                        },
1644                    }),
1645                    metadata: None,
1646                }),
1647                SettingsPageItem::SettingItem(SettingItem {
1648                    title: "Enable Preview From Code Navigation",
1649                    description: "Whether a preview tab gets replaced when code navigation is used to navigate away from the tab",
1650                    field: Box::new(SettingField {
1651                        pick: |settings_content| {
1652                            if let Some(preview_tabs) = &settings_content.preview_tabs {
1653                                &preview_tabs.enable_preview_from_code_navigation
1654                            } else {
1655                                &None
1656                            }
1657                        },
1658                        pick_mut: |settings_content| {
1659                            &mut settings_content
1660                                .preview_tabs
1661                                .get_or_insert_default()
1662                                .enable_preview_from_code_navigation
1663                        },
1664                    }),
1665                    metadata: None,
1666                }),
1667            ],
1668        },
1669        SettingsPage {
1670            title: "Version Control",
1671            expanded: false,
1672            items: vec![
1673                SettingsPageItem::SectionHeader("Git"),
1674                SettingsPageItem::SettingItem(SettingItem {
1675                    title: "Git Gutter",
1676                    description: "Control whether the git gutter is shown",
1677                    field: Box::new(SettingField {
1678                        pick: |settings_content| {
1679                            if let Some(git) = &settings_content.git {
1680                                &git.git_gutter
1681                            } else {
1682                                &None
1683                            }
1684                        },
1685                        pick_mut: |settings_content| {
1686                            &mut settings_content.git.get_or_insert_default().git_gutter
1687                        },
1688                    }),
1689                    metadata: None,
1690                }),
1691                // todo(settings_ui): Needs numeric stepper
1692                // SettingsPageItem::SettingItem(SettingItem {
1693                //     title: "Gutter Debounce",
1694                //     description: "Debounce threshold in milliseconds after which changes are reflected in the git gutter",
1695                //     field: Box::new(SettingField {
1696                //         pick: |settings_content| {
1697                //             if let Some(git) = &settings_content.git {
1698                //                 &git.gutter_debounce
1699                //             } else {
1700                //                 &None
1701                //             }
1702                //         },
1703                //         pick_mut: |settings_content| {
1704                //             &mut settings_content.git.get_or_insert_default().gutter_debounce
1705                //         },
1706                //     }),
1707                //     metadata: None,
1708                // }),
1709                SettingsPageItem::SettingItem(SettingItem {
1710                    title: "Inline Blame Enabled",
1711                    description: "Whether or not to show git blame data inline in the currently focused line",
1712                    field: Box::new(SettingField {
1713                        pick: |settings_content| {
1714                            if let Some(git) = &settings_content.git {
1715                                if let Some(inline_blame) = &git.inline_blame {
1716                                    &inline_blame.enabled
1717                                } else {
1718                                    &None
1719                                }
1720                            } else {
1721                                &None
1722                            }
1723                        },
1724                        pick_mut: |settings_content| {
1725                            &mut settings_content
1726                                .git
1727                                .get_or_insert_default()
1728                                .inline_blame
1729                                .get_or_insert_default()
1730                                .enabled
1731                        },
1732                    }),
1733                    metadata: None,
1734                }),
1735                SettingsPageItem::SettingItem(SettingItem {
1736                    title: "Show Commit Summary",
1737                    description: "Whether to show commit summary as part of the inline blame",
1738                    field: Box::new(SettingField {
1739                        pick: |settings_content| {
1740                            if let Some(git) = &settings_content.git {
1741                                if let Some(inline_blame) = &git.inline_blame {
1742                                    &inline_blame.show_commit_summary
1743                                } else {
1744                                    &None
1745                                }
1746                            } else {
1747                                &None
1748                            }
1749                        },
1750                        pick_mut: |settings_content| {
1751                            &mut settings_content
1752                                .git
1753                                .get_or_insert_default()
1754                                .inline_blame
1755                                .get_or_insert_default()
1756                                .show_commit_summary
1757                        },
1758                    }),
1759                    metadata: None,
1760                }),
1761                SettingsPageItem::SettingItem(SettingItem {
1762                    title: "Show Avatar",
1763                    description: "Whether to show the avatar of the author of the commit",
1764                    field: Box::new(SettingField {
1765                        pick: |settings_content| {
1766                            if let Some(git) = &settings_content.git {
1767                                if let Some(blame) = &git.blame {
1768                                    &blame.show_avatar
1769                                } else {
1770                                    &None
1771                                }
1772                            } else {
1773                                &None
1774                            }
1775                        },
1776                        pick_mut: |settings_content| {
1777                            &mut settings_content
1778                                .git
1779                                .get_or_insert_default()
1780                                .blame
1781                                .get_or_insert_default()
1782                                .show_avatar
1783                        },
1784                    }),
1785                    metadata: None,
1786                }),
1787                SettingsPageItem::SettingItem(SettingItem {
1788                    title: "Show Author Name In Branch Picker",
1789                    description: "Whether to show author name as part of the commit information in branch picker",
1790                    field: Box::new(SettingField {
1791                        pick: |settings_content| {
1792                            if let Some(git) = &settings_content.git {
1793                                if let Some(branch_picker) = &git.branch_picker {
1794                                    &branch_picker.show_author_name
1795                                } else {
1796                                    &None
1797                                }
1798                            } else {
1799                                &None
1800                            }
1801                        },
1802                        pick_mut: |settings_content| {
1803                            &mut settings_content
1804                                .git
1805                                .get_or_insert_default()
1806                                .branch_picker
1807                                .get_or_insert_default()
1808                                .show_author_name
1809                        },
1810                    }),
1811                    metadata: None,
1812                }),
1813                SettingsPageItem::SettingItem(SettingItem {
1814                    title: "Hunk Style",
1815                    description: "How git hunks are displayed visually in the editor",
1816                    field: Box::new(SettingField {
1817                        pick: |settings_content| {
1818                            if let Some(git) = &settings_content.git {
1819                                &git.hunk_style
1820                            } else {
1821                                &None
1822                            }
1823                        },
1824                        pick_mut: |settings_content| {
1825                            &mut settings_content.git.get_or_insert_default().hunk_style
1826                        },
1827                    }),
1828                    metadata: None,
1829                }),
1830            ],
1831        },
1832        SettingsPage {
1833            title: "System & Network",
1834            expanded: false,
1835            items: vec![
1836                SettingsPageItem::SectionHeader("Network"),
1837                // todo(settings_ui): Proxy needs a default
1838                // SettingsPageItem::SettingItem(SettingItem {
1839                //     title: "Proxy",
1840                //     description: "The proxy to use for network requests",
1841                //     field: Box::new(SettingField {
1842                //         pick: |settings_content| &settings_content.proxy,
1843                //         pick_mut: |settings_content| &mut settings_content.proxy,
1844                //     }),
1845                //     metadata: Some(Box::new(SettingsFieldMetadata {
1846                //         placeholder: Some("socks5h://localhost:10808"),
1847                //     })),
1848                // }),
1849                SettingsPageItem::SettingItem(SettingItem {
1850                    title: "Server URL",
1851                    description: "The URL of the Zed server to connect to",
1852                    field: Box::new(SettingField {
1853                        pick: |settings_content| &settings_content.server_url,
1854                        pick_mut: |settings_content| &mut settings_content.server_url,
1855                    }),
1856                    metadata: Some(Box::new(SettingsFieldMetadata {
1857                        placeholder: Some("https://zed.dev"),
1858                    })),
1859                }),
1860                SettingsPageItem::SectionHeader("System"),
1861                SettingsPageItem::SettingItem(SettingItem {
1862                    title: "Auto Update",
1863                    description: "Whether or not to automatically check for updates",
1864                    field: Box::new(SettingField {
1865                        pick: |settings_content| &settings_content.auto_update,
1866                        pick_mut: |settings_content| &mut settings_content.auto_update,
1867                    }),
1868                    metadata: None,
1869                }),
1870            ],
1871        },
1872        SettingsPage {
1873            title: "Diagnostics & Errors",
1874            expanded: false,
1875            items: vec![
1876                SettingsPageItem::SectionHeader("Display"),
1877                SettingsPageItem::SettingItem(SettingItem {
1878                    title: "Diagnostics Button",
1879                    description: "Whether to show the project diagnostics button in the status bar",
1880                    field: Box::new(SettingField {
1881                        pick: |settings_content| {
1882                            if let Some(diagnostics) = &settings_content.diagnostics {
1883                                &diagnostics.button
1884                            } else {
1885                                &None
1886                            }
1887                        },
1888                        pick_mut: |settings_content| {
1889                            &mut settings_content.diagnostics.get_or_insert_default().button
1890                        },
1891                    }),
1892                    metadata: None,
1893                }),
1894                SettingsPageItem::SectionHeader("Filtering"),
1895                SettingsPageItem::SettingItem(SettingItem {
1896                    title: "Max Severity",
1897                    description: "Which level to use to filter out diagnostics displayed in the editor",
1898                    field: Box::new(SettingField {
1899                        pick: |settings_content| &settings_content.editor.diagnostics_max_severity,
1900                        pick_mut: |settings_content| {
1901                            &mut settings_content.editor.diagnostics_max_severity
1902                        },
1903                    }),
1904                    metadata: None,
1905                }),
1906                SettingsPageItem::SettingItem(SettingItem {
1907                    title: "Include Warnings",
1908                    description: "Whether to show warnings or not by default",
1909                    field: Box::new(SettingField {
1910                        pick: |settings_content| {
1911                            if let Some(diagnostics) = &settings_content.diagnostics {
1912                                &diagnostics.include_warnings
1913                            } else {
1914                                &None
1915                            }
1916                        },
1917                        pick_mut: |settings_content| {
1918                            &mut settings_content
1919                                .diagnostics
1920                                .get_or_insert_default()
1921                                .include_warnings
1922                        },
1923                    }),
1924                    metadata: None,
1925                }),
1926                SettingsPageItem::SectionHeader("Inline"),
1927                SettingsPageItem::SettingItem(SettingItem {
1928                    title: "Inline Diagnostics Enabled",
1929                    description: "Whether to show diagnostics inline or not",
1930                    field: Box::new(SettingField {
1931                        pick: |settings_content| {
1932                            if let Some(diagnostics) = &settings_content.diagnostics {
1933                                if let Some(inline) = &diagnostics.inline {
1934                                    &inline.enabled
1935                                } else {
1936                                    &None
1937                                }
1938                            } else {
1939                                &None
1940                            }
1941                        },
1942                        pick_mut: |settings_content| {
1943                            &mut settings_content
1944                                .diagnostics
1945                                .get_or_insert_default()
1946                                .inline
1947                                .get_or_insert_default()
1948                                .enabled
1949                        },
1950                    }),
1951                    metadata: None,
1952                }),
1953                // todo(settings_ui): Needs numeric stepper
1954                // SettingsPageItem::SettingItem(SettingItem {
1955                //     title: "Inline Update Debounce",
1956                //     description: "The delay in milliseconds to show inline diagnostics after the last diagnostic update",
1957                //     field: Box::new(SettingField {
1958                //         pick: |settings_content| {
1959                //             if let Some(diagnostics) = &settings_content.diagnostics {
1960                //                 if let Some(inline) = &diagnostics.inline {
1961                //                     &inline.update_debounce_ms
1962                //                 } else {
1963                //                     &None
1964                //                 }
1965                //             } else {
1966                //                 &None
1967                //             }
1968                //         },
1969                //         pick_mut: |settings_content| {
1970                //             &mut settings_content
1971                //                 .diagnostics
1972                //                 .get_or_insert_default()
1973                //                 .inline
1974                //                 .get_or_insert_default()
1975                //                 .update_debounce_ms
1976                //         },
1977                //     }),
1978                //     metadata: None,
1979                // }),
1980                // todo(settings_ui): Needs numeric stepper
1981                // SettingsPageItem::SettingItem(SettingItem {
1982                //     title: "Inline Padding",
1983                //     description: "The amount of padding between the end of the source line and the start of the inline diagnostic",
1984                //     field: Box::new(SettingField {
1985                //         pick: |settings_content| {
1986                //             if let Some(diagnostics) = &settings_content.diagnostics {
1987                //                 if let Some(inline) = &diagnostics.inline {
1988                //                     &inline.padding
1989                //                 } else {
1990                //                     &None
1991                //                 }
1992                //             } else {
1993                //                 &None
1994                //             }
1995                //         },
1996                //         pick_mut: |settings_content| {
1997                //             &mut settings_content
1998                //                 .diagnostics
1999                //                 .get_or_insert_default()
2000                //                 .inline
2001                //                 .get_or_insert_default()
2002                //                 .padding
2003                //         },
2004                //     }),
2005                //     metadata: None,
2006                // }),
2007                // todo(settings_ui): Needs numeric stepper
2008                // SettingsPageItem::SettingItem(SettingItem {
2009                //     title: "Inline Min Column",
2010                //     description: "The minimum column to display inline diagnostics",
2011                //     field: Box::new(SettingField {
2012                //         pick: |settings_content| {
2013                //             if let Some(diagnostics) = &settings_content.diagnostics {
2014                //                 if let Some(inline) = &diagnostics.inline {
2015                //                     &inline.min_column
2016                //                 } else {
2017                //                     &None
2018                //                 }
2019                //             } else {
2020                //                 &None
2021                //             }
2022                //         },
2023                //         pick_mut: |settings_content| {
2024                //             &mut settings_content
2025                //                 .diagnostics
2026                //                 .get_or_insert_default()
2027                //                 .inline
2028                //                 .get_or_insert_default()
2029                //                 .min_column
2030                //         },
2031                //     }),
2032                //     metadata: None,
2033                // }),
2034                SettingsPageItem::SectionHeader("Performance"),
2035                SettingsPageItem::SettingItem(SettingItem {
2036                    title: "LSP Pull Diagnostics Enabled",
2037                    description: "Whether to pull for diagnostics or not",
2038                    field: Box::new(SettingField {
2039                        pick: |settings_content| {
2040                            if let Some(diagnostics) = &settings_content.diagnostics {
2041                                if let Some(lsp_pull) = &diagnostics.lsp_pull_diagnostics {
2042                                    &lsp_pull.enabled
2043                                } else {
2044                                    &None
2045                                }
2046                            } else {
2047                                &None
2048                            }
2049                        },
2050                        pick_mut: |settings_content| {
2051                            &mut settings_content
2052                                .diagnostics
2053                                .get_or_insert_default()
2054                                .lsp_pull_diagnostics
2055                                .get_or_insert_default()
2056                                .enabled
2057                        },
2058                    }),
2059                    metadata: None,
2060                }),
2061                // todo(settings_ui): Needs numeric stepper
2062                // SettingsPageItem::SettingItem(SettingItem {
2063                //     title: "LSP Pull Debounce",
2064                //     description: "Minimum time to wait before pulling diagnostics from the language server(s)",
2065                //     field: Box::new(SettingField {
2066                //         pick: |settings_content| {
2067                //             if let Some(diagnostics) = &settings_content.diagnostics {
2068                //                 if let Some(lsp_pull) = &diagnostics.lsp_pull_diagnostics {
2069                //                     &lsp_pull.debounce_ms
2070                //                 } else {
2071                //                     &None
2072                //                 }
2073                //             } else {
2074                //                 &None
2075                //             }
2076                //         },
2077                //         pick_mut: |settings_content| {
2078                //             &mut settings_content
2079                //                 .diagnostics
2080                //                 .get_or_insert_default()
2081                //                 .lsp_pull_diagnostics
2082                //                 .get_or_insert_default()
2083                //                 .debounce_ms
2084                //         },
2085                //     }),
2086                //     metadata: None,
2087                // }),
2088            ],
2089        },
2090        SettingsPage {
2091            title: "Collaboration",
2092            expanded: false,
2093            items: vec![
2094                SettingsPageItem::SectionHeader("Calls"),
2095                SettingsPageItem::SettingItem(SettingItem {
2096                    title: "Mute On Join",
2097                    description: "Whether the microphone should be muted when joining a channel or a call",
2098                    field: Box::new(SettingField {
2099                        pick: |settings_content| {
2100                            if let Some(calls) = &settings_content.calls {
2101                                &calls.mute_on_join
2102                            } else {
2103                                &None
2104                            }
2105                        },
2106                        pick_mut: |settings_content| {
2107                            &mut settings_content.calls.get_or_insert_default().mute_on_join
2108                        },
2109                    }),
2110                    metadata: None,
2111                }),
2112                SettingsPageItem::SettingItem(SettingItem {
2113                    title: "Share On Join",
2114                    description: "Whether your current project should be shared when joining an empty channel",
2115                    field: Box::new(SettingField {
2116                        pick: |settings_content| {
2117                            if let Some(calls) = &settings_content.calls {
2118                                &calls.share_on_join
2119                            } else {
2120                                &None
2121                            }
2122                        },
2123                        pick_mut: |settings_content| {
2124                            &mut settings_content.calls.get_or_insert_default().share_on_join
2125                        },
2126                    }),
2127                    metadata: None,
2128                }),
2129                SettingsPageItem::SectionHeader("Panel"),
2130                SettingsPageItem::SettingItem(SettingItem {
2131                    title: "Collaboration Panel Button",
2132                    description: "Whether to show the collaboration panel button in the status bar",
2133                    field: Box::new(SettingField {
2134                        pick: |settings_content| {
2135                            if let Some(collab) = &settings_content.collaboration_panel {
2136                                &collab.button
2137                            } else {
2138                                &None
2139                            }
2140                        },
2141                        pick_mut: |settings_content| {
2142                            &mut settings_content
2143                                .collaboration_panel
2144                                .get_or_insert_default()
2145                                .button
2146                        },
2147                    }),
2148                    metadata: None,
2149                }),
2150                SettingsPageItem::SectionHeader("Experimental"),
2151                SettingsPageItem::SettingItem(SettingItem {
2152                    title: "Rodio Audio",
2153                    description: "Opt into the new audio system",
2154                    field: Box::new(SettingField {
2155                        pick: |settings_content| {
2156                            if let Some(audio) = &settings_content.audio {
2157                                &audio.rodio_audio
2158                            } else {
2159                                &None
2160                            }
2161                        },
2162                        pick_mut: |settings_content| {
2163                            &mut settings_content.audio.get_or_insert_default().rodio_audio
2164                        },
2165                    }),
2166                    metadata: None,
2167                }),
2168            ],
2169        },
2170        SettingsPage {
2171            title: "AI",
2172            expanded: false,
2173            items: vec![
2174                SettingsPageItem::SectionHeader("General"),
2175                SettingsPageItem::SettingItem(SettingItem {
2176                    title: "Disable AI",
2177                    description: "Whether to disable all AI features in Zed",
2178                    field: Box::new(SettingField {
2179                        pick: |settings_content| &settings_content.disable_ai,
2180                        pick_mut: |settings_content| &mut settings_content.disable_ai,
2181                    }),
2182                    metadata: None,
2183                }),
2184            ],
2185        },
2186    ]
2187}
2188
2189// Derive Macro, on the new ProjectSettings struct
2190
2191fn project_settings_data() -> Vec<SettingsPage> {
2192    vec![
2193        SettingsPage {
2194            title: "Project",
2195            expanded: false,
2196            items: vec![
2197                SettingsPageItem::SectionHeader("Worktree Settings Content"),
2198                SettingsPageItem::SettingItem(SettingItem {
2199                    title: "Project Name",
2200                    description: "The displayed name of this project. If not set, the root directory name",
2201                    field: Box::new(SettingField {
2202                        pick: |settings_content| &settings_content.project.worktree.project_name,
2203                        pick_mut: |settings_content| {
2204                            &mut settings_content.project.worktree.project_name
2205                        },
2206                    }),
2207                    metadata: Some(Box::new(SettingsFieldMetadata {
2208                        placeholder: Some("A new name"),
2209                    })),
2210                }),
2211            ],
2212        },
2213        SettingsPage {
2214            title: "Appearance & Behavior",
2215            expanded: false,
2216            items: vec![
2217                SettingsPageItem::SectionHeader("Guides"),
2218                SettingsPageItem::SettingItem(SettingItem {
2219                    title: "Show Wrap Guides",
2220                    description: "Whether to show wrap guides (vertical rulers)",
2221                    field: Box::new(SettingField {
2222                        pick: |settings_content| {
2223                            &settings_content
2224                                .project
2225                                .all_languages
2226                                .defaults
2227                                .show_wrap_guides
2228                        },
2229                        pick_mut: |settings_content| {
2230                            &mut settings_content
2231                                .project
2232                                .all_languages
2233                                .defaults
2234                                .show_wrap_guides
2235                        },
2236                    }),
2237                    metadata: None,
2238                }),
2239                // todo(settings_ui): This needs a custom component
2240                // SettingsPageItem::SettingItem(SettingItem {
2241                //     title: "Wrap Guides",
2242                //     description: "Character counts at which to show wrap guides",
2243                //     field: Box::new(SettingField {
2244                //         pick: |settings_content| {
2245                //             &settings_content
2246                //                 .project
2247                //                 .all_languages
2248                //                 .defaults
2249                //                 .wrap_guides
2250                //         },
2251                //         pick_mut: |settings_content| {
2252                //             &mut settings_content
2253                //                 .project
2254                //                 .all_languages
2255                //                 .defaults
2256                //                 .wrap_guides
2257                //         },
2258                //     }),
2259                //     metadata: None,
2260                // }),
2261                SettingsPageItem::SectionHeader("Whitespace"),
2262                SettingsPageItem::SettingItem(SettingItem {
2263                    title: "Show Whitespace",
2264                    description: "Whether to show tabs and spaces",
2265                    field: Box::new(SettingField {
2266                        pick: |settings_content| {
2267                            &settings_content
2268                                .project
2269                                .all_languages
2270                                .defaults
2271                                .show_whitespaces
2272                        },
2273                        pick_mut: |settings_content| {
2274                            &mut settings_content
2275                                .project
2276                                .all_languages
2277                                .defaults
2278                                .show_whitespaces
2279                        },
2280                    }),
2281                    metadata: None,
2282                }),
2283            ],
2284        },
2285        SettingsPage {
2286            title: "Editing",
2287            expanded: false,
2288            items: vec![
2289                SettingsPageItem::SectionHeader("Indentation"),
2290                // todo(settings_ui): Needs numeric stepper
2291                // SettingsPageItem::SettingItem(SettingItem {
2292                //     title: "Tab Size",
2293                //     description: "How many columns a tab should occupy",
2294                //     field: Box::new(SettingField {
2295                //         pick: |settings_content| &settings_content.project.all_languages.defaults.tab_size,
2296                //         pick_mut: |settings_content| &mut settings_content.project.all_languages.defaults.tab_size,
2297                //     }),
2298                //     metadata: None,
2299                // }),
2300                SettingsPageItem::SettingItem(SettingItem {
2301                    title: "Hard Tabs",
2302                    description: "Whether to indent lines using tab characters, as opposed to multiple spaces",
2303                    field: Box::new(SettingField {
2304                        pick: |settings_content| {
2305                            &settings_content.project.all_languages.defaults.hard_tabs
2306                        },
2307                        pick_mut: |settings_content| {
2308                            &mut settings_content.project.all_languages.defaults.hard_tabs
2309                        },
2310                    }),
2311                    metadata: None,
2312                }),
2313                SettingsPageItem::SettingItem(SettingItem {
2314                    title: "Auto Indent",
2315                    description: "Whether indentation should be adjusted based on the context whilst typing",
2316                    field: Box::new(SettingField {
2317                        pick: |settings_content| {
2318                            &settings_content.project.all_languages.defaults.auto_indent
2319                        },
2320                        pick_mut: |settings_content| {
2321                            &mut settings_content.project.all_languages.defaults.auto_indent
2322                        },
2323                    }),
2324                    metadata: None,
2325                }),
2326                SettingsPageItem::SettingItem(SettingItem {
2327                    title: "Auto Indent On Paste",
2328                    description: "Whether indentation of pasted content should be adjusted based on the context",
2329                    field: Box::new(SettingField {
2330                        pick: |settings_content| {
2331                            &settings_content
2332                                .project
2333                                .all_languages
2334                                .defaults
2335                                .auto_indent_on_paste
2336                        },
2337                        pick_mut: |settings_content| {
2338                            &mut settings_content
2339                                .project
2340                                .all_languages
2341                                .defaults
2342                                .auto_indent_on_paste
2343                        },
2344                    }),
2345                    metadata: None,
2346                }),
2347                SettingsPageItem::SectionHeader("Wrapping"),
2348                // todo(settings_ui): Needs numeric stepper
2349                // SettingsPageItem::SettingItem(SettingItem {
2350                //     title: "Preferred Line Length",
2351                //     description: "The column at which to soft-wrap lines, for buffers where soft-wrap is enabled",
2352                //     field: Box::new(SettingField {
2353                //         pick: |settings_content| &settings_content.project.all_languages.defaults.preferred_line_length,
2354                //         pick_mut: |settings_content| &mut settings_content.project.all_languages.defaults.preferred_line_length,
2355                //     }),
2356                //     metadata: None,
2357                // }),
2358                SettingsPageItem::SettingItem(SettingItem {
2359                    title: "Soft Wrap",
2360                    description: "How to soft-wrap long lines of text",
2361                    field: Box::new(SettingField {
2362                        pick: |settings_content| {
2363                            &settings_content.project.all_languages.defaults.soft_wrap
2364                        },
2365                        pick_mut: |settings_content| {
2366                            &mut settings_content.project.all_languages.defaults.soft_wrap
2367                        },
2368                    }),
2369                    metadata: None,
2370                }),
2371                SettingsPageItem::SectionHeader("Auto Actions"),
2372                SettingsPageItem::SettingItem(SettingItem {
2373                    title: "Use Autoclose",
2374                    description: "Whether to automatically type closing characters for you",
2375                    field: Box::new(SettingField {
2376                        pick: |settings_content| {
2377                            &settings_content
2378                                .project
2379                                .all_languages
2380                                .defaults
2381                                .use_autoclose
2382                        },
2383                        pick_mut: |settings_content| {
2384                            &mut settings_content
2385                                .project
2386                                .all_languages
2387                                .defaults
2388                                .use_autoclose
2389                        },
2390                    }),
2391                    metadata: None,
2392                }),
2393                SettingsPageItem::SettingItem(SettingItem {
2394                    title: "Use Auto Surround",
2395                    description: "Whether to automatically surround text with characters for you",
2396                    field: Box::new(SettingField {
2397                        pick: |settings_content| {
2398                            &settings_content
2399                                .project
2400                                .all_languages
2401                                .defaults
2402                                .use_auto_surround
2403                        },
2404                        pick_mut: |settings_content| {
2405                            &mut settings_content
2406                                .project
2407                                .all_languages
2408                                .defaults
2409                                .use_auto_surround
2410                        },
2411                    }),
2412                    metadata: None,
2413                }),
2414                SettingsPageItem::SettingItem(SettingItem {
2415                    title: "Use On Type Format",
2416                    description: "Whether to use additional LSP queries to format the code after every trigger symbol input",
2417                    field: Box::new(SettingField {
2418                        pick: |settings_content| {
2419                            &settings_content
2420                                .project
2421                                .all_languages
2422                                .defaults
2423                                .use_on_type_format
2424                        },
2425                        pick_mut: |settings_content| {
2426                            &mut settings_content
2427                                .project
2428                                .all_languages
2429                                .defaults
2430                                .use_on_type_format
2431                        },
2432                    }),
2433                    metadata: None,
2434                }),
2435                SettingsPageItem::SettingItem(SettingItem {
2436                    title: "Always Treat Brackets As Autoclosed",
2437                    description: "Controls how the editor handles the autoclosed characters",
2438                    field: Box::new(SettingField {
2439                        pick: |settings_content| {
2440                            &settings_content
2441                                .project
2442                                .all_languages
2443                                .defaults
2444                                .always_treat_brackets_as_autoclosed
2445                        },
2446                        pick_mut: |settings_content| {
2447                            &mut settings_content
2448                                .project
2449                                .all_languages
2450                                .defaults
2451                                .always_treat_brackets_as_autoclosed
2452                        },
2453                    }),
2454                    metadata: None,
2455                }),
2456                SettingsPageItem::SectionHeader("Formatting"),
2457                SettingsPageItem::SettingItem(SettingItem {
2458                    title: "Remove Trailing Whitespace On Save",
2459                    description: "Whether or not to remove any trailing whitespace from lines of a buffer before saving it",
2460                    field: Box::new(SettingField {
2461                        pick: |settings_content| {
2462                            &settings_content
2463                                .project
2464                                .all_languages
2465                                .defaults
2466                                .remove_trailing_whitespace_on_save
2467                        },
2468                        pick_mut: |settings_content| {
2469                            &mut settings_content
2470                                .project
2471                                .all_languages
2472                                .defaults
2473                                .remove_trailing_whitespace_on_save
2474                        },
2475                    }),
2476                    metadata: None,
2477                }),
2478                SettingsPageItem::SettingItem(SettingItem {
2479                    title: "Ensure Final Newline On Save",
2480                    description: "Whether or not to ensure there's a single newline at the end of a buffer when saving it",
2481                    field: Box::new(SettingField {
2482                        pick: |settings_content| {
2483                            &settings_content
2484                                .project
2485                                .all_languages
2486                                .defaults
2487                                .ensure_final_newline_on_save
2488                        },
2489                        pick_mut: |settings_content| {
2490                            &mut settings_content
2491                                .project
2492                                .all_languages
2493                                .defaults
2494                                .ensure_final_newline_on_save
2495                        },
2496                    }),
2497                    metadata: None,
2498                }),
2499                SettingsPageItem::SettingItem(SettingItem {
2500                    title: "Extend Comment On Newline",
2501                    description: "Whether to start a new line with a comment when a previous line is a comment as well",
2502                    field: Box::new(SettingField {
2503                        pick: |settings_content| {
2504                            &settings_content
2505                                .project
2506                                .all_languages
2507                                .defaults
2508                                .extend_comment_on_newline
2509                        },
2510                        pick_mut: |settings_content| {
2511                            &mut settings_content
2512                                .project
2513                                .all_languages
2514                                .defaults
2515                                .extend_comment_on_newline
2516                        },
2517                    }),
2518                    metadata: None,
2519                }),
2520                SettingsPageItem::SectionHeader("Completions"),
2521                SettingsPageItem::SettingItem(SettingItem {
2522                    title: "Show Completions On Input",
2523                    description: "Whether to pop the completions menu while typing in an editor without explicitly requesting it",
2524                    field: Box::new(SettingField {
2525                        pick: |settings_content| {
2526                            &settings_content
2527                                .project
2528                                .all_languages
2529                                .defaults
2530                                .show_completions_on_input
2531                        },
2532                        pick_mut: |settings_content| {
2533                            &mut settings_content
2534                                .project
2535                                .all_languages
2536                                .defaults
2537                                .show_completions_on_input
2538                        },
2539                    }),
2540                    metadata: None,
2541                }),
2542                SettingsPageItem::SettingItem(SettingItem {
2543                    title: "Show Completion Documentation",
2544                    description: "Whether to display inline and alongside documentation for items in the completions menu",
2545                    field: Box::new(SettingField {
2546                        pick: |settings_content| {
2547                            &settings_content
2548                                .project
2549                                .all_languages
2550                                .defaults
2551                                .show_completion_documentation
2552                        },
2553                        pick_mut: |settings_content| {
2554                            &mut settings_content
2555                                .project
2556                                .all_languages
2557                                .defaults
2558                                .show_completion_documentation
2559                        },
2560                    }),
2561                    metadata: None,
2562                }),
2563            ],
2564        },
2565    ]
2566}
2567
2568pub struct SettingsUiFeatureFlag;
2569
2570impl FeatureFlag for SettingsUiFeatureFlag {
2571    const NAME: &'static str = "settings-ui";
2572}
2573
2574actions!(
2575    zed,
2576    [
2577        /// Opens Settings Editor.
2578        OpenSettingsEditor
2579    ]
2580);
2581
2582pub fn init(cx: &mut App) {
2583    init_renderers(cx);
2584
2585    cx.observe_new(|workspace: &mut workspace::Workspace, _, _| {
2586        workspace.register_action_renderer(|div, _, _, cx| {
2587            let settings_ui_actions = [std::any::TypeId::of::<OpenSettingsEditor>()];
2588            let has_flag = cx.has_flag::<SettingsUiFeatureFlag>();
2589            command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _| {
2590                if has_flag {
2591                    filter.show_action_types(&settings_ui_actions);
2592                } else {
2593                    filter.hide_action_types(&settings_ui_actions);
2594                }
2595            });
2596            if has_flag {
2597                div.on_action(cx.listener(|_, _: &OpenSettingsEditor, _, cx| {
2598                    open_settings_editor(cx).ok();
2599                }))
2600            } else {
2601                div
2602            }
2603        });
2604    })
2605    .detach();
2606}
2607
2608fn init_renderers(cx: &mut App) {
2609    // fn (field: SettingsField, current_file: SettingsFile, cx) -> (currently_set_in: SettingsFile, overridden_in: Vec<SettingsFile>)
2610    cx.default_global::<SettingFieldRenderer>()
2611        .add_renderer::<bool>(|settings_field, file, _, _, cx| {
2612            render_toggle_button(*settings_field, file, cx).into_any_element()
2613        })
2614        .add_renderer::<String>(|settings_field, file, metadata, _, cx| {
2615            render_text_field(settings_field.clone(), file, metadata, cx)
2616        })
2617        .add_renderer::<SaturatingBool>(|settings_field, file, _, _, cx| {
2618            render_toggle_button(*settings_field, file, cx)
2619        })
2620        .add_renderer::<CursorShape>(|settings_field, file, _, window, cx| {
2621            render_dropdown(*settings_field, file, window, cx)
2622        })
2623        .add_renderer::<RestoreOnStartupBehavior>(|settings_field, file, _, window, cx| {
2624            render_dropdown(*settings_field, file, window, cx)
2625        })
2626        .add_renderer::<BottomDockLayout>(|settings_field, file, _, window, cx| {
2627            render_dropdown(*settings_field, file, window, cx)
2628        })
2629        .add_renderer::<OnLastWindowClosed>(|settings_field, file, _, window, cx| {
2630            render_dropdown(*settings_field, file, window, cx)
2631        })
2632        .add_renderer::<CloseWindowWhenNoItems>(|settings_field, file, _, window, cx| {
2633            render_dropdown(*settings_field, file, window, cx)
2634        })
2635        .add_renderer::<settings::FontFamilyName>(|settings_field, file, metadata, _, cx| {
2636            // todo(settings_ui): We need to pass in a validator for this to ensure that users that type in invalid font names
2637            render_text_field(settings_field.clone(), file, metadata, cx)
2638        })
2639        .add_renderer::<settings::BufferLineHeight>(|settings_field, file, _, window, cx| {
2640            // todo(settings_ui): Do we want to expose the custom variant of buffer line height?
2641            // right now there's a manual impl of strum::VariantArray
2642            render_dropdown(*settings_field, file, window, cx)
2643        })
2644        .add_renderer::<settings::BaseKeymapContent>(|settings_field, file, _, window, cx| {
2645            render_dropdown(*settings_field, file, window, cx)
2646        })
2647        .add_renderer::<settings::MultiCursorModifier>(|settings_field, file, _, window, cx| {
2648            render_dropdown(*settings_field, file, window, cx)
2649        })
2650        .add_renderer::<settings::HideMouseMode>(|settings_field, file, _, window, cx| {
2651            render_dropdown(*settings_field, file, window, cx)
2652        })
2653        .add_renderer::<settings::CurrentLineHighlight>(|settings_field, file, _, window, cx| {
2654            render_dropdown(*settings_field, file, window, cx)
2655        })
2656        .add_renderer::<settings::ShowWhitespaceSetting>(|settings_field, file, _, window, cx| {
2657            render_dropdown(*settings_field, file, window, cx)
2658        })
2659        .add_renderer::<settings::SoftWrap>(|settings_field, file, _, window, cx| {
2660            render_dropdown(*settings_field, file, window, cx)
2661        })
2662        .add_renderer::<settings::ScrollBeyondLastLine>(|settings_field, file, _, window, cx| {
2663            render_dropdown(*settings_field, file, window, cx)
2664        })
2665        .add_renderer::<settings::SnippetSortOrder>(|settings_field, file, _, window, cx| {
2666            render_dropdown(*settings_field, file, window, cx)
2667        })
2668        .add_renderer::<settings::ClosePosition>(|settings_field, file, _, window, cx| {
2669            render_dropdown(*settings_field, file, window, cx)
2670        })
2671        .add_renderer::<settings::DockSide>(|settings_field, file, _, window, cx| {
2672            render_dropdown(*settings_field, file, window, cx)
2673        })
2674        .add_renderer::<settings::TerminalDockPosition>(|settings_field, file, _, window, cx| {
2675            render_dropdown(*settings_field, file, window, cx)
2676        })
2677        .add_renderer::<settings::GitGutterSetting>(|settings_field, file, _, window, cx| {
2678            render_dropdown(*settings_field, file, window, cx)
2679        })
2680        .add_renderer::<settings::GitHunkStyleSetting>(|settings_field, file, _, window, cx| {
2681            render_dropdown(*settings_field, file, window, cx)
2682        })
2683        .add_renderer::<settings::DiagnosticSeverityContent>(
2684            |settings_field, file, _, window, cx| {
2685                render_dropdown(*settings_field, file, window, cx)
2686            },
2687        )
2688        .add_renderer::<settings::SeedQuerySetting>(|settings_field, file, _, window, cx| {
2689            render_dropdown(*settings_field, file, window, cx)
2690        })
2691        .add_renderer::<settings::DoubleClickInMultibuffer>(
2692            |settings_field, file, _, window, cx| {
2693                render_dropdown(*settings_field, file, window, cx)
2694            },
2695        )
2696        .add_renderer::<settings::GoToDefinitionFallback>(|settings_field, file, _, window, cx| {
2697            render_dropdown(*settings_field, file, window, cx)
2698        })
2699        .add_renderer::<settings::ActivateOnClose>(|settings_field, file, _, window, cx| {
2700            render_dropdown(*settings_field, file, window, cx)
2701        })
2702        .add_renderer::<settings::ShowDiagnostics>(|settings_field, file, _, window, cx| {
2703            render_dropdown(*settings_field, file, window, cx)
2704        })
2705        .add_renderer::<settings::ShowCloseButton>(|settings_field, file, _, window, cx| {
2706            render_dropdown(*settings_field, file, window, cx)
2707        });
2708
2709    // todo(settings_ui): Figure out how we want to handle discriminant unions
2710    // .add_renderer::<ThemeSelection>(|settings_field, file, _, window, cx| {
2711    //     render_dropdown(*settings_field, file, window, cx)
2712    // });
2713}
2714
2715pub fn open_settings_editor(cx: &mut App) -> anyhow::Result<WindowHandle<SettingsWindow>> {
2716    cx.open_window(
2717        WindowOptions {
2718            titlebar: Some(TitlebarOptions {
2719                title: Some("Settings Window".into()),
2720                appears_transparent: true,
2721                traffic_light_position: Some(point(px(12.0), px(12.0))),
2722            }),
2723            focus: true,
2724            show: true,
2725            kind: gpui::WindowKind::Normal,
2726            window_background: cx.theme().window_background_appearance(),
2727            window_min_size: Some(size(px(800.), px(600.))), // 4:3 Aspect Ratio
2728            ..Default::default()
2729        },
2730        |window, cx| cx.new(|cx| SettingsWindow::new(window, cx)),
2731    )
2732}
2733
2734pub struct SettingsWindow {
2735    files: Vec<SettingsUiFile>,
2736    current_file: SettingsUiFile,
2737    pages: Vec<SettingsPage>,
2738    search_bar: Entity<Editor>,
2739    search_task: Option<Task<()>>,
2740    navbar_entry: usize, // Index into pages - should probably be (usize, Option<usize>) for section + page
2741    navbar_entries: Vec<NavBarEntry>,
2742    list_handle: UniformListScrollHandle,
2743    search_matches: Vec<Vec<bool>>,
2744}
2745
2746#[derive(PartialEq, Debug)]
2747struct NavBarEntry {
2748    title: &'static str,
2749    is_root: bool,
2750    page_index: usize,
2751}
2752
2753struct SettingsPage {
2754    title: &'static str,
2755    expanded: bool,
2756    items: Vec<SettingsPageItem>,
2757}
2758
2759#[derive(PartialEq)]
2760enum SettingsPageItem {
2761    SectionHeader(&'static str),
2762    SettingItem(SettingItem),
2763}
2764
2765impl std::fmt::Debug for SettingsPageItem {
2766    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2767        match self {
2768            SettingsPageItem::SectionHeader(header) => write!(f, "SectionHeader({})", header),
2769            SettingsPageItem::SettingItem(setting_item) => {
2770                write!(f, "SettingItem({})", setting_item.title)
2771            }
2772        }
2773    }
2774}
2775
2776impl SettingsPageItem {
2777    fn render(
2778        &self,
2779        file: SettingsUiFile,
2780        is_last: bool,
2781        window: &mut Window,
2782        cx: &mut App,
2783    ) -> AnyElement {
2784        match self {
2785            SettingsPageItem::SectionHeader(header) => v_flex()
2786                .w_full()
2787                .gap_1()
2788                .child(
2789                    Label::new(SharedString::new_static(header))
2790                        .size(LabelSize::XSmall)
2791                        .color(Color::Muted)
2792                        .buffer_font(cx),
2793                )
2794                .child(Divider::horizontal().color(ui::DividerColor::BorderVariant))
2795                .into_any_element(),
2796            SettingsPageItem::SettingItem(setting_item) => {
2797                let renderer = cx.default_global::<SettingFieldRenderer>().clone();
2798                let file_set_in =
2799                    SettingsUiFile::from_settings(setting_item.field.file_set_in(file.clone(), cx));
2800
2801                h_flex()
2802                    .id(setting_item.title)
2803                    .w_full()
2804                    .gap_2()
2805                    .flex_wrap()
2806                    .justify_between()
2807                    .when(!is_last, |this| {
2808                        this.pb_4()
2809                            .border_b_1()
2810                            .border_color(cx.theme().colors().border_variant)
2811                    })
2812                    .child(
2813                        v_flex()
2814                            .max_w_1_2()
2815                            .flex_shrink()
2816                            .child(
2817                                h_flex()
2818                                    .w_full()
2819                                    .gap_4()
2820                                    .child(
2821                                        Label::new(SharedString::new_static(setting_item.title))
2822                                            .size(LabelSize::Default),
2823                                    )
2824                                    .when_some(
2825                                        file_set_in.filter(|file_set_in| file_set_in != &file),
2826                                        |elem, file_set_in| {
2827                                            elem.child(
2828                                                Label::new(format!(
2829                                                    "set in {}",
2830                                                    file_set_in.name()
2831                                                ))
2832                                                .color(Color::Muted),
2833                                            )
2834                                        },
2835                                    ),
2836                            )
2837                            .child(
2838                                Label::new(SharedString::new_static(setting_item.description))
2839                                    .size(LabelSize::Small)
2840                                    .color(Color::Muted),
2841                            ),
2842                    )
2843                    .child(renderer.render(
2844                        setting_item.field.as_ref(),
2845                        file,
2846                        setting_item.metadata.as_deref(),
2847                        window,
2848                        cx,
2849                    ))
2850                    .into_any_element()
2851            }
2852        }
2853    }
2854}
2855
2856struct SettingItem {
2857    title: &'static str,
2858    description: &'static str,
2859    field: Box<dyn AnySettingField>,
2860    metadata: Option<Box<SettingsFieldMetadata>>,
2861}
2862
2863impl PartialEq for SettingItem {
2864    fn eq(&self, other: &Self) -> bool {
2865        self.title == other.title
2866            && self.description == other.description
2867            && (match (&self.metadata, &other.metadata) {
2868                (None, None) => true,
2869                (Some(m1), Some(m2)) => m1.placeholder == m2.placeholder,
2870                _ => false,
2871            })
2872    }
2873}
2874
2875#[allow(unused)]
2876#[derive(Clone, PartialEq)]
2877enum SettingsUiFile {
2878    User,                              // Uses all settings.
2879    Local((WorktreeId, Arc<RelPath>)), // Has a special name, and special set of settings
2880    Server(&'static str),              // Uses a special name, and the user settings
2881}
2882
2883impl SettingsUiFile {
2884    fn pages(&self) -> Vec<SettingsPage> {
2885        match self {
2886            SettingsUiFile::User => user_settings_data(),
2887            SettingsUiFile::Local(_) => project_settings_data(),
2888            SettingsUiFile::Server(_) => user_settings_data(),
2889        }
2890    }
2891
2892    fn name(&self) -> SharedString {
2893        match self {
2894            SettingsUiFile::User => SharedString::new_static("User"),
2895            // TODO is PathStyle::local() ever not appropriate?
2896            SettingsUiFile::Local((_, path)) => {
2897                format!("Local ({})", path.display(PathStyle::local())).into()
2898            }
2899            SettingsUiFile::Server(file) => format!("Server ({})", file).into(),
2900        }
2901    }
2902
2903    fn from_settings(file: settings::SettingsFile) -> Option<Self> {
2904        Some(match file {
2905            settings::SettingsFile::User => SettingsUiFile::User,
2906            settings::SettingsFile::Local(location) => SettingsUiFile::Local(location),
2907            settings::SettingsFile::Server => SettingsUiFile::Server("todo: server name"),
2908            settings::SettingsFile::Default => return None,
2909        })
2910    }
2911
2912    fn to_settings(&self) -> settings::SettingsFile {
2913        match self {
2914            SettingsUiFile::User => settings::SettingsFile::User,
2915            SettingsUiFile::Local(location) => settings::SettingsFile::Local(location.clone()),
2916            SettingsUiFile::Server(_) => settings::SettingsFile::Server,
2917        }
2918    }
2919}
2920
2921impl SettingsWindow {
2922    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
2923        let current_file = SettingsUiFile::User;
2924        let search_bar = cx.new(|cx| {
2925            let mut editor = Editor::single_line(window, cx);
2926            editor.set_placeholder_text("Search settings…", window, cx);
2927            editor
2928        });
2929
2930        cx.subscribe(&search_bar, |this, _, event: &EditorEvent, cx| {
2931            let EditorEvent::Edited { transaction_id: _ } = event else {
2932                return;
2933            };
2934
2935            this.update_matches(cx);
2936        })
2937        .detach();
2938
2939        cx.observe_global_in::<SettingsStore>(window, move |this, _, cx| {
2940            this.fetch_files(cx);
2941            cx.notify();
2942        })
2943        .detach();
2944
2945        let mut this = Self {
2946            files: vec![],
2947            current_file: current_file,
2948            pages: vec![],
2949            navbar_entries: vec![],
2950            navbar_entry: 0,
2951            list_handle: UniformListScrollHandle::default(),
2952            search_bar,
2953            search_task: None,
2954            search_matches: vec![],
2955        };
2956
2957        this.fetch_files(cx);
2958        this.build_ui(cx);
2959
2960        this
2961    }
2962
2963    fn toggle_navbar_entry(&mut self, ix: usize) {
2964        // We can only toggle root entries
2965        if !self.navbar_entries[ix].is_root {
2966            return;
2967        }
2968
2969        let toggle_page_index = self.page_index_from_navbar_index(ix);
2970        let selected_page_index = self.page_index_from_navbar_index(self.navbar_entry);
2971
2972        let expanded = &mut self.page_for_navbar_index(ix).expanded;
2973        *expanded = !*expanded;
2974        let expanded = *expanded;
2975        // if currently selected page is a child of the parent page we are folding,
2976        // set the current page to the parent page
2977        if selected_page_index == toggle_page_index {
2978            self.navbar_entry = ix;
2979        } else if selected_page_index > toggle_page_index {
2980            let sub_items_count = self.pages[toggle_page_index]
2981                .items
2982                .iter()
2983                .filter(|item| matches!(item, SettingsPageItem::SectionHeader(_)))
2984                .count();
2985            if expanded {
2986                self.navbar_entry += sub_items_count;
2987            } else {
2988                self.navbar_entry -= sub_items_count;
2989            }
2990        }
2991
2992        self.build_navbar();
2993    }
2994
2995    fn build_navbar(&mut self) {
2996        let mut navbar_entries = Vec::with_capacity(self.navbar_entries.len());
2997        for (page_index, page) in self.pages.iter().enumerate() {
2998            if !self.search_matches[page_index]
2999                .iter()
3000                .any(|is_match| *is_match)
3001                && !self.search_matches[page_index].is_empty()
3002            {
3003                continue;
3004            }
3005            navbar_entries.push(NavBarEntry {
3006                title: page.title,
3007                is_root: true,
3008                page_index,
3009            });
3010            if !page.expanded {
3011                continue;
3012            }
3013
3014            for (item_index, item) in page.items.iter().enumerate() {
3015                let SettingsPageItem::SectionHeader(title) = item else {
3016                    continue;
3017                };
3018                if !self.search_matches[page_index][item_index] {
3019                    continue;
3020                }
3021
3022                navbar_entries.push(NavBarEntry {
3023                    title,
3024                    is_root: false,
3025                    page_index,
3026                });
3027            }
3028        }
3029        self.navbar_entries = navbar_entries;
3030    }
3031
3032    fn update_matches(&mut self, cx: &mut Context<SettingsWindow>) {
3033        self.search_task.take();
3034        let query = self.search_bar.read(cx).text(cx);
3035        if query.is_empty() {
3036            for page in &mut self.search_matches {
3037                page.fill(true);
3038            }
3039            self.build_navbar();
3040            cx.notify();
3041            return;
3042        }
3043
3044        struct ItemKey {
3045            page_index: usize,
3046            header_index: usize,
3047            item_index: usize,
3048        }
3049        let mut key_lut: Vec<ItemKey> = vec![];
3050        let mut candidates = Vec::default();
3051
3052        for (page_index, page) in self.pages.iter().enumerate() {
3053            let mut header_index = 0;
3054            for (item_index, item) in page.items.iter().enumerate() {
3055                let key_index = key_lut.len();
3056                match item {
3057                    SettingsPageItem::SettingItem(item) => {
3058                        candidates.push(StringMatchCandidate::new(key_index, item.title));
3059                        candidates.push(StringMatchCandidate::new(key_index, item.description));
3060                    }
3061                    SettingsPageItem::SectionHeader(header) => {
3062                        candidates.push(StringMatchCandidate::new(key_index, header));
3063                        header_index = item_index;
3064                    }
3065                }
3066                key_lut.push(ItemKey {
3067                    page_index,
3068                    header_index,
3069                    item_index,
3070                });
3071            }
3072        }
3073        let atomic_bool = AtomicBool::new(false);
3074
3075        self.search_task = Some(cx.spawn(async move |this, cx| {
3076            let string_matches = fuzzy::match_strings(
3077                candidates.as_slice(),
3078                &query,
3079                false,
3080                false,
3081                candidates.len(),
3082                &atomic_bool,
3083                cx.background_executor().clone(),
3084            );
3085            let string_matches = string_matches.await;
3086
3087            this.update(cx, |this, cx| {
3088                for page in &mut this.search_matches {
3089                    page.fill(false);
3090                }
3091
3092                for string_match in string_matches {
3093                    let ItemKey {
3094                        page_index,
3095                        header_index,
3096                        item_index,
3097                    } = key_lut[string_match.candidate_id];
3098                    let page = &mut this.search_matches[page_index];
3099                    page[header_index] = true;
3100                    page[item_index] = true;
3101                }
3102                this.build_navbar();
3103                this.navbar_entry = 0;
3104                cx.notify();
3105            })
3106            .ok();
3107        }));
3108    }
3109
3110    fn build_ui(&mut self, cx: &mut Context<SettingsWindow>) {
3111        self.pages = self.current_file.pages();
3112        self.search_matches = self
3113            .pages
3114            .iter()
3115            .map(|page| vec![true; page.items.len()])
3116            .collect::<Vec<_>>();
3117        self.build_navbar();
3118
3119        if !self.search_bar.read(cx).is_empty(cx) {
3120            self.update_matches(cx);
3121        }
3122
3123        cx.notify();
3124    }
3125
3126    fn fetch_files(&mut self, cx: &mut Context<SettingsWindow>) {
3127        let settings_store = cx.global::<SettingsStore>();
3128        let mut ui_files = vec![];
3129        let all_files = settings_store.get_all_files();
3130        for file in all_files {
3131            let Some(settings_ui_file) = SettingsUiFile::from_settings(file) else {
3132                continue;
3133            };
3134            ui_files.push(settings_ui_file);
3135        }
3136        ui_files.reverse();
3137        self.files = ui_files;
3138        if !self.files.contains(&self.current_file) {
3139            self.change_file(0, cx);
3140        }
3141    }
3142
3143    fn change_file(&mut self, ix: usize, cx: &mut Context<SettingsWindow>) {
3144        if ix >= self.files.len() {
3145            self.current_file = SettingsUiFile::User;
3146            return;
3147        }
3148        if self.files[ix] == self.current_file {
3149            return;
3150        }
3151        self.current_file = self.files[ix].clone();
3152        self.navbar_entry = 0;
3153        self.build_ui(cx);
3154    }
3155
3156    fn render_files(&self, _window: &mut Window, cx: &mut Context<SettingsWindow>) -> Div {
3157        h_flex()
3158            .gap_1()
3159            .children(self.files.iter().enumerate().map(|(ix, file)| {
3160                Button::new(ix, file.name())
3161                    .on_click(cx.listener(move |this, _, _window, cx| this.change_file(ix, cx)))
3162            }))
3163    }
3164
3165    fn render_search(&self, _window: &mut Window, cx: &mut App) -> Div {
3166        h_flex()
3167            .pt_1()
3168            .px_1p5()
3169            .gap_1p5()
3170            .rounded_sm()
3171            .bg(cx.theme().colors().editor_background)
3172            .border_1()
3173            .border_color(cx.theme().colors().border)
3174            .child(Icon::new(IconName::MagnifyingGlass).color(Color::Muted))
3175            .child(self.search_bar.clone())
3176    }
3177
3178    fn render_nav(&self, window: &mut Window, cx: &mut Context<SettingsWindow>) -> Div {
3179        v_flex()
3180            .w_64()
3181            .p_2p5()
3182            .pt_10()
3183            .gap_3()
3184            .flex_none()
3185            .border_r_1()
3186            .border_color(cx.theme().colors().border)
3187            .bg(cx.theme().colors().panel_background)
3188            .child(self.render_search(window, cx).pb_1())
3189            .child(
3190                uniform_list(
3191                    "settings-ui-nav-bar",
3192                    self.navbar_entries.len(),
3193                    cx.processor(|this, range: Range<usize>, _, cx| {
3194                        range
3195                            .into_iter()
3196                            .map(|ix| {
3197                                let entry = &this.navbar_entries[ix];
3198
3199                                TreeViewItem::new(("settings-ui-navbar-entry", ix), entry.title)
3200                                    .root_item(entry.is_root)
3201                                    .toggle_state(this.is_navbar_entry_selected(ix))
3202                                    .when(entry.is_root, |item| {
3203                                        item.toggle(
3204                                            this.pages[this.page_index_from_navbar_index(ix)]
3205                                                .expanded,
3206                                        )
3207                                        .on_toggle(
3208                                            cx.listener(move |this, _, _, cx| {
3209                                                this.toggle_navbar_entry(ix);
3210                                                cx.notify();
3211                                            }),
3212                                        )
3213                                    })
3214                                    .on_click(cx.listener(move |this, _, _, cx| {
3215                                        this.navbar_entry = ix;
3216                                        cx.notify();
3217                                    }))
3218                                    .into_any_element()
3219                            })
3220                            .collect()
3221                    }),
3222                )
3223                .track_scroll(self.list_handle.clone())
3224                .size_full()
3225                .flex_grow(),
3226            )
3227    }
3228
3229    fn page_items(&self) -> impl Iterator<Item = &SettingsPageItem> {
3230        let page_idx = self.current_page_index();
3231
3232        self.current_page()
3233            .items
3234            .iter()
3235            .enumerate()
3236            .filter_map(move |(item_index, item)| {
3237                self.search_matches[page_idx][item_index].then_some(item)
3238            })
3239    }
3240
3241    fn render_page(&self, window: &mut Window, cx: &mut Context<SettingsWindow>) -> Stateful<Div> {
3242        let items: Vec<_> = self.page_items().collect();
3243        let items_len = items.len();
3244
3245        v_flex()
3246            .id("settings-ui-page")
3247            .gap_4()
3248            .children(items.into_iter().enumerate().map(|(index, item)| {
3249                let is_last = index == items_len - 1;
3250                item.render(self.current_file.clone(), is_last, window, cx)
3251            }))
3252            .overflow_y_scroll()
3253            .track_scroll(
3254                window
3255                    .use_state(cx, |_, _| ScrollHandle::default())
3256                    .read(cx),
3257            )
3258    }
3259
3260    fn current_page_index(&self) -> usize {
3261        self.page_index_from_navbar_index(self.navbar_entry)
3262    }
3263
3264    fn current_page(&self) -> &SettingsPage {
3265        &self.pages[self.current_page_index()]
3266    }
3267
3268    fn page_index_from_navbar_index(&self, index: usize) -> usize {
3269        if self.navbar_entries.is_empty() {
3270            return 0;
3271        }
3272
3273        self.navbar_entries[index].page_index
3274    }
3275
3276    fn page_for_navbar_index(&mut self, index: usize) -> &mut SettingsPage {
3277        let index = self.page_index_from_navbar_index(index);
3278        &mut self.pages[index]
3279    }
3280
3281    fn is_navbar_entry_selected(&self, ix: usize) -> bool {
3282        ix == self.navbar_entry
3283    }
3284}
3285
3286impl Render for SettingsWindow {
3287    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3288        let ui_font = theme::setup_ui_font(window, cx);
3289
3290        div()
3291            .flex()
3292            .flex_row()
3293            .size_full()
3294            .font(ui_font)
3295            .bg(cx.theme().colors().background)
3296            .text_color(cx.theme().colors().text)
3297            .child(self.render_nav(window, cx))
3298            .child(
3299                v_flex()
3300                    .w_full()
3301                    .pt_4()
3302                    .px_6()
3303                    .gap_4()
3304                    .bg(cx.theme().colors().editor_background)
3305                    .child(self.render_files(window, cx))
3306                    .child(self.render_page(window, cx)),
3307            )
3308    }
3309}
3310
3311fn render_text_field<T: From<String> + Into<String> + AsRef<str> + Clone>(
3312    field: SettingField<T>,
3313    file: SettingsUiFile,
3314    metadata: Option<&SettingsFieldMetadata>,
3315    cx: &mut App,
3316) -> AnyElement {
3317    let (_, initial_text) =
3318        SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick);
3319    let initial_text = Some(initial_text.clone()).filter(|s| !s.as_ref().is_empty());
3320
3321    SettingsEditor::new()
3322        .when_some(initial_text, |editor, text| {
3323            editor.with_initial_text(text.into())
3324        })
3325        .when_some(
3326            metadata.and_then(|metadata| metadata.placeholder),
3327            |editor, placeholder| editor.with_placeholder(placeholder),
3328        )
3329        .on_confirm(move |new_text, cx: &mut App| {
3330            cx.update_global(move |store: &mut SettingsStore, cx| {
3331                store.update_settings_file(<dyn fs::Fs>::global(cx), move |settings, _cx| {
3332                    *(field.pick_mut)(settings) = new_text.map(Into::into);
3333                });
3334            });
3335        })
3336        .into_any_element()
3337}
3338
3339fn render_toggle_button<B: Into<bool> + From<bool> + Copy>(
3340    field: SettingField<B>,
3341    file: SettingsUiFile,
3342    cx: &mut App,
3343) -> AnyElement {
3344    let (_, &value) = SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick);
3345
3346    let toggle_state = if value.into() {
3347        ToggleState::Selected
3348    } else {
3349        ToggleState::Unselected
3350    };
3351
3352    Switch::new("toggle_button", toggle_state)
3353        .color(ui::SwitchColor::Accent)
3354        .on_click({
3355            move |state, _window, cx| {
3356                let state = *state == ui::ToggleState::Selected;
3357                let field = field;
3358                cx.update_global(move |store: &mut SettingsStore, cx| {
3359                    store.update_settings_file(<dyn fs::Fs>::global(cx), move |settings, _cx| {
3360                        *(field.pick_mut)(settings) = Some(state.into());
3361                    });
3362                });
3363            }
3364        })
3365        .color(SwitchColor::Accent)
3366        .into_any_element()
3367}
3368
3369fn render_dropdown<T>(
3370    field: SettingField<T>,
3371    file: SettingsUiFile,
3372    window: &mut Window,
3373    cx: &mut App,
3374) -> AnyElement
3375where
3376    T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + 'static,
3377{
3378    let variants = || -> &'static [T] { <T as strum::VariantArray>::VARIANTS };
3379    let labels = || -> &'static [&'static str] { <T as strum::VariantNames>::VARIANTS };
3380
3381    let (_, &current_value) =
3382        SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick);
3383
3384    let current_value_label =
3385        labels()[variants().iter().position(|v| *v == current_value).unwrap()];
3386
3387    DropdownMenu::new(
3388        "dropdown",
3389        current_value_label,
3390        ContextMenu::build(window, cx, move |mut menu, _, _| {
3391            for (value, label) in variants()
3392                .into_iter()
3393                .copied()
3394                .zip(labels().into_iter().copied())
3395            {
3396                menu = menu.toggleable_entry(
3397                    label,
3398                    value == current_value,
3399                    IconPosition::Start,
3400                    None,
3401                    move |_, cx| {
3402                        if value == current_value {
3403                            return;
3404                        }
3405                        cx.update_global(move |store: &mut SettingsStore, cx| {
3406                            store.update_settings_file(
3407                                <dyn fs::Fs>::global(cx),
3408                                move |settings, _cx| {
3409                                    *(field.pick_mut)(settings) = Some(value);
3410                                },
3411                            );
3412                        });
3413                    },
3414                );
3415            }
3416            menu
3417        }),
3418    )
3419    .style(DropdownStyle::Outlined)
3420    .into_any_element()
3421}
3422
3423#[cfg(test)]
3424mod test {
3425
3426    use super::*;
3427
3428    impl SettingsWindow {
3429        fn navbar(&self) -> &[NavBarEntry] {
3430            self.navbar_entries.as_slice()
3431        }
3432
3433        fn navbar_entry(&self) -> usize {
3434            self.navbar_entry
3435        }
3436
3437        fn new_builder(window: &mut Window, cx: &mut Context<Self>) -> Self {
3438            let mut this = Self::new(window, cx);
3439            this.navbar_entries.clear();
3440            this.pages.clear();
3441            this
3442        }
3443
3444        fn build(mut self) -> Self {
3445            self.build_navbar();
3446            self
3447        }
3448
3449        fn add_page(
3450            mut self,
3451            title: &'static str,
3452            build_page: impl Fn(SettingsPage) -> SettingsPage,
3453        ) -> Self {
3454            let page = SettingsPage {
3455                title,
3456                expanded: false,
3457                items: Vec::default(),
3458            };
3459
3460            self.pages.push(build_page(page));
3461            self
3462        }
3463
3464        fn search(&mut self, search_query: &str, window: &mut Window, cx: &mut Context<Self>) {
3465            self.search_task.take();
3466            self.search_bar.update(cx, |editor, cx| {
3467                editor.set_text(search_query, window, cx);
3468            });
3469            self.update_matches(cx);
3470        }
3471
3472        fn assert_search_results(&self, other: &Self) {
3473            // page index could be different because of filtered out pages
3474            assert!(
3475                self.navbar_entries
3476                    .iter()
3477                    .zip(other.navbar_entries.iter())
3478                    .all(|(entry, other)| {
3479                        entry.is_root == other.is_root && entry.title == other.title
3480                    })
3481            );
3482            assert_eq!(
3483                self.current_page().items.iter().collect::<Vec<_>>(),
3484                other.page_items().collect::<Vec<_>>()
3485            );
3486        }
3487    }
3488
3489    impl SettingsPage {
3490        fn item(mut self, item: SettingsPageItem) -> Self {
3491            self.items.push(item);
3492            self
3493        }
3494    }
3495
3496    impl SettingsPageItem {
3497        fn basic_item(title: &'static str, description: &'static str) -> Self {
3498            SettingsPageItem::SettingItem(SettingItem {
3499                title,
3500                description,
3501                field: Box::new(SettingField {
3502                    pick: |settings_content| &settings_content.auto_update,
3503                    pick_mut: |settings_content| &mut settings_content.auto_update,
3504                }),
3505                metadata: None,
3506            })
3507        }
3508    }
3509
3510    fn register_settings(cx: &mut App) {
3511        settings::init(cx);
3512        theme::init(theme::LoadThemes::JustBase, cx);
3513        workspace::init_settings(cx);
3514        project::Project::init_settings(cx);
3515        language::init(cx);
3516        editor::init(cx);
3517        menu::init();
3518    }
3519
3520    fn parse(input: &'static str, window: &mut Window, cx: &mut App) -> SettingsWindow {
3521        let mut pages: Vec<SettingsPage> = Vec::new();
3522        let mut current_page = None;
3523        let mut selected_idx = None;
3524        let mut ix = 0;
3525        let mut in_closed_subentry = false;
3526
3527        for mut line in input
3528            .lines()
3529            .map(|line| line.trim())
3530            .filter(|line| !line.is_empty())
3531        {
3532            let mut is_selected = false;
3533            if line.ends_with("*") {
3534                assert!(
3535                    selected_idx.is_none(),
3536                    "Can only have one selected navbar entry at a time"
3537                );
3538                selected_idx = Some(ix);
3539                line = &line[..line.len() - 1];
3540                is_selected = true;
3541            }
3542
3543            if line.starts_with("v") || line.starts_with(">") {
3544                if let Some(current_page) = current_page.take() {
3545                    pages.push(current_page);
3546                }
3547
3548                let expanded = line.starts_with("v");
3549                in_closed_subentry = !expanded;
3550                ix += 1;
3551
3552                current_page = Some(SettingsPage {
3553                    title: line.split_once(" ").unwrap().1,
3554                    expanded,
3555                    items: Vec::default(),
3556                });
3557            } else if line.starts_with("-") {
3558                if !in_closed_subentry {
3559                    ix += 1;
3560                } else if is_selected && in_closed_subentry {
3561                    panic!("Can't select sub entry if it's parent is closed");
3562                }
3563
3564                let Some(current_page) = current_page.as_mut() else {
3565                    panic!("Sub entries must be within a page");
3566                };
3567
3568                current_page.items.push(SettingsPageItem::SectionHeader(
3569                    line.split_once(" ").unwrap().1,
3570                ));
3571            } else {
3572                panic!(
3573                    "Entries must start with one of 'v', '>', or '-'\n line: {}",
3574                    line
3575                );
3576            }
3577        }
3578
3579        if let Some(current_page) = current_page.take() {
3580            pages.push(current_page);
3581        }
3582
3583        let search_matches = pages
3584            .iter()
3585            .map(|page| vec![true; page.items.len()])
3586            .collect::<Vec<_>>();
3587
3588        let mut settings_window = SettingsWindow {
3589            files: Vec::default(),
3590            current_file: crate::SettingsUiFile::User,
3591            pages,
3592            search_bar: cx.new(|cx| Editor::single_line(window, cx)),
3593            navbar_entry: selected_idx.expect("Must have a selected navbar entry"),
3594            navbar_entries: Vec::default(),
3595            list_handle: UniformListScrollHandle::default(),
3596            search_matches,
3597            search_task: None,
3598        };
3599
3600        settings_window.build_navbar();
3601        settings_window
3602    }
3603
3604    #[track_caller]
3605    fn check_navbar_toggle(
3606        before: &'static str,
3607        toggle_idx: usize,
3608        after: &'static str,
3609        window: &mut Window,
3610        cx: &mut App,
3611    ) {
3612        let mut settings_window = parse(before, window, cx);
3613        settings_window.toggle_navbar_entry(toggle_idx);
3614
3615        let expected_settings_window = parse(after, window, cx);
3616
3617        assert_eq!(settings_window.navbar(), expected_settings_window.navbar());
3618        assert_eq!(
3619            settings_window.navbar_entry(),
3620            expected_settings_window.navbar_entry()
3621        );
3622    }
3623
3624    macro_rules! check_navbar_toggle {
3625        ($name:ident, before: $before:expr, toggle_idx: $toggle_idx:expr, after: $after:expr) => {
3626            #[gpui::test]
3627            fn $name(cx: &mut gpui::TestAppContext) {
3628                let window = cx.add_empty_window();
3629                window.update(|window, cx| {
3630                    register_settings(cx);
3631                    check_navbar_toggle($before, $toggle_idx, $after, window, cx);
3632                });
3633            }
3634        };
3635    }
3636
3637    check_navbar_toggle!(
3638        navbar_basic_open,
3639        before: r"
3640        v General
3641        - General
3642        - Privacy*
3643        v Project
3644        - Project Settings
3645        ",
3646        toggle_idx: 0,
3647        after: r"
3648        > General*
3649        v Project
3650        - Project Settings
3651        "
3652    );
3653
3654    check_navbar_toggle!(
3655        navbar_basic_close,
3656        before: r"
3657        > General*
3658        - General
3659        - Privacy
3660        v Project
3661        - Project Settings
3662        ",
3663        toggle_idx: 0,
3664        after: r"
3665        v General*
3666        - General
3667        - Privacy
3668        v Project
3669        - Project Settings
3670        "
3671    );
3672
3673    check_navbar_toggle!(
3674        navbar_basic_second_root_entry_close,
3675        before: r"
3676        > General
3677        - General
3678        - Privacy
3679        v Project
3680        - Project Settings*
3681        ",
3682        toggle_idx: 1,
3683        after: r"
3684        > General
3685        > Project*
3686        "
3687    );
3688
3689    check_navbar_toggle!(
3690        navbar_toggle_subroot,
3691        before: r"
3692        v General Page
3693        - General
3694        - Privacy
3695        v Project
3696        - Worktree Settings Content*
3697        v AI
3698        - General
3699        > Appearance & Behavior
3700        ",
3701        toggle_idx: 3,
3702        after: r"
3703        v General Page
3704        - General
3705        - Privacy
3706        > Project*
3707        v AI
3708        - General
3709        > Appearance & Behavior
3710        "
3711    );
3712
3713    check_navbar_toggle!(
3714        navbar_toggle_close_propagates_selected_index,
3715        before: r"
3716        v General Page
3717        - General
3718        - Privacy
3719        v Project
3720        - Worktree Settings Content
3721        v AI
3722        - General*
3723        > Appearance & Behavior
3724        ",
3725        toggle_idx: 0,
3726        after: r"
3727        > General Page
3728        v Project
3729        - Worktree Settings Content
3730        v AI
3731        - General*
3732        > Appearance & Behavior
3733        "
3734    );
3735
3736    check_navbar_toggle!(
3737        navbar_toggle_expand_propagates_selected_index,
3738        before: r"
3739        > General Page
3740        - General
3741        - Privacy
3742        v Project
3743        - Worktree Settings Content
3744        v AI
3745        - General*
3746        > Appearance & Behavior
3747        ",
3748        toggle_idx: 0,
3749        after: r"
3750        v General Page
3751        - General
3752        - Privacy
3753        v Project
3754        - Worktree Settings Content
3755        v AI
3756        - General*
3757        > Appearance & Behavior
3758        "
3759    );
3760
3761    check_navbar_toggle!(
3762        navbar_toggle_sub_entry_does_nothing,
3763        before: r"
3764        > General Page
3765        - General
3766        - Privacy
3767        v Project
3768        - Worktree Settings Content
3769        v AI
3770        - General*
3771        > Appearance & Behavior
3772        ",
3773        toggle_idx: 4,
3774        after: r"
3775        > General Page
3776        - General
3777        - Privacy
3778        v Project
3779        - Worktree Settings Content
3780        v AI
3781        - General*
3782        > Appearance & Behavior
3783        "
3784    );
3785
3786    #[gpui::test]
3787    fn test_basic_search(cx: &mut gpui::TestAppContext) {
3788        let cx = cx.add_empty_window();
3789        let (actual, expected) = cx.update(|window, cx| {
3790            register_settings(cx);
3791
3792            let expected = cx.new(|cx| {
3793                SettingsWindow::new_builder(window, cx)
3794                    .add_page("General", |page| {
3795                        page.item(SettingsPageItem::SectionHeader("General settings"))
3796                            .item(SettingsPageItem::basic_item("test title", "General test"))
3797                    })
3798                    .build()
3799            });
3800
3801            let actual = cx.new(|cx| {
3802                SettingsWindow::new_builder(window, cx)
3803                    .add_page("General", |page| {
3804                        page.item(SettingsPageItem::SectionHeader("General settings"))
3805                            .item(SettingsPageItem::basic_item("test title", "General test"))
3806                    })
3807                    .add_page("Theme", |page| {
3808                        page.item(SettingsPageItem::SectionHeader("Theme settings"))
3809                    })
3810                    .build()
3811            });
3812
3813            actual.update(cx, |settings, cx| settings.search("gen", window, cx));
3814
3815            (actual, expected)
3816        });
3817
3818        cx.cx.run_until_parked();
3819
3820        cx.update(|_window, cx| {
3821            let expected = expected.read(cx);
3822            let actual = actual.read(cx);
3823            expected.assert_search_results(&actual);
3824        })
3825    }
3826
3827    #[gpui::test]
3828    fn test_search_render_page_with_filtered_out_navbar_entries(cx: &mut gpui::TestAppContext) {
3829        let cx = cx.add_empty_window();
3830        let (actual, expected) = cx.update(|window, cx| {
3831            register_settings(cx);
3832
3833            let actual = cx.new(|cx| {
3834                SettingsWindow::new_builder(window, cx)
3835                    .add_page("General", |page| {
3836                        page.item(SettingsPageItem::SectionHeader("General settings"))
3837                            .item(SettingsPageItem::basic_item(
3838                                "Confirm Quit",
3839                                "Whether to confirm before quitting Zed",
3840                            ))
3841                            .item(SettingsPageItem::basic_item(
3842                                "Auto Update",
3843                                "Automatically update Zed",
3844                            ))
3845                    })
3846                    .add_page("AI", |page| {
3847                        page.item(SettingsPageItem::basic_item(
3848                            "Disable AI",
3849                            "Whether to disable all AI features in Zed",
3850                        ))
3851                    })
3852                    .add_page("Appearance & Behavior", |page| {
3853                        page.item(SettingsPageItem::SectionHeader("Cursor")).item(
3854                            SettingsPageItem::basic_item(
3855                                "Cursor Shape",
3856                                "Cursor shape for the editor",
3857                            ),
3858                        )
3859                    })
3860                    .build()
3861            });
3862
3863            let expected = cx.new(|cx| {
3864                SettingsWindow::new_builder(window, cx)
3865                    .add_page("Appearance & Behavior", |page| {
3866                        page.item(SettingsPageItem::SectionHeader("Cursor")).item(
3867                            SettingsPageItem::basic_item(
3868                                "Cursor Shape",
3869                                "Cursor shape for the editor",
3870                            ),
3871                        )
3872                    })
3873                    .build()
3874            });
3875
3876            actual.update(cx, |settings, cx| settings.search("cursor", window, cx));
3877
3878            (actual, expected)
3879        });
3880
3881        cx.cx.run_until_parked();
3882
3883        cx.update(|_window, cx| {
3884            let expected = expected.read(cx);
3885            let actual = actual.read(cx);
3886            expected.assert_search_results(&actual);
3887        })
3888    }
3889}