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