settings_ui.rs

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