settings_ui.rs

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