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, 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: "Languages & Frameworks",
1363            expanded: false,
1364            items: vec![
1365                SettingsPageItem::SectionHeader("General"),
1366                SettingsPageItem::SettingItem(SettingItem {
1367                    title: "Enable Language Server",
1368                    description: "Whether to use language servers to provide code intelligence",
1369                    field: Box::new(SettingField {
1370                        pick: |settings_content| {
1371                            &settings_content
1372                                .project
1373                                .all_languages
1374                                .defaults
1375                                .enable_language_server
1376                        },
1377                        pick_mut: |settings_content| {
1378                            &mut settings_content
1379                                .project
1380                                .all_languages
1381                                .defaults
1382                                .enable_language_server
1383                        },
1384                    }),
1385                    metadata: None,
1386                }),
1387                SettingsPageItem::SectionHeader("Languages"),
1388                SettingsPageItem::SubPageLink(SubPageLink {
1389                    title: "JSON",
1390                    render: Rc::new(|_, _, _| "A settings page!".into_any_element()),
1391                }),
1392            ],
1393        },
1394        SettingsPage {
1395            title: "Workbench & Window",
1396            expanded: false,
1397            items: vec![
1398                SettingsPageItem::SectionHeader("Workbench"),
1399                SettingsPageItem::SettingItem(SettingItem {
1400                    title: "Editor Tabs",
1401                    description: "Whether or not to show the tab bar in the editor",
1402                    field: Box::new(SettingField {
1403                        pick: |settings_content| {
1404                            if let Some(tab_bar) = &settings_content.tab_bar {
1405                                &tab_bar.show
1406                            } else {
1407                                &None
1408                            }
1409                        },
1410                        pick_mut: |settings_content| {
1411                            &mut settings_content.tab_bar.get_or_insert_default().show
1412                        },
1413                    }),
1414                    metadata: None,
1415                }),
1416                SettingsPageItem::SettingItem(SettingItem {
1417                    title: "Active language Button",
1418                    description: "Whether to show the active language button in the status bar",
1419                    field: Box::new(SettingField {
1420                        pick: |settings_content| {
1421                            if let Some(status_bar) = &settings_content.status_bar {
1422                                &status_bar.active_language_button
1423                            } else {
1424                                &None
1425                            }
1426                        },
1427                        pick_mut: |settings_content| {
1428                            &mut settings_content
1429                                .status_bar
1430                                .get_or_insert_default()
1431                                .active_language_button
1432                        },
1433                    }),
1434                    metadata: None,
1435                }),
1436                SettingsPageItem::SettingItem(SettingItem {
1437                    title: "Cursor Position Button",
1438                    description: "Whether to show the cursor position button in the status bar",
1439                    field: Box::new(SettingField {
1440                        pick: |settings_content| {
1441                            if let Some(status_bar) = &settings_content.status_bar {
1442                                &status_bar.cursor_position_button
1443                            } else {
1444                                &None
1445                            }
1446                        },
1447                        pick_mut: |settings_content| {
1448                            &mut settings_content
1449                                .status_bar
1450                                .get_or_insert_default()
1451                                .cursor_position_button
1452                        },
1453                    }),
1454                    metadata: None,
1455                }),
1456                SettingsPageItem::SectionHeader("Terminal"),
1457                SettingsPageItem::SettingItem(SettingItem {
1458                    title: "Terminal Button",
1459                    description: "Whether to show the terminal button in the status bar",
1460                    field: Box::new(SettingField {
1461                        pick: |settings_content| {
1462                            if let Some(terminal) = &settings_content.terminal {
1463                                &terminal.button
1464                            } else {
1465                                &None
1466                            }
1467                        },
1468                        pick_mut: |settings_content| {
1469                            &mut settings_content.terminal.get_or_insert_default().button
1470                        },
1471                    }),
1472                    metadata: None,
1473                }),
1474                SettingsPageItem::SettingItem(SettingItem {
1475                    title: "Show Navigation History Buttons",
1476                    description: "Whether or not to show the navigation history buttons in the tab bar",
1477                    field: Box::new(SettingField {
1478                        pick: |settings_content| {
1479                            if let Some(tab_bar) = &settings_content.tab_bar {
1480                                &tab_bar.show_nav_history_buttons
1481                            } else {
1482                                &None
1483                            }
1484                        },
1485                        pick_mut: |settings_content| {
1486                            &mut settings_content
1487                                .tab_bar
1488                                .get_or_insert_default()
1489                                .show_nav_history_buttons
1490                        },
1491                    }),
1492                    metadata: None,
1493                }),
1494            ],
1495        },
1496        SettingsPage {
1497            title: "Panels & Tools",
1498            expanded: false,
1499            items: vec![
1500                SettingsPageItem::SectionHeader("Project Panel"),
1501                SettingsPageItem::SettingItem(SettingItem {
1502                    title: "Project Panel Button",
1503                    description: "Whether to show the project panel button in the status bar",
1504                    field: Box::new(SettingField {
1505                        pick: |settings_content| {
1506                            if let Some(project_panel) = &settings_content.project_panel {
1507                                &project_panel.button
1508                            } else {
1509                                &None
1510                            }
1511                        },
1512                        pick_mut: |settings_content| {
1513                            &mut settings_content
1514                                .project_panel
1515                                .get_or_insert_default()
1516                                .button
1517                        },
1518                    }),
1519                    metadata: None,
1520                }),
1521                SettingsPageItem::SettingItem(SettingItem {
1522                    title: "Project Panel Dock",
1523                    description: "Where to dock the project panel",
1524                    field: Box::new(SettingField {
1525                        pick: |settings_content| {
1526                            if let Some(project_panel) = &settings_content.project_panel {
1527                                &project_panel.dock
1528                            } else {
1529                                &None
1530                            }
1531                        },
1532                        pick_mut: |settings_content| {
1533                            &mut settings_content.project_panel.get_or_insert_default().dock
1534                        },
1535                    }),
1536                    metadata: None,
1537                }),
1538                // todo(settings_ui): Needs numeric stepper
1539                // SettingsPageItem::SettingItem(SettingItem {
1540                //     title: "Project Panel Default Width",
1541                //     description: "Default width of the project panel in pixels",
1542                //     field: Box::new(SettingField {
1543                //         pick: |settings_content| {
1544                //             if let Some(project_panel) = &settings_content.project_panel {
1545                //                 &project_panel.default_width
1546                //             } else {
1547                //                 &None
1548                //             }
1549                //         },
1550                //         pick_mut: |settings_content| {
1551                //             &mut settings_content
1552                //                 .project_panel
1553                //                 .get_or_insert_default()
1554                //                 .default_width
1555                //         },
1556                //     }),
1557                //     metadata: None,
1558                // }),
1559                SettingsPageItem::SectionHeader("Terminal"),
1560                SettingsPageItem::SettingItem(SettingItem {
1561                    title: "Terminal Dock",
1562                    description: "Where to dock the terminal panel",
1563                    field: Box::new(SettingField {
1564                        pick: |settings_content| {
1565                            if let Some(terminal) = &settings_content.terminal {
1566                                &terminal.dock
1567                            } else {
1568                                &None
1569                            }
1570                        },
1571                        pick_mut: |settings_content| {
1572                            &mut settings_content.terminal.get_or_insert_default().dock
1573                        },
1574                    }),
1575                    metadata: None,
1576                }),
1577                SettingsPageItem::SectionHeader("Tab Settings"),
1578                SettingsPageItem::SettingItem(SettingItem {
1579                    title: "Activate On Close",
1580                    description: "What to do after closing the current tab",
1581                    field: Box::new(SettingField {
1582                        pick: |settings_content| {
1583                            if let Some(tabs) = &settings_content.tabs {
1584                                &tabs.activate_on_close
1585                            } else {
1586                                &None
1587                            }
1588                        },
1589                        pick_mut: |settings_content| {
1590                            &mut settings_content
1591                                .tabs
1592                                .get_or_insert_default()
1593                                .activate_on_close
1594                        },
1595                    }),
1596                    metadata: None,
1597                }),
1598                SettingsPageItem::SettingItem(SettingItem {
1599                    title: "Tab Show Diagnostics",
1600                    description: "Which files containing diagnostic errors/warnings to mark in the tabs",
1601                    field: Box::new(SettingField {
1602                        pick: |settings_content| {
1603                            if let Some(tabs) = &settings_content.tabs {
1604                                &tabs.show_diagnostics
1605                            } else {
1606                                &None
1607                            }
1608                        },
1609                        pick_mut: |settings_content| {
1610                            &mut settings_content
1611                                .tabs
1612                                .get_or_insert_default()
1613                                .show_diagnostics
1614                        },
1615                    }),
1616                    metadata: None,
1617                }),
1618                SettingsPageItem::SettingItem(SettingItem {
1619                    title: "Show Close Button",
1620                    description: "Controls the appearance behavior of the tab's close button",
1621                    field: Box::new(SettingField {
1622                        pick: |settings_content| {
1623                            if let Some(tabs) = &settings_content.tabs {
1624                                &tabs.show_close_button
1625                            } else {
1626                                &None
1627                            }
1628                        },
1629                        pick_mut: |settings_content| {
1630                            &mut settings_content
1631                                .tabs
1632                                .get_or_insert_default()
1633                                .show_close_button
1634                        },
1635                    }),
1636                    metadata: None,
1637                }),
1638                SettingsPageItem::SectionHeader("Preview Tabs"),
1639                SettingsPageItem::SettingItem(SettingItem {
1640                    title: "Preview Tabs Enabled",
1641                    description: "Whether to show opened editors as preview tabs",
1642                    field: Box::new(SettingField {
1643                        pick: |settings_content| {
1644                            if let Some(preview_tabs) = &settings_content.preview_tabs {
1645                                &preview_tabs.enabled
1646                            } else {
1647                                &None
1648                            }
1649                        },
1650                        pick_mut: |settings_content| {
1651                            &mut settings_content
1652                                .preview_tabs
1653                                .get_or_insert_default()
1654                                .enabled
1655                        },
1656                    }),
1657                    metadata: None,
1658                }),
1659                SettingsPageItem::SettingItem(SettingItem {
1660                    title: "Enable Preview From File Finder",
1661                    description: "Whether to open tabs in preview mode when selected from the file finder",
1662                    field: Box::new(SettingField {
1663                        pick: |settings_content| {
1664                            if let Some(preview_tabs) = &settings_content.preview_tabs {
1665                                &preview_tabs.enable_preview_from_file_finder
1666                            } else {
1667                                &None
1668                            }
1669                        },
1670                        pick_mut: |settings_content| {
1671                            &mut settings_content
1672                                .preview_tabs
1673                                .get_or_insert_default()
1674                                .enable_preview_from_file_finder
1675                        },
1676                    }),
1677                    metadata: None,
1678                }),
1679                SettingsPageItem::SettingItem(SettingItem {
1680                    title: "Enable Preview From Code Navigation",
1681                    description: "Whether a preview tab gets replaced when code navigation is used to navigate away from the tab",
1682                    field: Box::new(SettingField {
1683                        pick: |settings_content| {
1684                            if let Some(preview_tabs) = &settings_content.preview_tabs {
1685                                &preview_tabs.enable_preview_from_code_navigation
1686                            } else {
1687                                &None
1688                            }
1689                        },
1690                        pick_mut: |settings_content| {
1691                            &mut settings_content
1692                                .preview_tabs
1693                                .get_or_insert_default()
1694                                .enable_preview_from_code_navigation
1695                        },
1696                    }),
1697                    metadata: None,
1698                }),
1699            ],
1700        },
1701        SettingsPage {
1702            title: "Version Control",
1703            expanded: false,
1704            items: vec![
1705                SettingsPageItem::SectionHeader("Git"),
1706                SettingsPageItem::SettingItem(SettingItem {
1707                    title: "Git Gutter",
1708                    description: "Control whether the git gutter is shown",
1709                    field: Box::new(SettingField {
1710                        pick: |settings_content| {
1711                            if let Some(git) = &settings_content.git {
1712                                &git.git_gutter
1713                            } else {
1714                                &None
1715                            }
1716                        },
1717                        pick_mut: |settings_content| {
1718                            &mut settings_content.git.get_or_insert_default().git_gutter
1719                        },
1720                    }),
1721                    metadata: None,
1722                }),
1723                // todo(settings_ui): Needs numeric stepper
1724                // SettingsPageItem::SettingItem(SettingItem {
1725                //     title: "Gutter Debounce",
1726                //     description: "Debounce threshold in milliseconds after which changes are reflected in the git gutter",
1727                //     field: Box::new(SettingField {
1728                //         pick: |settings_content| {
1729                //             if let Some(git) = &settings_content.git {
1730                //                 &git.gutter_debounce
1731                //             } else {
1732                //                 &None
1733                //             }
1734                //         },
1735                //         pick_mut: |settings_content| {
1736                //             &mut settings_content.git.get_or_insert_default().gutter_debounce
1737                //         },
1738                //     }),
1739                //     metadata: None,
1740                // }),
1741                SettingsPageItem::SettingItem(SettingItem {
1742                    title: "Inline Blame Enabled",
1743                    description: "Whether or not to show git blame data inline in the currently focused line",
1744                    field: Box::new(SettingField {
1745                        pick: |settings_content| {
1746                            if let Some(git) = &settings_content.git {
1747                                if let Some(inline_blame) = &git.inline_blame {
1748                                    &inline_blame.enabled
1749                                } else {
1750                                    &None
1751                                }
1752                            } else {
1753                                &None
1754                            }
1755                        },
1756                        pick_mut: |settings_content| {
1757                            &mut settings_content
1758                                .git
1759                                .get_or_insert_default()
1760                                .inline_blame
1761                                .get_or_insert_default()
1762                                .enabled
1763                        },
1764                    }),
1765                    metadata: None,
1766                }),
1767                SettingsPageItem::SettingItem(SettingItem {
1768                    title: "Show Commit Summary",
1769                    description: "Whether to show commit summary as part of the inline blame",
1770                    field: Box::new(SettingField {
1771                        pick: |settings_content| {
1772                            if let Some(git) = &settings_content.git {
1773                                if let Some(inline_blame) = &git.inline_blame {
1774                                    &inline_blame.show_commit_summary
1775                                } else {
1776                                    &None
1777                                }
1778                            } else {
1779                                &None
1780                            }
1781                        },
1782                        pick_mut: |settings_content| {
1783                            &mut settings_content
1784                                .git
1785                                .get_or_insert_default()
1786                                .inline_blame
1787                                .get_or_insert_default()
1788                                .show_commit_summary
1789                        },
1790                    }),
1791                    metadata: None,
1792                }),
1793                SettingsPageItem::SettingItem(SettingItem {
1794                    title: "Show Avatar",
1795                    description: "Whether to show the avatar of the author of the commit",
1796                    field: Box::new(SettingField {
1797                        pick: |settings_content| {
1798                            if let Some(git) = &settings_content.git {
1799                                if let Some(blame) = &git.blame {
1800                                    &blame.show_avatar
1801                                } else {
1802                                    &None
1803                                }
1804                            } else {
1805                                &None
1806                            }
1807                        },
1808                        pick_mut: |settings_content| {
1809                            &mut settings_content
1810                                .git
1811                                .get_or_insert_default()
1812                                .blame
1813                                .get_or_insert_default()
1814                                .show_avatar
1815                        },
1816                    }),
1817                    metadata: None,
1818                }),
1819                SettingsPageItem::SettingItem(SettingItem {
1820                    title: "Show Author Name In Branch Picker",
1821                    description: "Whether to show author name as part of the commit information in branch picker",
1822                    field: Box::new(SettingField {
1823                        pick: |settings_content| {
1824                            if let Some(git) = &settings_content.git {
1825                                if let Some(branch_picker) = &git.branch_picker {
1826                                    &branch_picker.show_author_name
1827                                } else {
1828                                    &None
1829                                }
1830                            } else {
1831                                &None
1832                            }
1833                        },
1834                        pick_mut: |settings_content| {
1835                            &mut settings_content
1836                                .git
1837                                .get_or_insert_default()
1838                                .branch_picker
1839                                .get_or_insert_default()
1840                                .show_author_name
1841                        },
1842                    }),
1843                    metadata: None,
1844                }),
1845                SettingsPageItem::SettingItem(SettingItem {
1846                    title: "Hunk Style",
1847                    description: "How git hunks are displayed visually in the editor",
1848                    field: Box::new(SettingField {
1849                        pick: |settings_content| {
1850                            if let Some(git) = &settings_content.git {
1851                                &git.hunk_style
1852                            } else {
1853                                &None
1854                            }
1855                        },
1856                        pick_mut: |settings_content| {
1857                            &mut settings_content.git.get_or_insert_default().hunk_style
1858                        },
1859                    }),
1860                    metadata: None,
1861                }),
1862            ],
1863        },
1864        SettingsPage {
1865            title: "System & Network",
1866            expanded: false,
1867            items: vec![
1868                SettingsPageItem::SectionHeader("Network"),
1869                // todo(settings_ui): Proxy needs a default
1870                // SettingsPageItem::SettingItem(SettingItem {
1871                //     title: "Proxy",
1872                //     description: "The proxy to use for network requests",
1873                //     field: Box::new(SettingField {
1874                //         pick: |settings_content| &settings_content.proxy,
1875                //         pick_mut: |settings_content| &mut settings_content.proxy,
1876                //     }),
1877                //     metadata: Some(Box::new(SettingsFieldMetadata {
1878                //         placeholder: Some("socks5h://localhost:10808"),
1879                //     })),
1880                // }),
1881                SettingsPageItem::SettingItem(SettingItem {
1882                    title: "Server URL",
1883                    description: "The URL of the Zed server to connect to",
1884                    field: Box::new(SettingField {
1885                        pick: |settings_content| &settings_content.server_url,
1886                        pick_mut: |settings_content| &mut settings_content.server_url,
1887                    }),
1888                    metadata: Some(Box::new(SettingsFieldMetadata {
1889                        placeholder: Some("https://zed.dev"),
1890                    })),
1891                }),
1892                SettingsPageItem::SectionHeader("System"),
1893                SettingsPageItem::SettingItem(SettingItem {
1894                    title: "Auto Update",
1895                    description: "Whether or not to automatically check for updates",
1896                    field: Box::new(SettingField {
1897                        pick: |settings_content| &settings_content.auto_update,
1898                        pick_mut: |settings_content| &mut settings_content.auto_update,
1899                    }),
1900                    metadata: None,
1901                }),
1902            ],
1903        },
1904        SettingsPage {
1905            title: "Diagnostics & Errors",
1906            expanded: false,
1907            items: vec![
1908                SettingsPageItem::SectionHeader("Display"),
1909                SettingsPageItem::SettingItem(SettingItem {
1910                    title: "Diagnostics Button",
1911                    description: "Whether to show the project diagnostics button in the status bar",
1912                    field: Box::new(SettingField {
1913                        pick: |settings_content| {
1914                            if let Some(diagnostics) = &settings_content.diagnostics {
1915                                &diagnostics.button
1916                            } else {
1917                                &None
1918                            }
1919                        },
1920                        pick_mut: |settings_content| {
1921                            &mut settings_content.diagnostics.get_or_insert_default().button
1922                        },
1923                    }),
1924                    metadata: None,
1925                }),
1926                SettingsPageItem::SectionHeader("Filtering"),
1927                SettingsPageItem::SettingItem(SettingItem {
1928                    title: "Max Severity",
1929                    description: "Which level to use to filter out diagnostics displayed in the editor",
1930                    field: Box::new(SettingField {
1931                        pick: |settings_content| &settings_content.editor.diagnostics_max_severity,
1932                        pick_mut: |settings_content| {
1933                            &mut settings_content.editor.diagnostics_max_severity
1934                        },
1935                    }),
1936                    metadata: None,
1937                }),
1938                SettingsPageItem::SettingItem(SettingItem {
1939                    title: "Include Warnings",
1940                    description: "Whether to show warnings or not by default",
1941                    field: Box::new(SettingField {
1942                        pick: |settings_content| {
1943                            if let Some(diagnostics) = &settings_content.diagnostics {
1944                                &diagnostics.include_warnings
1945                            } else {
1946                                &None
1947                            }
1948                        },
1949                        pick_mut: |settings_content| {
1950                            &mut settings_content
1951                                .diagnostics
1952                                .get_or_insert_default()
1953                                .include_warnings
1954                        },
1955                    }),
1956                    metadata: None,
1957                }),
1958                SettingsPageItem::SectionHeader("Inline"),
1959                SettingsPageItem::SettingItem(SettingItem {
1960                    title: "Inline Diagnostics Enabled",
1961                    description: "Whether to show diagnostics inline or not",
1962                    field: Box::new(SettingField {
1963                        pick: |settings_content| {
1964                            if let Some(diagnostics) = &settings_content.diagnostics {
1965                                if let Some(inline) = &diagnostics.inline {
1966                                    &inline.enabled
1967                                } else {
1968                                    &None
1969                                }
1970                            } else {
1971                                &None
1972                            }
1973                        },
1974                        pick_mut: |settings_content| {
1975                            &mut settings_content
1976                                .diagnostics
1977                                .get_or_insert_default()
1978                                .inline
1979                                .get_or_insert_default()
1980                                .enabled
1981                        },
1982                    }),
1983                    metadata: None,
1984                }),
1985                // todo(settings_ui): Needs numeric stepper
1986                // SettingsPageItem::SettingItem(SettingItem {
1987                //     title: "Inline Update Debounce",
1988                //     description: "The delay in milliseconds to show inline diagnostics after the last diagnostic update",
1989                //     field: Box::new(SettingField {
1990                //         pick: |settings_content| {
1991                //             if let Some(diagnostics) = &settings_content.diagnostics {
1992                //                 if let Some(inline) = &diagnostics.inline {
1993                //                     &inline.update_debounce_ms
1994                //                 } else {
1995                //                     &None
1996                //                 }
1997                //             } else {
1998                //                 &None
1999                //             }
2000                //         },
2001                //         pick_mut: |settings_content| {
2002                //             &mut settings_content
2003                //                 .diagnostics
2004                //                 .get_or_insert_default()
2005                //                 .inline
2006                //                 .get_or_insert_default()
2007                //                 .update_debounce_ms
2008                //         },
2009                //     }),
2010                //     metadata: None,
2011                // }),
2012                // todo(settings_ui): Needs numeric stepper
2013                // SettingsPageItem::SettingItem(SettingItem {
2014                //     title: "Inline Padding",
2015                //     description: "The amount of padding between the end of the source line and the start of the inline diagnostic",
2016                //     field: Box::new(SettingField {
2017                //         pick: |settings_content| {
2018                //             if let Some(diagnostics) = &settings_content.diagnostics {
2019                //                 if let Some(inline) = &diagnostics.inline {
2020                //                     &inline.padding
2021                //                 } else {
2022                //                     &None
2023                //                 }
2024                //             } else {
2025                //                 &None
2026                //             }
2027                //         },
2028                //         pick_mut: |settings_content| {
2029                //             &mut settings_content
2030                //                 .diagnostics
2031                //                 .get_or_insert_default()
2032                //                 .inline
2033                //                 .get_or_insert_default()
2034                //                 .padding
2035                //         },
2036                //     }),
2037                //     metadata: None,
2038                // }),
2039                // todo(settings_ui): Needs numeric stepper
2040                // SettingsPageItem::SettingItem(SettingItem {
2041                //     title: "Inline Min Column",
2042                //     description: "The minimum column to display inline diagnostics",
2043                //     field: Box::new(SettingField {
2044                //         pick: |settings_content| {
2045                //             if let Some(diagnostics) = &settings_content.diagnostics {
2046                //                 if let Some(inline) = &diagnostics.inline {
2047                //                     &inline.min_column
2048                //                 } else {
2049                //                     &None
2050                //                 }
2051                //             } else {
2052                //                 &None
2053                //             }
2054                //         },
2055                //         pick_mut: |settings_content| {
2056                //             &mut settings_content
2057                //                 .diagnostics
2058                //                 .get_or_insert_default()
2059                //                 .inline
2060                //                 .get_or_insert_default()
2061                //                 .min_column
2062                //         },
2063                //     }),
2064                //     metadata: None,
2065                // }),
2066                SettingsPageItem::SectionHeader("Performance"),
2067                SettingsPageItem::SettingItem(SettingItem {
2068                    title: "LSP Pull Diagnostics Enabled",
2069                    description: "Whether to pull for diagnostics or not",
2070                    field: Box::new(SettingField {
2071                        pick: |settings_content| {
2072                            if let Some(diagnostics) = &settings_content.diagnostics {
2073                                if let Some(lsp_pull) = &diagnostics.lsp_pull_diagnostics {
2074                                    &lsp_pull.enabled
2075                                } else {
2076                                    &None
2077                                }
2078                            } else {
2079                                &None
2080                            }
2081                        },
2082                        pick_mut: |settings_content| {
2083                            &mut settings_content
2084                                .diagnostics
2085                                .get_or_insert_default()
2086                                .lsp_pull_diagnostics
2087                                .get_or_insert_default()
2088                                .enabled
2089                        },
2090                    }),
2091                    metadata: None,
2092                }),
2093                // todo(settings_ui): Needs numeric stepper
2094                // SettingsPageItem::SettingItem(SettingItem {
2095                //     title: "LSP Pull Debounce",
2096                //     description: "Minimum time to wait before pulling diagnostics from the language server(s)",
2097                //     field: Box::new(SettingField {
2098                //         pick: |settings_content| {
2099                //             if let Some(diagnostics) = &settings_content.diagnostics {
2100                //                 if let Some(lsp_pull) = &diagnostics.lsp_pull_diagnostics {
2101                //                     &lsp_pull.debounce_ms
2102                //                 } else {
2103                //                     &None
2104                //                 }
2105                //             } else {
2106                //                 &None
2107                //             }
2108                //         },
2109                //         pick_mut: |settings_content| {
2110                //             &mut settings_content
2111                //                 .diagnostics
2112                //                 .get_or_insert_default()
2113                //                 .lsp_pull_diagnostics
2114                //                 .get_or_insert_default()
2115                //                 .debounce_ms
2116                //         },
2117                //     }),
2118                //     metadata: None,
2119                // }),
2120            ],
2121        },
2122        SettingsPage {
2123            title: "Collaboration",
2124            expanded: false,
2125            items: vec![
2126                SettingsPageItem::SectionHeader("Calls"),
2127                SettingsPageItem::SettingItem(SettingItem {
2128                    title: "Mute On Join",
2129                    description: "Whether the microphone should be muted when joining a channel or a call",
2130                    field: Box::new(SettingField {
2131                        pick: |settings_content| {
2132                            if let Some(calls) = &settings_content.calls {
2133                                &calls.mute_on_join
2134                            } else {
2135                                &None
2136                            }
2137                        },
2138                        pick_mut: |settings_content| {
2139                            &mut settings_content.calls.get_or_insert_default().mute_on_join
2140                        },
2141                    }),
2142                    metadata: None,
2143                }),
2144                SettingsPageItem::SettingItem(SettingItem {
2145                    title: "Share On Join",
2146                    description: "Whether your current project should be shared when joining an empty channel",
2147                    field: Box::new(SettingField {
2148                        pick: |settings_content| {
2149                            if let Some(calls) = &settings_content.calls {
2150                                &calls.share_on_join
2151                            } else {
2152                                &None
2153                            }
2154                        },
2155                        pick_mut: |settings_content| {
2156                            &mut settings_content.calls.get_or_insert_default().share_on_join
2157                        },
2158                    }),
2159                    metadata: None,
2160                }),
2161                SettingsPageItem::SectionHeader("Panel"),
2162                SettingsPageItem::SettingItem(SettingItem {
2163                    title: "Collaboration Panel Button",
2164                    description: "Whether to show the collaboration panel button in the status bar",
2165                    field: Box::new(SettingField {
2166                        pick: |settings_content| {
2167                            if let Some(collab) = &settings_content.collaboration_panel {
2168                                &collab.button
2169                            } else {
2170                                &None
2171                            }
2172                        },
2173                        pick_mut: |settings_content| {
2174                            &mut settings_content
2175                                .collaboration_panel
2176                                .get_or_insert_default()
2177                                .button
2178                        },
2179                    }),
2180                    metadata: None,
2181                }),
2182                SettingsPageItem::SectionHeader("Experimental"),
2183                SettingsPageItem::SettingItem(SettingItem {
2184                    title: "Rodio Audio",
2185                    description: "Opt into the new audio system",
2186                    field: Box::new(SettingField {
2187                        pick: |settings_content| {
2188                            if let Some(audio) = &settings_content.audio {
2189                                &audio.rodio_audio
2190                            } else {
2191                                &None
2192                            }
2193                        },
2194                        pick_mut: |settings_content| {
2195                            &mut settings_content.audio.get_or_insert_default().rodio_audio
2196                        },
2197                    }),
2198                    metadata: None,
2199                }),
2200            ],
2201        },
2202        SettingsPage {
2203            title: "AI",
2204            expanded: false,
2205            items: vec![
2206                SettingsPageItem::SectionHeader("General"),
2207                SettingsPageItem::SettingItem(SettingItem {
2208                    title: "Disable AI",
2209                    description: "Whether to disable all AI features in Zed",
2210                    field: Box::new(SettingField {
2211                        pick: |settings_content| &settings_content.disable_ai,
2212                        pick_mut: |settings_content| &mut settings_content.disable_ai,
2213                    }),
2214                    metadata: None,
2215                }),
2216            ],
2217        },
2218    ]
2219}
2220
2221// Derive Macro, on the new ProjectSettings struct
2222
2223fn project_settings_data() -> Vec<SettingsPage> {
2224    vec![
2225        SettingsPage {
2226            title: "Project",
2227            expanded: false,
2228            items: vec![
2229                SettingsPageItem::SectionHeader("Worktree Settings Content"),
2230                SettingsPageItem::SettingItem(SettingItem {
2231                    title: "Project Name",
2232                    description: "The displayed name of this project. If not set, the root directory name",
2233                    field: Box::new(SettingField {
2234                        pick: |settings_content| &settings_content.project.worktree.project_name,
2235                        pick_mut: |settings_content| {
2236                            &mut settings_content.project.worktree.project_name
2237                        },
2238                    }),
2239                    metadata: Some(Box::new(SettingsFieldMetadata {
2240                        placeholder: Some("A new name"),
2241                    })),
2242                }),
2243            ],
2244        },
2245        SettingsPage {
2246            title: "Appearance & Behavior",
2247            expanded: false,
2248            items: vec![
2249                SettingsPageItem::SectionHeader("Guides"),
2250                SettingsPageItem::SettingItem(SettingItem {
2251                    title: "Show Wrap Guides",
2252                    description: "Whether to show wrap guides (vertical rulers)",
2253                    field: Box::new(SettingField {
2254                        pick: |settings_content| {
2255                            &settings_content
2256                                .project
2257                                .all_languages
2258                                .defaults
2259                                .show_wrap_guides
2260                        },
2261                        pick_mut: |settings_content| {
2262                            &mut settings_content
2263                                .project
2264                                .all_languages
2265                                .defaults
2266                                .show_wrap_guides
2267                        },
2268                    }),
2269                    metadata: None,
2270                }),
2271                // todo(settings_ui): This needs a custom component
2272                // SettingsPageItem::SettingItem(SettingItem {
2273                //     title: "Wrap Guides",
2274                //     description: "Character counts at which to show wrap guides",
2275                //     field: Box::new(SettingField {
2276                //         pick: |settings_content| {
2277                //             &settings_content
2278                //                 .project
2279                //                 .all_languages
2280                //                 .defaults
2281                //                 .wrap_guides
2282                //         },
2283                //         pick_mut: |settings_content| {
2284                //             &mut settings_content
2285                //                 .project
2286                //                 .all_languages
2287                //                 .defaults
2288                //                 .wrap_guides
2289                //         },
2290                //     }),
2291                //     metadata: None,
2292                // }),
2293                SettingsPageItem::SectionHeader("Whitespace"),
2294                SettingsPageItem::SettingItem(SettingItem {
2295                    title: "Show Whitespace",
2296                    description: "Whether to show tabs and spaces",
2297                    field: Box::new(SettingField {
2298                        pick: |settings_content| {
2299                            &settings_content
2300                                .project
2301                                .all_languages
2302                                .defaults
2303                                .show_whitespaces
2304                        },
2305                        pick_mut: |settings_content| {
2306                            &mut settings_content
2307                                .project
2308                                .all_languages
2309                                .defaults
2310                                .show_whitespaces
2311                        },
2312                    }),
2313                    metadata: None,
2314                }),
2315            ],
2316        },
2317        SettingsPage {
2318            title: "Editing",
2319            expanded: false,
2320            items: vec![
2321                SettingsPageItem::SectionHeader("Indentation"),
2322                // todo(settings_ui): Needs numeric stepper
2323                // SettingsPageItem::SettingItem(SettingItem {
2324                //     title: "Tab Size",
2325                //     description: "How many columns a tab should occupy",
2326                //     field: Box::new(SettingField {
2327                //         pick: |settings_content| &settings_content.project.all_languages.defaults.tab_size,
2328                //         pick_mut: |settings_content| &mut settings_content.project.all_languages.defaults.tab_size,
2329                //     }),
2330                //     metadata: None,
2331                // }),
2332                SettingsPageItem::SettingItem(SettingItem {
2333                    title: "Hard Tabs",
2334                    description: "Whether to indent lines using tab characters, as opposed to multiple spaces",
2335                    field: Box::new(SettingField {
2336                        pick: |settings_content| {
2337                            &settings_content.project.all_languages.defaults.hard_tabs
2338                        },
2339                        pick_mut: |settings_content| {
2340                            &mut settings_content.project.all_languages.defaults.hard_tabs
2341                        },
2342                    }),
2343                    metadata: None,
2344                }),
2345                SettingsPageItem::SettingItem(SettingItem {
2346                    title: "Auto Indent",
2347                    description: "Whether indentation should be adjusted based on the context whilst typing",
2348                    field: Box::new(SettingField {
2349                        pick: |settings_content| {
2350                            &settings_content.project.all_languages.defaults.auto_indent
2351                        },
2352                        pick_mut: |settings_content| {
2353                            &mut settings_content.project.all_languages.defaults.auto_indent
2354                        },
2355                    }),
2356                    metadata: None,
2357                }),
2358                SettingsPageItem::SettingItem(SettingItem {
2359                    title: "Auto Indent On Paste",
2360                    description: "Whether indentation of pasted content should be adjusted based on the context",
2361                    field: Box::new(SettingField {
2362                        pick: |settings_content| {
2363                            &settings_content
2364                                .project
2365                                .all_languages
2366                                .defaults
2367                                .auto_indent_on_paste
2368                        },
2369                        pick_mut: |settings_content| {
2370                            &mut settings_content
2371                                .project
2372                                .all_languages
2373                                .defaults
2374                                .auto_indent_on_paste
2375                        },
2376                    }),
2377                    metadata: None,
2378                }),
2379                SettingsPageItem::SectionHeader("Wrapping"),
2380                // todo(settings_ui): Needs numeric stepper
2381                // SettingsPageItem::SettingItem(SettingItem {
2382                //     title: "Preferred Line Length",
2383                //     description: "The column at which to soft-wrap lines, for buffers where soft-wrap is enabled",
2384                //     field: Box::new(SettingField {
2385                //         pick: |settings_content| &settings_content.project.all_languages.defaults.preferred_line_length,
2386                //         pick_mut: |settings_content| &mut settings_content.project.all_languages.defaults.preferred_line_length,
2387                //     }),
2388                //     metadata: None,
2389                // }),
2390                SettingsPageItem::SettingItem(SettingItem {
2391                    title: "Soft Wrap",
2392                    description: "How to soft-wrap long lines of text",
2393                    field: Box::new(SettingField {
2394                        pick: |settings_content| {
2395                            &settings_content.project.all_languages.defaults.soft_wrap
2396                        },
2397                        pick_mut: |settings_content| {
2398                            &mut settings_content.project.all_languages.defaults.soft_wrap
2399                        },
2400                    }),
2401                    metadata: None,
2402                }),
2403                SettingsPageItem::SectionHeader("Auto Actions"),
2404                SettingsPageItem::SettingItem(SettingItem {
2405                    title: "Use Autoclose",
2406                    description: "Whether to automatically type closing characters for you",
2407                    field: Box::new(SettingField {
2408                        pick: |settings_content| {
2409                            &settings_content
2410                                .project
2411                                .all_languages
2412                                .defaults
2413                                .use_autoclose
2414                        },
2415                        pick_mut: |settings_content| {
2416                            &mut settings_content
2417                                .project
2418                                .all_languages
2419                                .defaults
2420                                .use_autoclose
2421                        },
2422                    }),
2423                    metadata: None,
2424                }),
2425                SettingsPageItem::SettingItem(SettingItem {
2426                    title: "Use Auto Surround",
2427                    description: "Whether to automatically surround text with characters for you",
2428                    field: Box::new(SettingField {
2429                        pick: |settings_content| {
2430                            &settings_content
2431                                .project
2432                                .all_languages
2433                                .defaults
2434                                .use_auto_surround
2435                        },
2436                        pick_mut: |settings_content| {
2437                            &mut settings_content
2438                                .project
2439                                .all_languages
2440                                .defaults
2441                                .use_auto_surround
2442                        },
2443                    }),
2444                    metadata: None,
2445                }),
2446                SettingsPageItem::SettingItem(SettingItem {
2447                    title: "Use On Type Format",
2448                    description: "Whether to use additional LSP queries to format the code after every trigger symbol input",
2449                    field: Box::new(SettingField {
2450                        pick: |settings_content| {
2451                            &settings_content
2452                                .project
2453                                .all_languages
2454                                .defaults
2455                                .use_on_type_format
2456                        },
2457                        pick_mut: |settings_content| {
2458                            &mut settings_content
2459                                .project
2460                                .all_languages
2461                                .defaults
2462                                .use_on_type_format
2463                        },
2464                    }),
2465                    metadata: None,
2466                }),
2467                SettingsPageItem::SettingItem(SettingItem {
2468                    title: "Always Treat Brackets As Autoclosed",
2469                    description: "Controls how the editor handles the autoclosed characters",
2470                    field: Box::new(SettingField {
2471                        pick: |settings_content| {
2472                            &settings_content
2473                                .project
2474                                .all_languages
2475                                .defaults
2476                                .always_treat_brackets_as_autoclosed
2477                        },
2478                        pick_mut: |settings_content| {
2479                            &mut settings_content
2480                                .project
2481                                .all_languages
2482                                .defaults
2483                                .always_treat_brackets_as_autoclosed
2484                        },
2485                    }),
2486                    metadata: None,
2487                }),
2488                SettingsPageItem::SectionHeader("Formatting"),
2489                SettingsPageItem::SettingItem(SettingItem {
2490                    title: "Remove Trailing Whitespace On Save",
2491                    description: "Whether or not to remove any trailing whitespace from lines of a buffer before saving it",
2492                    field: Box::new(SettingField {
2493                        pick: |settings_content| {
2494                            &settings_content
2495                                .project
2496                                .all_languages
2497                                .defaults
2498                                .remove_trailing_whitespace_on_save
2499                        },
2500                        pick_mut: |settings_content| {
2501                            &mut settings_content
2502                                .project
2503                                .all_languages
2504                                .defaults
2505                                .remove_trailing_whitespace_on_save
2506                        },
2507                    }),
2508                    metadata: None,
2509                }),
2510                SettingsPageItem::SettingItem(SettingItem {
2511                    title: "Ensure Final Newline On Save",
2512                    description: "Whether or not to ensure there's a single newline at the end of a buffer when saving it",
2513                    field: Box::new(SettingField {
2514                        pick: |settings_content| {
2515                            &settings_content
2516                                .project
2517                                .all_languages
2518                                .defaults
2519                                .ensure_final_newline_on_save
2520                        },
2521                        pick_mut: |settings_content| {
2522                            &mut settings_content
2523                                .project
2524                                .all_languages
2525                                .defaults
2526                                .ensure_final_newline_on_save
2527                        },
2528                    }),
2529                    metadata: None,
2530                }),
2531                SettingsPageItem::SettingItem(SettingItem {
2532                    title: "Extend Comment On Newline",
2533                    description: "Whether to start a new line with a comment when a previous line is a comment as well",
2534                    field: Box::new(SettingField {
2535                        pick: |settings_content| {
2536                            &settings_content
2537                                .project
2538                                .all_languages
2539                                .defaults
2540                                .extend_comment_on_newline
2541                        },
2542                        pick_mut: |settings_content| {
2543                            &mut settings_content
2544                                .project
2545                                .all_languages
2546                                .defaults
2547                                .extend_comment_on_newline
2548                        },
2549                    }),
2550                    metadata: None,
2551                }),
2552                SettingsPageItem::SectionHeader("Completions"),
2553                SettingsPageItem::SettingItem(SettingItem {
2554                    title: "Show Completions On Input",
2555                    description: "Whether to pop the completions menu while typing in an editor without explicitly requesting it",
2556                    field: Box::new(SettingField {
2557                        pick: |settings_content| {
2558                            &settings_content
2559                                .project
2560                                .all_languages
2561                                .defaults
2562                                .show_completions_on_input
2563                        },
2564                        pick_mut: |settings_content| {
2565                            &mut settings_content
2566                                .project
2567                                .all_languages
2568                                .defaults
2569                                .show_completions_on_input
2570                        },
2571                    }),
2572                    metadata: None,
2573                }),
2574                SettingsPageItem::SettingItem(SettingItem {
2575                    title: "Show Completion Documentation",
2576                    description: "Whether to display inline and alongside documentation for items in the completions menu",
2577                    field: Box::new(SettingField {
2578                        pick: |settings_content| {
2579                            &settings_content
2580                                .project
2581                                .all_languages
2582                                .defaults
2583                                .show_completion_documentation
2584                        },
2585                        pick_mut: |settings_content| {
2586                            &mut settings_content
2587                                .project
2588                                .all_languages
2589                                .defaults
2590                                .show_completion_documentation
2591                        },
2592                    }),
2593                    metadata: None,
2594                }),
2595            ],
2596        },
2597    ]
2598}
2599
2600pub struct SettingsUiFeatureFlag;
2601
2602impl FeatureFlag for SettingsUiFeatureFlag {
2603    const NAME: &'static str = "settings-ui";
2604}
2605
2606actions!(
2607    zed,
2608    [
2609        /// Opens Settings Editor.
2610        OpenSettingsEditor
2611    ]
2612);
2613
2614pub fn init(cx: &mut App) {
2615    init_renderers(cx);
2616
2617    cx.observe_new(|workspace: &mut workspace::Workspace, _, _| {
2618        workspace.register_action_renderer(|div, _, _, cx| {
2619            let settings_ui_actions = [std::any::TypeId::of::<OpenSettingsEditor>()];
2620            let has_flag = cx.has_flag::<SettingsUiFeatureFlag>();
2621            command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _| {
2622                if has_flag {
2623                    filter.show_action_types(&settings_ui_actions);
2624                } else {
2625                    filter.hide_action_types(&settings_ui_actions);
2626                }
2627            });
2628            if has_flag {
2629                div.on_action(cx.listener(|_, _: &OpenSettingsEditor, _, cx| {
2630                    open_settings_editor(cx).ok();
2631                }))
2632            } else {
2633                div
2634            }
2635        });
2636    })
2637    .detach();
2638}
2639
2640fn init_renderers(cx: &mut App) {
2641    // fn (field: SettingsField, current_file: SettingsFile, cx) -> (currently_set_in: SettingsFile, overridden_in: Vec<SettingsFile>)
2642    cx.default_global::<SettingFieldRenderer>()
2643        .add_renderer::<bool>(|settings_field, file, _, _, cx| {
2644            render_toggle_button(*settings_field, file, cx).into_any_element()
2645        })
2646        .add_renderer::<String>(|settings_field, file, metadata, _, cx| {
2647            render_text_field(settings_field.clone(), file, metadata, cx)
2648        })
2649        .add_renderer::<SaturatingBool>(|settings_field, file, _, _, cx| {
2650            render_toggle_button(*settings_field, file, cx)
2651        })
2652        .add_renderer::<CursorShape>(|settings_field, file, _, window, cx| {
2653            render_dropdown(*settings_field, file, window, cx)
2654        })
2655        .add_renderer::<RestoreOnStartupBehavior>(|settings_field, file, _, window, cx| {
2656            render_dropdown(*settings_field, file, window, cx)
2657        })
2658        .add_renderer::<BottomDockLayout>(|settings_field, file, _, window, cx| {
2659            render_dropdown(*settings_field, file, window, cx)
2660        })
2661        .add_renderer::<OnLastWindowClosed>(|settings_field, file, _, window, cx| {
2662            render_dropdown(*settings_field, file, window, cx)
2663        })
2664        .add_renderer::<CloseWindowWhenNoItems>(|settings_field, file, _, window, cx| {
2665            render_dropdown(*settings_field, file, window, cx)
2666        })
2667        .add_renderer::<settings::FontFamilyName>(|settings_field, file, metadata, _, cx| {
2668            // todo(settings_ui): We need to pass in a validator for this to ensure that users that type in invalid font names
2669            render_text_field(settings_field.clone(), file, metadata, cx)
2670        })
2671        .add_renderer::<settings::BufferLineHeight>(|settings_field, file, _, window, cx| {
2672            // todo(settings_ui): Do we want to expose the custom variant of buffer line height?
2673            // right now there's a manual impl of strum::VariantArray
2674            render_dropdown(*settings_field, file, window, cx)
2675        })
2676        .add_renderer::<settings::BaseKeymapContent>(|settings_field, file, _, window, cx| {
2677            render_dropdown(*settings_field, file, window, cx)
2678        })
2679        .add_renderer::<settings::MultiCursorModifier>(|settings_field, file, _, window, cx| {
2680            render_dropdown(*settings_field, file, window, cx)
2681        })
2682        .add_renderer::<settings::HideMouseMode>(|settings_field, file, _, window, cx| {
2683            render_dropdown(*settings_field, file, window, cx)
2684        })
2685        .add_renderer::<settings::CurrentLineHighlight>(|settings_field, file, _, window, cx| {
2686            render_dropdown(*settings_field, file, window, cx)
2687        })
2688        .add_renderer::<settings::ShowWhitespaceSetting>(|settings_field, file, _, window, cx| {
2689            render_dropdown(*settings_field, file, window, cx)
2690        })
2691        .add_renderer::<settings::SoftWrap>(|settings_field, file, _, window, cx| {
2692            render_dropdown(*settings_field, file, window, cx)
2693        })
2694        .add_renderer::<settings::ScrollBeyondLastLine>(|settings_field, file, _, window, cx| {
2695            render_dropdown(*settings_field, file, window, cx)
2696        })
2697        .add_renderer::<settings::SnippetSortOrder>(|settings_field, file, _, window, cx| {
2698            render_dropdown(*settings_field, file, window, cx)
2699        })
2700        .add_renderer::<settings::ClosePosition>(|settings_field, file, _, window, cx| {
2701            render_dropdown(*settings_field, file, window, cx)
2702        })
2703        .add_renderer::<settings::DockSide>(|settings_field, file, _, window, cx| {
2704            render_dropdown(*settings_field, file, window, cx)
2705        })
2706        .add_renderer::<settings::TerminalDockPosition>(|settings_field, file, _, window, cx| {
2707            render_dropdown(*settings_field, file, window, cx)
2708        })
2709        .add_renderer::<settings::GitGutterSetting>(|settings_field, file, _, window, cx| {
2710            render_dropdown(*settings_field, file, window, cx)
2711        })
2712        .add_renderer::<settings::GitHunkStyleSetting>(|settings_field, file, _, window, cx| {
2713            render_dropdown(*settings_field, file, window, cx)
2714        })
2715        .add_renderer::<settings::DiagnosticSeverityContent>(
2716            |settings_field, file, _, window, cx| {
2717                render_dropdown(*settings_field, file, window, cx)
2718            },
2719        )
2720        .add_renderer::<settings::SeedQuerySetting>(|settings_field, file, _, window, cx| {
2721            render_dropdown(*settings_field, file, window, cx)
2722        })
2723        .add_renderer::<settings::DoubleClickInMultibuffer>(
2724            |settings_field, file, _, window, cx| {
2725                render_dropdown(*settings_field, file, window, cx)
2726            },
2727        )
2728        .add_renderer::<settings::GoToDefinitionFallback>(|settings_field, file, _, window, cx| {
2729            render_dropdown(*settings_field, file, window, cx)
2730        })
2731        .add_renderer::<settings::ActivateOnClose>(|settings_field, file, _, window, cx| {
2732            render_dropdown(*settings_field, file, window, cx)
2733        })
2734        .add_renderer::<settings::ShowDiagnostics>(|settings_field, file, _, window, cx| {
2735            render_dropdown(*settings_field, file, window, cx)
2736        })
2737        .add_renderer::<settings::ShowCloseButton>(|settings_field, file, _, window, cx| {
2738            render_dropdown(*settings_field, file, window, cx)
2739        });
2740
2741    // todo(settings_ui): Figure out how we want to handle discriminant unions
2742    // .add_renderer::<ThemeSelection>(|settings_field, file, _, window, cx| {
2743    //     render_dropdown(*settings_field, file, window, cx)
2744    // });
2745}
2746
2747pub fn open_settings_editor(cx: &mut App) -> anyhow::Result<WindowHandle<SettingsWindow>> {
2748    cx.open_window(
2749        WindowOptions {
2750            titlebar: Some(TitlebarOptions {
2751                title: Some("Settings Window".into()),
2752                appears_transparent: true,
2753                traffic_light_position: Some(point(px(12.0), px(12.0))),
2754            }),
2755            focus: true,
2756            show: true,
2757            kind: gpui::WindowKind::Normal,
2758            window_background: cx.theme().window_background_appearance(),
2759            window_min_size: Some(size(px(800.), px(600.))), // 4:3 Aspect Ratio
2760            ..Default::default()
2761        },
2762        |window, cx| cx.new(|cx| SettingsWindow::new(window, cx)),
2763    )
2764}
2765
2766pub struct SettingsWindow {
2767    files: Vec<SettingsUiFile>,
2768    current_file: SettingsUiFile,
2769    pages: Vec<SettingsPage>,
2770    search_bar: Entity<Editor>,
2771    search_task: Option<Task<()>>,
2772    navbar_entry: usize, // Index into pages - should probably be (usize, Option<usize>) for section + page
2773    navbar_entries: Vec<NavBarEntry>,
2774    list_handle: UniformListScrollHandle,
2775    search_matches: Vec<Vec<bool>>,
2776    /// The current sub page path that is selected.
2777    /// If this is empty the selected page is rendered,
2778    /// otherwise the last sub page gets rendered.
2779    sub_page_stack: Vec<SubPage>,
2780}
2781
2782struct SubPage {
2783    link: SubPageLink,
2784    section_header: &'static str,
2785}
2786
2787#[derive(PartialEq, Debug)]
2788struct NavBarEntry {
2789    title: &'static str,
2790    is_root: bool,
2791    page_index: usize,
2792}
2793
2794struct SettingsPage {
2795    title: &'static str,
2796    expanded: bool,
2797    items: Vec<SettingsPageItem>,
2798}
2799
2800#[derive(PartialEq)]
2801enum SettingsPageItem {
2802    SectionHeader(&'static str),
2803    SettingItem(SettingItem),
2804    SubPageLink(SubPageLink),
2805}
2806
2807impl std::fmt::Debug for SettingsPageItem {
2808    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2809        match self {
2810            SettingsPageItem::SectionHeader(header) => write!(f, "SectionHeader({})", header),
2811            SettingsPageItem::SettingItem(setting_item) => {
2812                write!(f, "SettingItem({})", setting_item.title)
2813            }
2814            SettingsPageItem::SubPageLink(sub_page_link) => {
2815                write!(f, "SubPageLink({})", sub_page_link.title)
2816            }
2817        }
2818    }
2819}
2820
2821impl SettingsPageItem {
2822    fn render(
2823        &self,
2824        file: SettingsUiFile,
2825        section_header: &'static str,
2826        is_last: bool,
2827        window: &mut Window,
2828        cx: &mut Context<SettingsWindow>,
2829    ) -> AnyElement {
2830        match self {
2831            SettingsPageItem::SectionHeader(header) => v_flex()
2832                .w_full()
2833                .gap_1()
2834                .child(
2835                    Label::new(SharedString::new_static(header))
2836                        .size(LabelSize::XSmall)
2837                        .color(Color::Muted)
2838                        .buffer_font(cx),
2839                )
2840                .child(Divider::horizontal().color(ui::DividerColor::BorderVariant))
2841                .into_any_element(),
2842            SettingsPageItem::SettingItem(setting_item) => {
2843                let renderer = cx.default_global::<SettingFieldRenderer>().clone();
2844                let file_set_in =
2845                    SettingsUiFile::from_settings(setting_item.field.file_set_in(file.clone(), cx));
2846
2847                h_flex()
2848                    .id(setting_item.title)
2849                    .w_full()
2850                    .gap_2()
2851                    .flex_wrap()
2852                    .justify_between()
2853                    .when(!is_last, |this| {
2854                        this.pb_4()
2855                            .border_b_1()
2856                            .border_color(cx.theme().colors().border_variant)
2857                    })
2858                    .child(
2859                        v_flex()
2860                            .max_w_1_2()
2861                            .flex_shrink()
2862                            .child(
2863                                h_flex()
2864                                    .w_full()
2865                                    .gap_4()
2866                                    .child(
2867                                        Label::new(SharedString::new_static(setting_item.title))
2868                                            .size(LabelSize::Default),
2869                                    )
2870                                    .when_some(
2871                                        file_set_in.filter(|file_set_in| file_set_in != &file),
2872                                        |elem, file_set_in| {
2873                                            elem.child(
2874                                                Label::new(format!(
2875                                                    "set in {}",
2876                                                    file_set_in.name()
2877                                                ))
2878                                                .color(Color::Muted),
2879                                            )
2880                                        },
2881                                    ),
2882                            )
2883                            .child(
2884                                Label::new(SharedString::new_static(setting_item.description))
2885                                    .size(LabelSize::Small)
2886                                    .color(Color::Muted),
2887                            ),
2888                    )
2889                    .child(renderer.render(
2890                        setting_item.field.as_ref(),
2891                        file,
2892                        setting_item.metadata.as_deref(),
2893                        window,
2894                        cx,
2895                    ))
2896                    .into_any_element()
2897            }
2898            SettingsPageItem::SubPageLink(sub_page_link) => h_flex()
2899                .id(sub_page_link.title)
2900                .w_full()
2901                .gap_2()
2902                .flex_wrap()
2903                .justify_between()
2904                .when(!is_last, |this| {
2905                    this.pb_4()
2906                        .border_b_1()
2907                        .border_color(cx.theme().colors().border_variant)
2908                })
2909                .child(
2910                    v_flex().max_w_1_2().flex_shrink().child(
2911                        Label::new(SharedString::new_static(sub_page_link.title))
2912                            .size(LabelSize::Default),
2913                    ),
2914                )
2915                .child(
2916                    Button::new(("sub-page".into(), sub_page_link.title), "Configure")
2917                        .icon(Some(IconName::ChevronRight))
2918                        .icon_position(Some(IconPosition::End))
2919                        .style(ButtonStyle::Outlined),
2920                )
2921                .on_click({
2922                    let sub_page_link = sub_page_link.clone();
2923                    cx.listener(move |this, _, _, cx| {
2924                        this.push_sub_page(sub_page_link.clone(), section_header, cx)
2925                    })
2926                })
2927                .into_any_element(),
2928        }
2929    }
2930}
2931
2932struct SettingItem {
2933    title: &'static str,
2934    description: &'static str,
2935    field: Box<dyn AnySettingField>,
2936    metadata: Option<Box<SettingsFieldMetadata>>,
2937}
2938
2939impl PartialEq for SettingItem {
2940    fn eq(&self, other: &Self) -> bool {
2941        self.title == other.title
2942            && self.description == other.description
2943            && (match (&self.metadata, &other.metadata) {
2944                (None, None) => true,
2945                (Some(m1), Some(m2)) => m1.placeholder == m2.placeholder,
2946                _ => false,
2947            })
2948    }
2949}
2950
2951#[derive(Clone)]
2952struct SubPageLink {
2953    title: &'static str,
2954    render: Rc<dyn Fn(&mut SettingsWindow, &mut Window, &mut App) -> AnyElement>,
2955}
2956
2957impl PartialEq for SubPageLink {
2958    fn eq(&self, other: &Self) -> bool {
2959        self.title == other.title
2960    }
2961}
2962
2963#[allow(unused)]
2964#[derive(Clone, PartialEq)]
2965enum SettingsUiFile {
2966    User,                              // Uses all settings.
2967    Local((WorktreeId, Arc<RelPath>)), // Has a special name, and special set of settings
2968    Server(&'static str),              // Uses a special name, and the user settings
2969}
2970
2971impl SettingsUiFile {
2972    fn pages(&self) -> Vec<SettingsPage> {
2973        match self {
2974            SettingsUiFile::User => user_settings_data(),
2975            SettingsUiFile::Local(_) => project_settings_data(),
2976            SettingsUiFile::Server(_) => user_settings_data(),
2977        }
2978    }
2979
2980    fn name(&self) -> SharedString {
2981        match self {
2982            SettingsUiFile::User => SharedString::new_static("User"),
2983            // TODO is PathStyle::local() ever not appropriate?
2984            SettingsUiFile::Local((_, path)) => {
2985                format!("Local ({})", path.display(PathStyle::local())).into()
2986            }
2987            SettingsUiFile::Server(file) => format!("Server ({})", file).into(),
2988        }
2989    }
2990
2991    fn from_settings(file: settings::SettingsFile) -> Option<Self> {
2992        Some(match file {
2993            settings::SettingsFile::User => SettingsUiFile::User,
2994            settings::SettingsFile::Local(location) => SettingsUiFile::Local(location),
2995            settings::SettingsFile::Server => SettingsUiFile::Server("todo: server name"),
2996            settings::SettingsFile::Default => return None,
2997        })
2998    }
2999
3000    fn to_settings(&self) -> settings::SettingsFile {
3001        match self {
3002            SettingsUiFile::User => settings::SettingsFile::User,
3003            SettingsUiFile::Local(location) => settings::SettingsFile::Local(location.clone()),
3004            SettingsUiFile::Server(_) => settings::SettingsFile::Server,
3005        }
3006    }
3007}
3008
3009impl SettingsWindow {
3010    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
3011        let current_file = SettingsUiFile::User;
3012        let search_bar = cx.new(|cx| {
3013            let mut editor = Editor::single_line(window, cx);
3014            editor.set_placeholder_text("Search settings…", window, cx);
3015            editor
3016        });
3017
3018        cx.subscribe(&search_bar, |this, _, event: &EditorEvent, cx| {
3019            let EditorEvent::Edited { transaction_id: _ } = event else {
3020                return;
3021            };
3022
3023            this.update_matches(cx);
3024        })
3025        .detach();
3026
3027        cx.observe_global_in::<SettingsStore>(window, move |this, _, cx| {
3028            this.fetch_files(cx);
3029            cx.notify();
3030        })
3031        .detach();
3032
3033        let mut this = Self {
3034            files: vec![],
3035            current_file: current_file,
3036            pages: vec![],
3037            navbar_entries: vec![],
3038            navbar_entry: 0,
3039            list_handle: UniformListScrollHandle::default(),
3040            search_bar,
3041            search_task: None,
3042            search_matches: vec![],
3043            sub_page_stack: vec![],
3044        };
3045
3046        this.fetch_files(cx);
3047        this.build_ui(cx);
3048
3049        this
3050    }
3051
3052    fn toggle_navbar_entry(&mut self, ix: usize) {
3053        // We can only toggle root entries
3054        if !self.navbar_entries[ix].is_root {
3055            return;
3056        }
3057
3058        let toggle_page_index = self.page_index_from_navbar_index(ix);
3059        let selected_page_index = self.page_index_from_navbar_index(self.navbar_entry);
3060
3061        let expanded = &mut self.page_for_navbar_index(ix).expanded;
3062        *expanded = !*expanded;
3063        let expanded = *expanded;
3064        // if currently selected page is a child of the parent page we are folding,
3065        // set the current page to the parent page
3066        if selected_page_index == toggle_page_index {
3067            self.navbar_entry = ix;
3068        } else if selected_page_index > toggle_page_index {
3069            let sub_items_count = self.pages[toggle_page_index]
3070                .items
3071                .iter()
3072                .filter(|item| matches!(item, SettingsPageItem::SectionHeader(_)))
3073                .count();
3074            if expanded {
3075                self.navbar_entry += sub_items_count;
3076            } else {
3077                self.navbar_entry -= sub_items_count;
3078            }
3079        }
3080
3081        self.build_navbar();
3082    }
3083
3084    fn build_navbar(&mut self) {
3085        let mut navbar_entries = Vec::with_capacity(self.navbar_entries.len());
3086        for (page_index, page) in self.pages.iter().enumerate() {
3087            if !self.search_matches[page_index]
3088                .iter()
3089                .any(|is_match| *is_match)
3090                && !self.search_matches[page_index].is_empty()
3091            {
3092                continue;
3093            }
3094            navbar_entries.push(NavBarEntry {
3095                title: page.title,
3096                is_root: true,
3097                page_index,
3098            });
3099            if !page.expanded {
3100                continue;
3101            }
3102
3103            for (item_index, item) in page.items.iter().enumerate() {
3104                let SettingsPageItem::SectionHeader(title) = item else {
3105                    continue;
3106                };
3107                if !self.search_matches[page_index][item_index] {
3108                    continue;
3109                }
3110
3111                navbar_entries.push(NavBarEntry {
3112                    title,
3113                    is_root: false,
3114                    page_index,
3115                });
3116            }
3117        }
3118        self.navbar_entries = navbar_entries;
3119    }
3120
3121    fn update_matches(&mut self, cx: &mut Context<SettingsWindow>) {
3122        self.search_task.take();
3123        let query = self.search_bar.read(cx).text(cx);
3124        if query.is_empty() {
3125            for page in &mut self.search_matches {
3126                page.fill(true);
3127            }
3128            self.build_navbar();
3129            cx.notify();
3130            return;
3131        }
3132
3133        struct ItemKey {
3134            page_index: usize,
3135            header_index: usize,
3136            item_index: usize,
3137        }
3138        let mut key_lut: Vec<ItemKey> = vec![];
3139        let mut candidates = Vec::default();
3140
3141        for (page_index, page) in self.pages.iter().enumerate() {
3142            let mut header_index = 0;
3143            for (item_index, item) in page.items.iter().enumerate() {
3144                let key_index = key_lut.len();
3145                match item {
3146                    SettingsPageItem::SettingItem(item) => {
3147                        candidates.push(StringMatchCandidate::new(key_index, item.title));
3148                        candidates.push(StringMatchCandidate::new(key_index, item.description));
3149                    }
3150                    SettingsPageItem::SectionHeader(header) => {
3151                        candidates.push(StringMatchCandidate::new(key_index, header));
3152                        header_index = item_index;
3153                    }
3154                    SettingsPageItem::SubPageLink(sub_page_link) => {
3155                        candidates.push(StringMatchCandidate::new(key_index, sub_page_link.title));
3156                    }
3157                }
3158                key_lut.push(ItemKey {
3159                    page_index,
3160                    header_index,
3161                    item_index,
3162                });
3163            }
3164        }
3165        let atomic_bool = AtomicBool::new(false);
3166
3167        self.search_task = Some(cx.spawn(async move |this, cx| {
3168            let string_matches = fuzzy::match_strings(
3169                candidates.as_slice(),
3170                &query,
3171                false,
3172                false,
3173                candidates.len(),
3174                &atomic_bool,
3175                cx.background_executor().clone(),
3176            );
3177            let string_matches = string_matches.await;
3178
3179            this.update(cx, |this, cx| {
3180                for page in &mut this.search_matches {
3181                    page.fill(false);
3182                }
3183
3184                for string_match in string_matches {
3185                    let ItemKey {
3186                        page_index,
3187                        header_index,
3188                        item_index,
3189                    } = key_lut[string_match.candidate_id];
3190                    let page = &mut this.search_matches[page_index];
3191                    page[header_index] = true;
3192                    page[item_index] = true;
3193                }
3194                this.build_navbar();
3195                this.navbar_entry = 0;
3196                cx.notify();
3197            })
3198            .ok();
3199        }));
3200    }
3201
3202    fn build_ui(&mut self, cx: &mut Context<SettingsWindow>) {
3203        self.pages = self.current_file.pages();
3204        self.search_matches = self
3205            .pages
3206            .iter()
3207            .map(|page| vec![true; page.items.len()])
3208            .collect::<Vec<_>>();
3209        self.build_navbar();
3210
3211        if !self.search_bar.read(cx).is_empty(cx) {
3212            self.update_matches(cx);
3213        }
3214
3215        cx.notify();
3216    }
3217
3218    fn fetch_files(&mut self, cx: &mut Context<SettingsWindow>) {
3219        let settings_store = cx.global::<SettingsStore>();
3220        let mut ui_files = vec![];
3221        let all_files = settings_store.get_all_files();
3222        for file in all_files {
3223            let Some(settings_ui_file) = SettingsUiFile::from_settings(file) else {
3224                continue;
3225            };
3226            ui_files.push(settings_ui_file);
3227        }
3228        ui_files.reverse();
3229        self.files = ui_files;
3230        if !self.files.contains(&self.current_file) {
3231            self.change_file(0, cx);
3232        }
3233    }
3234
3235    fn change_file(&mut self, ix: usize, cx: &mut Context<SettingsWindow>) {
3236        if ix >= self.files.len() {
3237            self.current_file = SettingsUiFile::User;
3238            return;
3239        }
3240        if self.files[ix] == self.current_file {
3241            return;
3242        }
3243        self.current_file = self.files[ix].clone();
3244        self.navbar_entry = 0;
3245        self.build_ui(cx);
3246    }
3247
3248    fn render_files(&self, _window: &mut Window, cx: &mut Context<SettingsWindow>) -> Div {
3249        h_flex()
3250            .gap_1()
3251            .children(self.files.iter().enumerate().map(|(ix, file)| {
3252                Button::new(ix, file.name())
3253                    .on_click(cx.listener(move |this, _, _window, cx| this.change_file(ix, cx)))
3254            }))
3255    }
3256
3257    fn render_search(&self, _window: &mut Window, cx: &mut App) -> Div {
3258        h_flex()
3259            .pt_1()
3260            .px_1p5()
3261            .gap_1p5()
3262            .rounded_sm()
3263            .bg(cx.theme().colors().editor_background)
3264            .border_1()
3265            .border_color(cx.theme().colors().border)
3266            .child(Icon::new(IconName::MagnifyingGlass).color(Color::Muted))
3267            .child(self.search_bar.clone())
3268    }
3269
3270    fn render_nav(&self, window: &mut Window, cx: &mut Context<SettingsWindow>) -> Div {
3271        v_flex()
3272            .w_64()
3273            .p_2p5()
3274            .pt_10()
3275            .gap_3()
3276            .flex_none()
3277            .border_r_1()
3278            .border_color(cx.theme().colors().border)
3279            .bg(cx.theme().colors().panel_background)
3280            .child(self.render_search(window, cx).pb_1())
3281            .child(
3282                uniform_list(
3283                    "settings-ui-nav-bar",
3284                    self.navbar_entries.len(),
3285                    cx.processor(|this, range: Range<usize>, _, cx| {
3286                        range
3287                            .into_iter()
3288                            .map(|ix| {
3289                                let entry = &this.navbar_entries[ix];
3290
3291                                TreeViewItem::new(("settings-ui-navbar-entry", ix), entry.title)
3292                                    .root_item(entry.is_root)
3293                                    .toggle_state(this.is_navbar_entry_selected(ix))
3294                                    .when(entry.is_root, |item| {
3295                                        item.toggle(
3296                                            this.pages[this.page_index_from_navbar_index(ix)]
3297                                                .expanded,
3298                                        )
3299                                        .on_toggle(
3300                                            cx.listener(move |this, _, _, cx| {
3301                                                this.toggle_navbar_entry(ix);
3302                                                cx.notify();
3303                                            }),
3304                                        )
3305                                    })
3306                                    .on_click(cx.listener(move |this, _, _, cx| {
3307                                        this.navbar_entry = ix;
3308                                        cx.notify();
3309                                    }))
3310                                    .into_any_element()
3311                            })
3312                            .collect()
3313                    }),
3314                )
3315                .track_scroll(self.list_handle.clone())
3316                .size_full()
3317                .flex_grow(),
3318            )
3319    }
3320
3321    fn page_items(&self) -> impl Iterator<Item = &SettingsPageItem> {
3322        let page_idx = self.current_page_index();
3323
3324        self.current_page()
3325            .items
3326            .iter()
3327            .enumerate()
3328            .filter_map(move |(item_index, item)| {
3329                self.search_matches[page_idx][item_index].then_some(item)
3330            })
3331    }
3332
3333    fn render_sub_page_breadcrumbs(&self) -> impl IntoElement {
3334        let mut items = vec![];
3335        items.push(self.current_page().title);
3336        items.extend(
3337            self.sub_page_stack
3338                .iter()
3339                .flat_map(|page| [page.section_header, page.link.title]),
3340        );
3341
3342        let last = items.pop().unwrap();
3343        h_flex()
3344            .gap_1()
3345            .children(
3346                items
3347                    .into_iter()
3348                    .flat_map(|item| [item, "/"])
3349                    .map(|item| Label::new(item).color(Color::Muted)),
3350            )
3351            .child(Label::new(last))
3352    }
3353
3354    fn render_page(&mut self, window: &mut Window, cx: &mut Context<SettingsWindow>) -> Div {
3355        let mut page = v_flex()
3356            .w_full()
3357            .pt_4()
3358            .px_6()
3359            .gap_4()
3360            .bg(cx.theme().colors().editor_background);
3361        let mut page_content = v_flex()
3362            .id("settings-ui-page")
3363            .gap_4()
3364            .overflow_y_scroll()
3365            .track_scroll(
3366                window
3367                    .use_state(cx, |_, _| ScrollHandle::default())
3368                    .read(cx),
3369            );
3370        if self.sub_page_stack.len() == 0 {
3371            page = page.child(self.render_files(window, cx));
3372
3373            let items: Vec<_> = self.page_items().collect();
3374            let items_len = items.len();
3375            let mut section_header = None;
3376
3377            page_content =
3378                page_content.children(items.into_iter().enumerate().map(|(index, item)| {
3379                    let is_last = index == items_len - 1;
3380                    if let SettingsPageItem::SectionHeader(header) = item {
3381                        section_header = Some(*header);
3382                    }
3383                    item.render(
3384                        self.current_file.clone(),
3385                        section_header.expect("All items rendered after a section header"),
3386                        is_last,
3387                        window,
3388                        cx,
3389                    )
3390                }))
3391        } else {
3392            page = page.child(
3393                h_flex()
3394                    .gap_2()
3395                    .child(IconButton::new("back-btn", IconName::ChevronLeft).on_click(
3396                        cx.listener(|this, _, _, cx| {
3397                            this.pop_sub_page(cx);
3398                        }),
3399                    ))
3400                    .child(self.render_sub_page_breadcrumbs()),
3401            );
3402
3403            let active_page_render_fn = self.sub_page_stack.last().unwrap().link.render.clone();
3404            page_content = page_content.child((active_page_render_fn)(self, window, cx));
3405        }
3406
3407        return page.child(page_content);
3408    }
3409
3410    fn current_page_index(&self) -> usize {
3411        self.page_index_from_navbar_index(self.navbar_entry)
3412    }
3413
3414    fn current_page(&self) -> &SettingsPage {
3415        &self.pages[self.current_page_index()]
3416    }
3417
3418    fn page_index_from_navbar_index(&self, index: usize) -> usize {
3419        if self.navbar_entries.is_empty() {
3420            return 0;
3421        }
3422
3423        self.navbar_entries[index].page_index
3424    }
3425
3426    fn page_for_navbar_index(&mut self, index: usize) -> &mut SettingsPage {
3427        let index = self.page_index_from_navbar_index(index);
3428        &mut self.pages[index]
3429    }
3430
3431    fn is_navbar_entry_selected(&self, ix: usize) -> bool {
3432        ix == self.navbar_entry
3433    }
3434
3435    fn push_sub_page(
3436        &mut self,
3437        sub_page_link: SubPageLink,
3438        section_header: &'static str,
3439        cx: &mut Context<SettingsWindow>,
3440    ) {
3441        self.sub_page_stack.push(SubPage {
3442            link: sub_page_link,
3443            section_header,
3444        });
3445        cx.notify();
3446    }
3447
3448    fn pop_sub_page(&mut self, cx: &mut Context<SettingsWindow>) {
3449        self.sub_page_stack.pop();
3450        cx.notify();
3451    }
3452}
3453
3454impl Render for SettingsWindow {
3455    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3456        let ui_font = theme::setup_ui_font(window, cx);
3457
3458        div()
3459            .flex()
3460            .flex_row()
3461            .size_full()
3462            .font(ui_font)
3463            .bg(cx.theme().colors().background)
3464            .text_color(cx.theme().colors().text)
3465            .child(self.render_nav(window, cx))
3466            .child(self.render_page(window, cx))
3467    }
3468}
3469
3470fn update_settings_file(
3471    file: SettingsUiFile,
3472    cx: &mut App,
3473    update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
3474) -> Result<()> {
3475    match file {
3476        SettingsUiFile::Local((worktree_id, rel_path)) => {
3477            fn all_projects(cx: &App) -> impl Iterator<Item = Entity<project::Project>> {
3478                workspace::AppState::global(cx)
3479                    .upgrade()
3480                    .map(|app_state| {
3481                        app_state
3482                            .workspace_store
3483                            .read(cx)
3484                            .workspaces()
3485                            .iter()
3486                            .filter_map(|workspace| {
3487                                Some(workspace.read(cx).ok()?.project().clone())
3488                            })
3489                    })
3490                    .into_iter()
3491                    .flatten()
3492            }
3493            let rel_path = rel_path.join(paths::local_settings_file_relative_path());
3494            let project = all_projects(cx).find(|project| {
3495                project.read_with(cx, |project, cx| {
3496                    project.contains_local_settings_file(worktree_id, &rel_path, cx)
3497                })
3498            });
3499            let Some(project) = project else {
3500                anyhow::bail!(
3501                    "Could not find worktree containing settings file: {}",
3502                    &rel_path.display(PathStyle::local())
3503                );
3504            };
3505            project.update(cx, |project, cx| {
3506                project.update_local_settings_file(worktree_id, rel_path, cx, update);
3507            });
3508            return Ok(());
3509        }
3510        SettingsUiFile::User => {
3511            // todo(settings_ui) error?
3512            SettingsStore::global(cx).update_settings_file(<dyn fs::Fs>::global(cx), update);
3513            Ok(())
3514        }
3515        SettingsUiFile::Server(_) => unimplemented!(),
3516    }
3517}
3518
3519fn render_text_field<T: From<String> + Into<String> + AsRef<str> + Clone>(
3520    field: SettingField<T>,
3521    file: SettingsUiFile,
3522    metadata: Option<&SettingsFieldMetadata>,
3523    cx: &mut App,
3524) -> AnyElement {
3525    let (_, initial_text) =
3526        SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick);
3527    let initial_text = Some(initial_text.clone()).filter(|s| !s.as_ref().is_empty());
3528
3529    SettingsEditor::new()
3530        .when_some(initial_text, |editor, text| {
3531            editor.with_initial_text(text.into())
3532        })
3533        .when_some(
3534            metadata.and_then(|metadata| metadata.placeholder),
3535            |editor, placeholder| editor.with_placeholder(placeholder),
3536        )
3537        .on_confirm({
3538            move |new_text, cx| {
3539                update_settings_file(file.clone(), cx, move |settings, _cx| {
3540                    *(field.pick_mut)(settings) = new_text.map(Into::into);
3541                })
3542                .log_err(); // todo(settings_ui) don't log err
3543            }
3544        })
3545        .into_any_element()
3546}
3547
3548fn render_toggle_button<B: Into<bool> + From<bool> + Copy>(
3549    field: SettingField<B>,
3550    file: SettingsUiFile,
3551    cx: &mut App,
3552) -> AnyElement {
3553    let (_, &value) = SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick);
3554
3555    let toggle_state = if value.into() {
3556        ToggleState::Selected
3557    } else {
3558        ToggleState::Unselected
3559    };
3560
3561    Switch::new("toggle_button", toggle_state)
3562        .color(ui::SwitchColor::Accent)
3563        .on_click({
3564            move |state, _window, cx| {
3565                let state = *state == ui::ToggleState::Selected;
3566                update_settings_file(file.clone(), cx, move |settings, _cx| {
3567                    *(field.pick_mut)(settings) = Some(state.into());
3568                })
3569                .log_err(); // todo(settings_ui) don't log err
3570            }
3571        })
3572        .color(SwitchColor::Accent)
3573        .into_any_element()
3574}
3575
3576fn render_dropdown<T>(
3577    field: SettingField<T>,
3578    file: SettingsUiFile,
3579    window: &mut Window,
3580    cx: &mut App,
3581) -> AnyElement
3582where
3583    T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + Sync + 'static,
3584{
3585    let variants = || -> &'static [T] { <T as strum::VariantArray>::VARIANTS };
3586    let labels = || -> &'static [&'static str] { <T as strum::VariantNames>::VARIANTS };
3587
3588    let (_, &current_value) =
3589        SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick);
3590
3591    let current_value_label =
3592        labels()[variants().iter().position(|v| *v == current_value).unwrap()];
3593
3594    DropdownMenu::new(
3595        "dropdown",
3596        current_value_label,
3597        ContextMenu::build(window, cx, move |mut menu, _, _| {
3598            for (&value, &label) in std::iter::zip(variants(), labels()) {
3599                let file = file.clone();
3600                menu = menu.toggleable_entry(
3601                    label,
3602                    value == current_value,
3603                    IconPosition::Start,
3604                    None,
3605                    move |_, cx| {
3606                        if value == current_value {
3607                            return;
3608                        }
3609                        update_settings_file(file.clone(), cx, move |settings, _cx| {
3610                            *(field.pick_mut)(settings) = Some(value);
3611                        })
3612                        .log_err(); // todo(settings_ui) don't log err
3613                    },
3614                );
3615            }
3616            menu
3617        }),
3618    )
3619    .style(DropdownStyle::Outlined)
3620    .into_any_element()
3621}
3622
3623#[cfg(test)]
3624mod test {
3625
3626    use super::*;
3627
3628    impl SettingsWindow {
3629        fn navbar(&self) -> &[NavBarEntry] {
3630            self.navbar_entries.as_slice()
3631        }
3632
3633        fn navbar_entry(&self) -> usize {
3634            self.navbar_entry
3635        }
3636
3637        fn new_builder(window: &mut Window, cx: &mut Context<Self>) -> Self {
3638            let mut this = Self::new(window, cx);
3639            this.navbar_entries.clear();
3640            this.pages.clear();
3641            this
3642        }
3643
3644        fn build(mut self) -> Self {
3645            self.build_navbar();
3646            self
3647        }
3648
3649        fn add_page(
3650            mut self,
3651            title: &'static str,
3652            build_page: impl Fn(SettingsPage) -> SettingsPage,
3653        ) -> Self {
3654            let page = SettingsPage {
3655                title,
3656                expanded: false,
3657                items: Vec::default(),
3658            };
3659
3660            self.pages.push(build_page(page));
3661            self
3662        }
3663
3664        fn search(&mut self, search_query: &str, window: &mut Window, cx: &mut Context<Self>) {
3665            self.search_task.take();
3666            self.search_bar.update(cx, |editor, cx| {
3667                editor.set_text(search_query, window, cx);
3668            });
3669            self.update_matches(cx);
3670        }
3671
3672        fn assert_search_results(&self, other: &Self) {
3673            // page index could be different because of filtered out pages
3674            assert!(
3675                self.navbar_entries
3676                    .iter()
3677                    .zip(other.navbar_entries.iter())
3678                    .all(|(entry, other)| {
3679                        entry.is_root == other.is_root && entry.title == other.title
3680                    })
3681            );
3682            assert_eq!(
3683                self.current_page().items.iter().collect::<Vec<_>>(),
3684                other.page_items().collect::<Vec<_>>()
3685            );
3686        }
3687    }
3688
3689    impl SettingsPage {
3690        fn item(mut self, item: SettingsPageItem) -> Self {
3691            self.items.push(item);
3692            self
3693        }
3694    }
3695
3696    impl SettingsPageItem {
3697        fn basic_item(title: &'static str, description: &'static str) -> Self {
3698            SettingsPageItem::SettingItem(SettingItem {
3699                title,
3700                description,
3701                field: Box::new(SettingField {
3702                    pick: |settings_content| &settings_content.auto_update,
3703                    pick_mut: |settings_content| &mut settings_content.auto_update,
3704                }),
3705                metadata: None,
3706            })
3707        }
3708    }
3709
3710    fn register_settings(cx: &mut App) {
3711        settings::init(cx);
3712        theme::init(theme::LoadThemes::JustBase, cx);
3713        workspace::init_settings(cx);
3714        project::Project::init_settings(cx);
3715        language::init(cx);
3716        editor::init(cx);
3717        menu::init();
3718    }
3719
3720    fn parse(input: &'static str, window: &mut Window, cx: &mut App) -> SettingsWindow {
3721        let mut pages: Vec<SettingsPage> = Vec::new();
3722        let mut current_page = None;
3723        let mut selected_idx = None;
3724        let mut ix = 0;
3725        let mut in_closed_subentry = false;
3726
3727        for mut line in input
3728            .lines()
3729            .map(|line| line.trim())
3730            .filter(|line| !line.is_empty())
3731        {
3732            let mut is_selected = false;
3733            if line.ends_with("*") {
3734                assert!(
3735                    selected_idx.is_none(),
3736                    "Can only have one selected navbar entry at a time"
3737                );
3738                selected_idx = Some(ix);
3739                line = &line[..line.len() - 1];
3740                is_selected = true;
3741            }
3742
3743            if line.starts_with("v") || line.starts_with(">") {
3744                if let Some(current_page) = current_page.take() {
3745                    pages.push(current_page);
3746                }
3747
3748                let expanded = line.starts_with("v");
3749                in_closed_subentry = !expanded;
3750                ix += 1;
3751
3752                current_page = Some(SettingsPage {
3753                    title: line.split_once(" ").unwrap().1,
3754                    expanded,
3755                    items: Vec::default(),
3756                });
3757            } else if line.starts_with("-") {
3758                if !in_closed_subentry {
3759                    ix += 1;
3760                } else if is_selected && in_closed_subentry {
3761                    panic!("Can't select sub entry if it's parent is closed");
3762                }
3763
3764                let Some(current_page) = current_page.as_mut() else {
3765                    panic!("Sub entries must be within a page");
3766                };
3767
3768                current_page.items.push(SettingsPageItem::SectionHeader(
3769                    line.split_once(" ").unwrap().1,
3770                ));
3771            } else {
3772                panic!(
3773                    "Entries must start with one of 'v', '>', or '-'\n line: {}",
3774                    line
3775                );
3776            }
3777        }
3778
3779        if let Some(current_page) = current_page.take() {
3780            pages.push(current_page);
3781        }
3782
3783        let search_matches = pages
3784            .iter()
3785            .map(|page| vec![true; page.items.len()])
3786            .collect::<Vec<_>>();
3787
3788        let mut settings_window = SettingsWindow {
3789            files: Vec::default(),
3790            current_file: crate::SettingsUiFile::User,
3791            pages,
3792            search_bar: cx.new(|cx| Editor::single_line(window, cx)),
3793            navbar_entry: selected_idx.expect("Must have a selected navbar entry"),
3794            navbar_entries: Vec::default(),
3795            list_handle: UniformListScrollHandle::default(),
3796            search_matches,
3797            search_task: None,
3798            sub_page_stack: vec![],
3799        };
3800
3801        settings_window.build_navbar();
3802        settings_window
3803    }
3804
3805    #[track_caller]
3806    fn check_navbar_toggle(
3807        before: &'static str,
3808        toggle_idx: usize,
3809        after: &'static str,
3810        window: &mut Window,
3811        cx: &mut App,
3812    ) {
3813        let mut settings_window = parse(before, window, cx);
3814        settings_window.toggle_navbar_entry(toggle_idx);
3815
3816        let expected_settings_window = parse(after, window, cx);
3817
3818        assert_eq!(settings_window.navbar(), expected_settings_window.navbar());
3819        assert_eq!(
3820            settings_window.navbar_entry(),
3821            expected_settings_window.navbar_entry()
3822        );
3823    }
3824
3825    macro_rules! check_navbar_toggle {
3826        ($name:ident, before: $before:expr, toggle_idx: $toggle_idx:expr, after: $after:expr) => {
3827            #[gpui::test]
3828            fn $name(cx: &mut gpui::TestAppContext) {
3829                let window = cx.add_empty_window();
3830                window.update(|window, cx| {
3831                    register_settings(cx);
3832                    check_navbar_toggle($before, $toggle_idx, $after, window, cx);
3833                });
3834            }
3835        };
3836    }
3837
3838    check_navbar_toggle!(
3839        navbar_basic_open,
3840        before: r"
3841        v General
3842        - General
3843        - Privacy*
3844        v Project
3845        - Project Settings
3846        ",
3847        toggle_idx: 0,
3848        after: r"
3849        > General*
3850        v Project
3851        - Project Settings
3852        "
3853    );
3854
3855    check_navbar_toggle!(
3856        navbar_basic_close,
3857        before: r"
3858        > General*
3859        - General
3860        - Privacy
3861        v Project
3862        - Project Settings
3863        ",
3864        toggle_idx: 0,
3865        after: r"
3866        v General*
3867        - General
3868        - Privacy
3869        v Project
3870        - Project Settings
3871        "
3872    );
3873
3874    check_navbar_toggle!(
3875        navbar_basic_second_root_entry_close,
3876        before: r"
3877        > General
3878        - General
3879        - Privacy
3880        v Project
3881        - Project Settings*
3882        ",
3883        toggle_idx: 1,
3884        after: r"
3885        > General
3886        > Project*
3887        "
3888    );
3889
3890    check_navbar_toggle!(
3891        navbar_toggle_subroot,
3892        before: r"
3893        v General Page
3894        - General
3895        - Privacy
3896        v Project
3897        - Worktree Settings Content*
3898        v AI
3899        - General
3900        > Appearance & Behavior
3901        ",
3902        toggle_idx: 3,
3903        after: r"
3904        v General Page
3905        - General
3906        - Privacy
3907        > Project*
3908        v AI
3909        - General
3910        > Appearance & Behavior
3911        "
3912    );
3913
3914    check_navbar_toggle!(
3915        navbar_toggle_close_propagates_selected_index,
3916        before: r"
3917        v General Page
3918        - General
3919        - Privacy
3920        v Project
3921        - Worktree Settings Content
3922        v AI
3923        - General*
3924        > Appearance & Behavior
3925        ",
3926        toggle_idx: 0,
3927        after: r"
3928        > General Page
3929        v Project
3930        - Worktree Settings Content
3931        v AI
3932        - General*
3933        > Appearance & Behavior
3934        "
3935    );
3936
3937    check_navbar_toggle!(
3938        navbar_toggle_expand_propagates_selected_index,
3939        before: r"
3940        > General Page
3941        - General
3942        - Privacy
3943        v Project
3944        - Worktree Settings Content
3945        v AI
3946        - General*
3947        > Appearance & Behavior
3948        ",
3949        toggle_idx: 0,
3950        after: r"
3951        v General Page
3952        - General
3953        - Privacy
3954        v Project
3955        - Worktree Settings Content
3956        v AI
3957        - General*
3958        > Appearance & Behavior
3959        "
3960    );
3961
3962    check_navbar_toggle!(
3963        navbar_toggle_sub_entry_does_nothing,
3964        before: r"
3965        > General Page
3966        - General
3967        - Privacy
3968        v Project
3969        - Worktree Settings Content
3970        v AI
3971        - General*
3972        > Appearance & Behavior
3973        ",
3974        toggle_idx: 4,
3975        after: r"
3976        > General Page
3977        - General
3978        - Privacy
3979        v Project
3980        - Worktree Settings Content
3981        v AI
3982        - General*
3983        > Appearance & Behavior
3984        "
3985    );
3986
3987    #[gpui::test]
3988    fn test_basic_search(cx: &mut gpui::TestAppContext) {
3989        let cx = cx.add_empty_window();
3990        let (actual, expected) = cx.update(|window, cx| {
3991            register_settings(cx);
3992
3993            let expected = cx.new(|cx| {
3994                SettingsWindow::new_builder(window, cx)
3995                    .add_page("General", |page| {
3996                        page.item(SettingsPageItem::SectionHeader("General settings"))
3997                            .item(SettingsPageItem::basic_item("test title", "General test"))
3998                    })
3999                    .build()
4000            });
4001
4002            let actual = cx.new(|cx| {
4003                SettingsWindow::new_builder(window, cx)
4004                    .add_page("General", |page| {
4005                        page.item(SettingsPageItem::SectionHeader("General settings"))
4006                            .item(SettingsPageItem::basic_item("test title", "General test"))
4007                    })
4008                    .add_page("Theme", |page| {
4009                        page.item(SettingsPageItem::SectionHeader("Theme settings"))
4010                    })
4011                    .build()
4012            });
4013
4014            actual.update(cx, |settings, cx| settings.search("gen", window, cx));
4015
4016            (actual, expected)
4017        });
4018
4019        cx.cx.run_until_parked();
4020
4021        cx.update(|_window, cx| {
4022            let expected = expected.read(cx);
4023            let actual = actual.read(cx);
4024            expected.assert_search_results(&actual);
4025        })
4026    }
4027
4028    #[gpui::test]
4029    fn test_search_render_page_with_filtered_out_navbar_entries(cx: &mut gpui::TestAppContext) {
4030        let cx = cx.add_empty_window();
4031        let (actual, expected) = cx.update(|window, cx| {
4032            register_settings(cx);
4033
4034            let actual = cx.new(|cx| {
4035                SettingsWindow::new_builder(window, cx)
4036                    .add_page("General", |page| {
4037                        page.item(SettingsPageItem::SectionHeader("General settings"))
4038                            .item(SettingsPageItem::basic_item(
4039                                "Confirm Quit",
4040                                "Whether to confirm before quitting Zed",
4041                            ))
4042                            .item(SettingsPageItem::basic_item(
4043                                "Auto Update",
4044                                "Automatically update Zed",
4045                            ))
4046                    })
4047                    .add_page("AI", |page| {
4048                        page.item(SettingsPageItem::basic_item(
4049                            "Disable AI",
4050                            "Whether to disable all AI features in Zed",
4051                        ))
4052                    })
4053                    .add_page("Appearance & Behavior", |page| {
4054                        page.item(SettingsPageItem::SectionHeader("Cursor")).item(
4055                            SettingsPageItem::basic_item(
4056                                "Cursor Shape",
4057                                "Cursor shape for the editor",
4058                            ),
4059                        )
4060                    })
4061                    .build()
4062            });
4063
4064            let expected = cx.new(|cx| {
4065                SettingsWindow::new_builder(window, cx)
4066                    .add_page("Appearance & Behavior", |page| {
4067                        page.item(SettingsPageItem::SectionHeader("Cursor")).item(
4068                            SettingsPageItem::basic_item(
4069                                "Cursor Shape",
4070                                "Cursor shape for the editor",
4071                            ),
4072                        )
4073                    })
4074                    .build()
4075            });
4076
4077            actual.update(cx, |settings, cx| settings.search("cursor", window, cx));
4078
4079            (actual, expected)
4080        });
4081
4082        cx.cx.run_until_parked();
4083
4084        cx.update(|_window, cx| {
4085            let expected = expected.read(cx);
4086            let actual = actual.read(cx);
4087            expected.assert_search_results(&actual);
4088        })
4089    }
4090}