settings_ui: Add more settings (#40708)

Ben Kunkle created

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

assets/settings/default.json                            |   8 
crates/file_finder/src/file_finder_settings.rs          |   6 
crates/migrator/src/migrations.rs                       |   6 
crates/migrator/src/migrations/m_2025_10_17/settings.rs |  23 
crates/migrator/src/migrator.rs                         |  72 +
crates/settings/src/settings_content.rs                 |  35 
crates/settings/src/settings_content/terminal.rs        |  13 
crates/settings/src/settings_content/theme.rs           |  27 
crates/settings/src/settings_content/workspace.rs       |  14 
crates/settings_ui/src/page_data.rs                     | 463 +++++++++-
crates/settings_ui/src/settings_ui.rs                   |  64 +
11 files changed, 650 insertions(+), 81 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -1091,10 +1091,10 @@
     // Only the file Zed had indexed will be used, not necessary all the gitignored files.
     //
     // Can accept 3 values:
-    //   * `true`: Use all gitignored files
-    //   * `false`: Use only the files Zed had indexed
-    //   * `null`: Be smart and search for ignored when called from a gitignored worktree
-    "include_ignored": null
+    //   * "all": Use all gitignored files
+    //   * "indexed": Use only the files Zed had indexed
+    //   * "smart": Be smart and search for ignored when called from a gitignored worktree
+    "include_ignored": "smart"
   },
   // Whether or not to remove any trailing whitespace from lines of a buffer
   // before saving it.

crates/file_finder/src/file_finder_settings.rs 🔗

@@ -18,7 +18,11 @@ impl Settings for FileFinderSettings {
             file_icons: file_finder.file_icons.unwrap(),
             modal_max_width: file_finder.modal_max_width.unwrap().into(),
             skip_focus_for_active_in_search: file_finder.skip_focus_for_active_in_search.unwrap(),
-            include_ignored: file_finder.include_ignored,
+            include_ignored: match file_finder.include_ignored.unwrap() {
+                settings::IncludeIgnoredContent::All => Some(true),
+                settings::IncludeIgnoredContent::Indexed => Some(false),
+                settings::IncludeIgnoredContent::Smart => None,
+            },
         }
     }
 }

crates/migrator/src/migrations.rs 🔗

@@ -123,3 +123,9 @@ pub(crate) mod m_2025_10_16 {
 
     pub(crate) use settings::restore_code_actions_on_format;
 }
+
+pub(crate) mod m_2025_10_17 {
+    mod settings;
+
+    pub(crate) use settings::make_file_finder_include_ignored_an_enum;
+}

crates/migrator/src/migrations/m_2025_10_17/settings.rs 🔗

@@ -0,0 +1,23 @@
+use anyhow::Result;
+use serde_json::Value;
+
+pub fn make_file_finder_include_ignored_an_enum(value: &mut Value) -> Result<()> {
+    let Some(file_finder) = value.get_mut("file_finder") else {
+        return Ok(());
+    };
+
+    let Some(file_finder_obj) = file_finder.as_object_mut() else {
+        anyhow::bail!("Expected file_finder to be an object");
+    };
+
+    let Some(include_ignored) = file_finder_obj.get_mut("include_ignored") else {
+        return Ok(());
+    };
+    *include_ignored = match include_ignored {
+        Value::Bool(true) => Value::String("all".to_string()),
+        Value::Bool(false) => Value::String("indexed".to_string()),
+        Value::Null => Value::String("smart".to_string()),
+        _ => anyhow::bail!("Expected include_ignored to be a boolean or null"),
+    };
+    Ok(())
+}

crates/migrator/src/migrator.rs 🔗

@@ -212,6 +212,7 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
             &SETTINGS_QUERY_2025_10_03,
         ),
         MigrationType::Json(migrations::m_2025_10_16::restore_code_actions_on_format),
+        MigrationType::Json(migrations::m_2025_10_17::make_file_finder_include_ignored_an_enum),
     ];
     run_migrations(text, migrations)
 }
@@ -2090,4 +2091,75 @@ mod tests {
             ),
         );
     }
+
+    #[test]
+    fn test_make_file_finder_include_ignored_an_enum() {
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2025_10_17::make_file_finder_include_ignored_an_enum,
+            )],
+            &r#"{ }"#.unindent(),
+            None,
+        );
+
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2025_10_17::make_file_finder_include_ignored_an_enum,
+            )],
+            &r#"{
+                "file_finder": {
+                    "include_ignored": true
+                }
+            }"#
+            .unindent(),
+            Some(
+                &r#"{
+                    "file_finder": {
+                        "include_ignored": "all"
+                    }
+                }"#
+                .unindent(),
+            ),
+        );
+
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2025_10_17::make_file_finder_include_ignored_an_enum,
+            )],
+            &r#"{
+                "file_finder": {
+                    "include_ignored": false
+                }
+            }"#
+            .unindent(),
+            Some(
+                &r#"{
+                    "file_finder": {
+                        "include_ignored": "indexed"
+                    }
+                }"#
+                .unindent(),
+            ),
+        );
+
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2025_10_17::make_file_finder_include_ignored_an_enum,
+            )],
+            &r#"{
+                "file_finder": {
+                    "include_ignored": null
+                }
+            }"#
+            .unindent(),
+            Some(
+                &r#"{
+                    "file_finder": {
+                        "include_ignored": "smart"
+                    }
+                }"#
+                .unindent(),
+            ),
+        );
+    }
 }

crates/settings/src/settings_content.rs 🔗

@@ -608,14 +608,33 @@ pub struct FileFinderSettingsContent {
     /// Whether to use gitignored files when searching.
     /// Only the file Zed had indexed will be used, not necessary all the gitignored files.
     ///
-    /// Can accept 3 values:
-    /// * `Some(true)`: Use all gitignored files
-    /// * `Some(false)`: Use only the files Zed had indexed
-    /// * `None`: Be smart and search for ignored when called from a gitignored worktree
-    ///
-    /// Default: None
-    /// todo() -> Change this type to an enum
-    pub include_ignored: Option<bool>,
+    /// Default: Smart
+    pub include_ignored: Option<IncludeIgnoredContent>,
+}
+
+#[derive(
+    Debug,
+    PartialEq,
+    Eq,
+    Clone,
+    Copy,
+    Default,
+    Serialize,
+    Deserialize,
+    JsonSchema,
+    MergeFrom,
+    strum::VariantArray,
+    strum::VariantNames,
+)]
+#[serde(rename_all = "snake_case")]
+pub enum IncludeIgnoredContent {
+    /// Use all gitignored files
+    All,
+    /// Use only the files Zed had indexed
+    Indexed,
+    /// Be smart and search for ignored when called from a gitignored worktree
+    #[default]
+    Smart,
 }
 
 #[derive(

crates/settings/src/settings_content/terminal.rs 🔗

@@ -153,7 +153,18 @@ pub enum Shell {
     },
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
+#[derive(
+    Clone,
+    Debug,
+    Serialize,
+    Deserialize,
+    PartialEq,
+    Eq,
+    JsonSchema,
+    MergeFrom,
+    strum::EnumDiscriminants,
+)]
+#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
 #[serde(rename_all = "snake_case")]
 pub enum WorkingDirectory {
     /// Use the current file's project directory.  Will Fallback to the

crates/settings/src/settings_content/theme.rs 🔗

@@ -160,7 +160,18 @@ pub enum ThemeSelection {
 }
 
 /// Represents the selection of an icon theme, which can be either static or dynamic.
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)]
+#[derive(
+    Clone,
+    Debug,
+    Serialize,
+    Deserialize,
+    JsonSchema,
+    MergeFrom,
+    PartialEq,
+    Eq,
+    strum::EnumDiscriminants,
+)]
+#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
 #[serde(untagged)]
 pub enum IconThemeSelection {
     /// A static icon theme selection, represented by a single icon theme name.
@@ -290,7 +301,19 @@ impl From<FontFamilyName> for String {
 }
 
 /// The buffer's line height.
-#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Default)]
+#[derive(
+    Clone,
+    Copy,
+    Debug,
+    Serialize,
+    Deserialize,
+    PartialEq,
+    JsonSchema,
+    MergeFrom,
+    Default,
+    strum::EnumDiscriminants,
+)]
+#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
 #[serde(rename_all = "snake_case")]
 pub enum BufferLineHeight {
     /// A less dense line height.

crates/settings/src/settings_content/workspace.rs 🔗

@@ -382,7 +382,19 @@ pub struct StatusBarSettingsContent {
     pub cursor_position_button: Option<bool>,
 }
 
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
+#[derive(
+    Copy,
+    Clone,
+    Debug,
+    Serialize,
+    Deserialize,
+    PartialEq,
+    Eq,
+    JsonSchema,
+    MergeFrom,
+    strum::EnumDiscriminants,
+)]
+#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
 #[serde(rename_all = "snake_case")]
 pub enum AutosaveSetting {
     /// Disable autosave.

crates/settings_ui/src/page_data.rs 🔗

@@ -252,7 +252,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                         description: "How to select the theme",
                         field: Box::new(SettingField {
                             pick: |settings_content| {
-                                Some(&<<settings::ThemeSelection as strum::IntoDiscriminant>::Discriminant as strum::VariantArray>::VARIANTS[
+                                Some(&dynamic_variants::<settings::ThemeSelection>()[
                                     settings_content
                                         .theme
                                         .theme
@@ -263,7 +263,9 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                                 let Some(value) = value else {
                                     return;
                                 };
-                                let settings_value = settings_content.theme.theme.as_mut().expect("Has Default");
+                                let settings_value = settings_content.theme.theme.get_or_insert_with(|| {
+                                    settings::ThemeSelection::Static(theme::ThemeName(theme::default_theme(theme::SystemAppearance::default().0).into()))
+                                });
                                 *settings_value = match value {
                                     settings::ThemeSelectionDiscriminants::Static => {
                                         let name = match settings_value {
@@ -298,7 +300,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                     pick_discriminant: |settings_content| {
                         Some(settings_content.theme.theme.as_ref()?.discriminant() as usize)
                     },
-                    fields: <<settings::ThemeSelection as strum::IntoDiscriminant>::Discriminant as strum::VariantArray>::VARIANTS.into_iter().map(|variant| {
+                    fields: dynamic_variants::<settings::ThemeSelection>().into_iter().map(|variant| {
                         match variant {
                             settings::ThemeSelectionDiscriminants::Static => vec![
                                 SettingItem {
@@ -407,20 +409,169 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                         }
                     }).collect(),
                 }),
-                SettingsPageItem::SettingItem(SettingItem {
-                    files: USER,
-                    title: "Icon Theme",
-                    // todo(settings_ui)
-                    // This description is misleading because the icon theme is used in more places than the file explorer)
-                    description: "Choose the icon theme for file explorer",
-                    field: Box::new(
-                        SettingField {
-                            pick: |settings_content| settings_content.theme.icon_theme.as_ref(),
-                            write: |settings_content, value|{  settings_content.theme.icon_theme = value;},
+                SettingsPageItem::DynamicItem(DynamicItem {
+                    discriminant: SettingItem {
+                        files: USER,
+                        title: "Icon Theme",
+                        description: "The Icon Theme Zed Will Associate With Files And Directories",
+                        field: Box::new(SettingField {
+                            pick: |settings_content| {
+                                Some(&dynamic_variants::<settings::IconThemeSelection>()[
+                                    settings_content
+                                        .theme
+                                        .icon_theme
+                                        .as_ref()?
+                                        .discriminant() as usize])
+                            },
+                            write: |settings_content, value| {
+                                let Some(value) = value else {
+                                    return;
+                                };
+                                let settings_value = settings_content.theme.icon_theme.get_or_insert_with(|| {
+                                    settings::IconThemeSelection::Static(settings::IconThemeName(theme::default_icon_theme().name.clone().into()))
+                                });
+                                *settings_value = match value {
+                                    settings::IconThemeSelectionDiscriminants::Static => {
+                                        let name = match settings_value {
+                                            settings::IconThemeSelection::Static(_) => return,
+                                            settings::IconThemeSelection::Dynamic { mode, light, dark } => {
+                                                match mode {
+                                                    theme::ThemeMode::Light => light.clone(),
+                                                    theme::ThemeMode::Dark => dark.clone(),
+                                                    theme::ThemeMode::System => dark.clone(), // no cx, can't determine correct choice
+                                                }
+                                            },
+                                        };
+                                        settings::IconThemeSelection::Static(name)
+                                    },
+                                    settings::IconThemeSelectionDiscriminants::Dynamic => {
+                                        let static_name = match settings_value {
+                                            settings::IconThemeSelection::Static(theme_name) => theme_name.clone(),
+                                            settings::IconThemeSelection::Dynamic {..} => return,
+                                        };
+
+                                        settings::IconThemeSelection::Dynamic {
+                                            mode: settings::ThemeMode::System,
+                                            light: static_name.clone(),
+                                            dark: static_name,
+                                        }
+                                    },
+                                };
+                            },
+                        }),
+                        metadata: None,
+                    },
+                    pick_discriminant: |settings_content| {
+                        Some(settings_content.theme.icon_theme.as_ref()?.discriminant() as usize)
+                    },
+                    fields: dynamic_variants::<settings::IconThemeSelection>().into_iter().map(|variant| {
+                        match variant {
+                            settings::IconThemeSelectionDiscriminants::Static => vec![
+                                SettingItem {
+                                    files: USER,
+                                    title: "Icon Theme Name",
+                                    description: "The Name Of The Icon Theme To Use",
+                                    field: Box::new(SettingField {
+                                        pick: |settings_content| {
+                                            match settings_content.theme.icon_theme.as_ref() {
+                                                Some(settings::IconThemeSelection::Static(name)) => Some(name),
+                                                _ => None
+                                            }
+                                        },
+                                        write: |settings_content, value| {
+                                            let Some(value) = value else {
+                                                return;
+                                            };
+                                            match settings_content
+                                                .theme
+                                                .icon_theme.as_mut() {
+                                                    Some(settings::IconThemeSelection::Static(theme_name)) => *theme_name = value,
+                                                    _ => return
+                                                }
+                                        },
+                                    }),
+                                    metadata: None,
+                                }
+                            ],
+                            settings::IconThemeSelectionDiscriminants::Dynamic => vec![
+                                SettingItem {
+                                    files: USER,
+                                    title: "Mode",
+                                    description: "How To Determine Whether to Use a Light or Dark Icon Theme",
+                                    field: Box::new(SettingField {
+                                        pick: |settings_content| {
+                                            match settings_content.theme.icon_theme.as_ref() {
+                                                Some(settings::IconThemeSelection::Dynamic { mode, ..}) => Some(mode),
+                                                _ => None
+                                            }
+                                        },
+                                        write: |settings_content, value| {
+                                            let Some(value) = value else {
+                                                return;
+                                            };
+                                            match settings_content
+                                                .theme
+                                                .icon_theme.as_mut() {
+                                                    Some(settings::IconThemeSelection::Dynamic{ mode, ..}) => *mode = value,
+                                                    _ => return
+                                                }
+                                        },
+                                    }),
+                                    metadata: None,
+                                },
+                                SettingItem {
+                                    files: USER,
+                                    title: "Light Icon Theme",
+                                    description: "The Icon Theme To Use When Mode Is Set To Light, Or When Mode Is Set To System And The System Is In Light Mode",
+                                    field: Box::new(SettingField {
+                                        pick: |settings_content| {
+                                            match settings_content.theme.icon_theme.as_ref() {
+                                                Some(settings::IconThemeSelection::Dynamic { light, ..}) => Some(light),
+                                                _ => None
+                                            }
+                                        },
+                                        write: |settings_content, value| {
+                                            let Some(value) = value else {
+                                                return;
+                                            };
+                                            match settings_content
+                                                .theme
+                                                .icon_theme.as_mut() {
+                                                    Some(settings::IconThemeSelection::Dynamic{ light, ..}) => *light = value,
+                                                    _ => return
+                                                }
+                                        },
+                                    }),
+                                    metadata: None,
+                                },
+                                SettingItem {
+                                    files: USER,
+                                    title: "Dark Icon Theme",
+                                    description: "The Icon Theme To Use When Mode Is Set To Dark, Or When Mode Is Set To System And The System Is In Dark Mode",
+                                    field: Box::new(SettingField {
+                                        pick: |settings_content| {
+                                            match settings_content.theme.icon_theme.as_ref() {
+                                                Some(settings::IconThemeSelection::Dynamic { dark, ..}) => Some(dark),
+                                                _ => None
+                                            }
+                                        },
+                                        write: |settings_content, value| {
+                                            let Some(value) = value else {
+                                                return;
+                                            };
+                                            match settings_content
+                                                .theme
+                                                .icon_theme.as_mut() {
+                                                    Some(settings::IconThemeSelection::Dynamic{ dark, ..}) => *dark = value,
+                                                    _ => return
+                                                }
+                                        },
+                                    }),
+                                    metadata: None,
+                                }
+                            ],
                         }
-                        .unimplemented(),
-                    ),
-                    metadata: None,
+                    }).collect(),
                 }),
                 SettingsPageItem::SectionHeader("Buffer Font"),
                 SettingsPageItem::SettingItem(SettingItem {
@@ -453,24 +604,79 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                     metadata: None,
                     files: USER,
                 }),
-                // todo(settings_ui): This needs custom ui
-                SettingsPageItem::SettingItem(SettingItem {
-                    files: USER,
-                    title: "Line Height",
-                    description: "Line height for editor text",
-                    field: Box::new(
-                        SettingField {
+                SettingsPageItem::DynamicItem(DynamicItem {
+                    discriminant: SettingItem {
+                        files: USER,
+                        title: "Line Height",
+                        description: "Line height for editor text",
+                        field: Box::new(SettingField {
                             pick: |settings_content| {
-                                settings_content.theme.buffer_line_height.as_ref()
+                                Some(&dynamic_variants::<settings::BufferLineHeight>()[
+                                    settings_content
+                                        .theme
+                                        .buffer_line_height
+                                        .as_ref()?
+                                        .discriminant() as usize])
                             },
                             write: |settings_content, value| {
-                                settings_content.theme.buffer_line_height = value;
-
+                                let Some(value) = value else {
+                                    return;
+                                };
+                                let settings_value = settings_content.theme.buffer_line_height.get_or_insert_with(|| {
+                                    settings::BufferLineHeight::default()
+                                });
+                                *settings_value = match value {
+                                    settings::BufferLineHeightDiscriminants::Comfortable => {
+                                        settings::BufferLineHeight::Comfortable
+                                    },
+                                    settings::BufferLineHeightDiscriminants::Standard => {
+                                        settings::BufferLineHeight::Standard
+                                    },
+                                    settings::BufferLineHeightDiscriminants::Custom => {
+                                        let custom_value = theme::BufferLineHeight::from(*settings_value).value();
+                                        settings::BufferLineHeight::Custom(custom_value)
+                                    },
+                                };
                             },
+                        }),
+                        metadata: None,
+                    },
+                    pick_discriminant: |settings_content| {
+                        Some(settings_content.theme.buffer_line_height.as_ref()?.discriminant() as usize)
+                    },
+                    fields: dynamic_variants::<settings::BufferLineHeight>().into_iter().map(|variant| {
+                        match variant {
+                            settings::BufferLineHeightDiscriminants::Comfortable => vec![],
+                            settings::BufferLineHeightDiscriminants::Standard => vec![],
+                            settings::BufferLineHeightDiscriminants::Custom => vec![
+                                SettingItem {
+                                    files: USER,
+                                    title: "Custom Line Height",
+                                    description: "Custom line height value (must be at least 1.0)",
+                                    field: Box::new(SettingField {
+                                        pick: |settings_content| {
+                                            match settings_content.theme.buffer_line_height.as_ref() {
+                                                Some(settings::BufferLineHeight::Custom(value)) => Some(value),
+                                                _ => None
+                                            }
+                                        },
+                                        write: |settings_content, value| {
+                                            let Some(value) = value else {
+                                                return;
+                                            };
+                                            match settings_content
+                                                .theme
+                                                .buffer_line_height.as_mut() {
+                                                    Some(settings::BufferLineHeight::Custom(line_height)) => *line_height = f32::max(value, 1.0),
+                                                    _ => return
+                                                }
+                                        },
+                                    }),
+                                    metadata: None,
+                                }
+                            ],
                         }
-                        .unimplemented(),
-                    ),
-                    metadata: None,
+                    }).collect(),
                 }),
                 SettingsPageItem::SettingItem(SettingItem {
                     files: USER,
@@ -835,22 +1041,86 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
             items: {
                 let mut items = vec![
                     SettingsPageItem::SectionHeader("Auto Save"),
-                    SettingsPageItem::SettingItem(SettingItem {
-                        title: "Auto Save Mode",
-                        description: "When to Auto Save Buffer Changes",
-                        field: Box::new(
-                            SettingField {
+                    SettingsPageItem::DynamicItem(DynamicItem {
+                        discriminant: SettingItem {
+                            files: USER,
+                            title: "Auto Save Mode",
+                            description: "When to Auto Save Buffer Changes",
+                            field: Box::new(SettingField {
                                 pick: |settings_content| {
-                                    settings_content.workspace.autosave.as_ref()
+                                    Some(&dynamic_variants::<settings::AutosaveSetting>()[
+                                        settings_content
+                                            .workspace
+                                            .autosave
+                                            .as_ref()?
+                                            .discriminant() as usize])
                                 },
                                 write: |settings_content, value| {
-                                    settings_content.workspace.autosave = value;
+                                    let Some(value) = value else {
+                                        return;
+                                    };
+                                    let settings_value = settings_content.workspace.autosave.get_or_insert_with(|| {
+                                        settings::AutosaveSetting::Off
+                                    });
+                                    *settings_value = match value {
+                                        settings::AutosaveSettingDiscriminants::Off => {
+                                            settings::AutosaveSetting::Off
+                                        },
+                                        settings::AutosaveSettingDiscriminants::AfterDelay => {
+                                            let milliseconds = match settings_value {
+                                                settings::AutosaveSetting::AfterDelay { milliseconds } => *milliseconds,
+                                                _ => settings::DelayMs(1000),
+                                            };
+                                            settings::AutosaveSetting::AfterDelay { milliseconds }
+                                        },
+                                        settings::AutosaveSettingDiscriminants::OnFocusChange => {
+                                            settings::AutosaveSetting::OnFocusChange
+                                        },
+                                        settings::AutosaveSettingDiscriminants::OnWindowChange => {
+                                            settings::AutosaveSetting::OnWindowChange
+                                        },
+                                    };
                                 },
+                            }),
+                            metadata: None,
+                        },
+                        pick_discriminant: |settings_content| {
+                            Some(settings_content.workspace.autosave.as_ref()?.discriminant() as usize)
+                        },
+                        fields: dynamic_variants::<settings::AutosaveSetting>().into_iter().map(|variant| {
+                            match variant {
+                                settings::AutosaveSettingDiscriminants::Off => vec![],
+                                settings::AutosaveSettingDiscriminants::AfterDelay => vec![
+                                    SettingItem {
+                                        files: USER,
+                                        title: "Delay (milliseconds)",
+                                        description: "Save after inactivity period (in milliseconds)",
+                                        field: Box::new(SettingField {
+                                            pick: |settings_content| {
+                                                match settings_content.workspace.autosave.as_ref() {
+                                                    Some(settings::AutosaveSetting::AfterDelay { milliseconds }) => Some(milliseconds),
+                                                    _ => None
+                                                }
+                                            },
+                                            write: |settings_content, value| {
+                                                let Some(value) = value else {
+                                                    return;
+                                                };
+                                                match settings_content
+                                                    .workspace
+                                                    .autosave.as_mut() {
+                                                        Some(settings::AutosaveSetting::AfterDelay { milliseconds }) => *milliseconds = value,
+                                                        _ => return
+                                                    }
+                                            },
+                                        }),
+                                        metadata: None,
+                                    }
+                                ],
+                                settings::AutosaveSettingDiscriminants::OnFocusChange => vec![],
+                                settings::AutosaveSettingDiscriminants::OnWindowChange => vec![],
                             }
-                            .unimplemented(),
-                        ),
-                        metadata: None,
-                        files: USER,
+                        }).collect(),
                     }),
                     SettingsPageItem::SectionHeader("Multibuffer"),
                     SettingsPageItem::SettingItem(SettingItem {
@@ -2088,7 +2358,6 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                                     .include_ignored = value;
                             },
                         }
-                        .unimplemented(),
                     ),
                     metadata: None,
                     files: USER,
@@ -3174,8 +3443,8 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                 }),
                 SettingsPageItem::SettingItem(SettingItem {
                     files: USER,
-                    title: "Indent Guides Show",
-                    description: "Show indent guides in the project panel",
+                    title: "Show Indent Guides",
+                    description: "Show Indent Guides In The Project Panel",
                     field: Box::new(
                         SettingField {
                             pick: |settings_content| {
@@ -3196,7 +3465,6 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                                     .show = value;
                             },
                         }
-                        .unimplemented(),
                     ),
                     metadata: None,
                 }),
@@ -3466,8 +3734,8 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                 }),
                 SettingsPageItem::SettingItem(SettingItem {
                     files: USER,
-                    title: "Indent Guides Show",
-                    description: "When to show indent guides in the outline panel",
+                    title: "Show Indent Guides",
+                    description: "When To Show Indent Guides In The Outline Panel",
                     field: Box::new(
                         SettingField {
                             pick: |settings_content| {
@@ -3488,7 +3756,6 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                                     .show = value;
                             },
                         }
-                        .unimplemented(),
                     ),
                     metadata: None,
                 }),
@@ -3959,31 +4226,91 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
                     metadata: None,
                     files: USER | LOCAL,
                 }),
-                SettingsPageItem::SettingItem(SettingItem {
-                    title: "Working Directory",
-                    description: "What working directory to use when launching the terminal",
-                    field: Box::new(
-                        SettingField {
+                SettingsPageItem::DynamicItem(DynamicItem {
+                    discriminant: SettingItem {
+                        files: USER | LOCAL,
+                        title: "Working Directory",
+                        description: "What working directory to use when launching the terminal",
+                        field: Box::new(SettingField {
                             pick: |settings_content| {
-                                settings_content
-                                    .terminal
-                                    .as_ref()?
-                                    .project
-                                    .working_directory
-                                    .as_ref()
+                                Some(&dynamic_variants::<settings::WorkingDirectory>()[
+                                    settings_content
+                                        .terminal
+                                        .as_ref()?
+                                        .project
+                                        .working_directory
+                                        .as_ref()?
+                                        .discriminant() as usize])
                             },
                             write: |settings_content, value| {
-                                settings_content
+                                let Some(value) = value else {
+                                    return;
+                                };
+                                let settings_value = settings_content
                                     .terminal
                                     .get_or_insert_default()
                                     .project
-                                    .working_directory = value;
+                                    .working_directory
+                                    .get_or_insert_with(|| settings::WorkingDirectory::CurrentProjectDirectory);
+                                *settings_value = match value {
+                                    settings::WorkingDirectoryDiscriminants::CurrentProjectDirectory => {
+                                        settings::WorkingDirectory::CurrentProjectDirectory
+                                    },
+                                    settings::WorkingDirectoryDiscriminants::FirstProjectDirectory => {
+                                        settings::WorkingDirectory::FirstProjectDirectory
+                                    },
+                                    settings::WorkingDirectoryDiscriminants::AlwaysHome => {
+                                        settings::WorkingDirectory::AlwaysHome
+                                    },
+                                    settings::WorkingDirectoryDiscriminants::Always => {
+                                        let directory = match settings_value {
+                                            settings::WorkingDirectory::Always { .. } => return,
+                                            _ => String::new(),
+                                        };
+                                        settings::WorkingDirectory::Always { directory }
+                                    },
+                                };
                             },
+                        }),
+                        metadata: None,
+                    },
+                    pick_discriminant: |settings_content| {
+                        Some(settings_content.terminal.as_ref()?.project.working_directory.as_ref()?.discriminant() as usize)
+                    },
+                    fields: dynamic_variants::<settings::WorkingDirectory>().into_iter().map(|variant| {
+                        match variant {
+                            settings::WorkingDirectoryDiscriminants::CurrentProjectDirectory => vec![],
+                            settings::WorkingDirectoryDiscriminants::FirstProjectDirectory => vec![],
+                            settings::WorkingDirectoryDiscriminants::AlwaysHome => vec![],
+                            settings::WorkingDirectoryDiscriminants::Always => vec![
+                                SettingItem {
+                                    files: USER | LOCAL,
+                                    title: "Directory",
+                                    description: "The directory path to use (will be shell expanded)",
+                                    field: Box::new(SettingField {
+                                        pick: |settings_content| {
+                                            match settings_content.terminal.as_ref()?.project.working_directory.as_ref() {
+                                                Some(settings::WorkingDirectory::Always { directory }) => Some(directory),
+                                                _ => None
+                                            }
+                                        },
+                                        write: |settings_content, value| {
+                                            let value = value.unwrap_or_default();
+                                            match settings_content
+                                                .terminal
+                                                .get_or_insert_default()
+                                                .project
+                                                .working_directory.as_mut() {
+                                                    Some(settings::WorkingDirectory::Always { directory }) => *directory = value,
+                                                    _ => return
+                                                }
+                                        },
+                                    }),
+                                    metadata: None,
+                                }
+                            ],
                         }
-                        .unimplemented(),
-                    ),
-                    metadata: None,
-                    files: USER | LOCAL,
+                    }).collect(),
                 }),
                 SettingsPageItem::SettingItem(SettingItem {
                     title: "Environment Variables",
@@ -6293,3 +6620,11 @@ fn show_scrollbar_or_editor(
         .as_ref()
         .and_then(|scrollbar| scrollbar.show.as_ref()))
 }
+
+fn dynamic_variants<T>() -> &'static [T::Discriminant]
+where
+    T: strum::IntoDiscriminant,
+    T::Discriminant: strum::VariantArray,
+{
+    <<T as strum::IntoDiscriminant>::Discriminant as strum::VariantArray>::VARIANTS
+}

crates/settings_ui/src/settings_ui.rs 🔗

@@ -439,6 +439,13 @@ fn init_renderers(cx: &mut App) {
         .add_basic_renderer::<settings::ThemeSelectionDiscriminants>(render_dropdown)
         .add_basic_renderer::<settings::ThemeMode>(render_dropdown)
         .add_basic_renderer::<settings::ThemeName>(render_theme_picker)
+        .add_basic_renderer::<settings::IconThemeSelectionDiscriminants>(render_dropdown)
+        .add_basic_renderer::<settings::IconThemeName>(render_icon_theme_picker)
+        .add_basic_renderer::<settings::BufferLineHeightDiscriminants>(render_dropdown)
+        .add_basic_renderer::<settings::AutosaveSettingDiscriminants>(render_dropdown)
+        .add_basic_renderer::<settings::WorkingDirectoryDiscriminants>(render_dropdown)
+        .add_basic_renderer::<settings::IncludeIgnoredContent>(render_dropdown)
+        .add_basic_renderer::<settings::ShowIndentGuides>(render_dropdown)
         // please semicolon stay on next line
         ;
 }
@@ -2942,6 +2949,63 @@ fn render_theme_picker(
     .into_any_element()
 }
 
+fn render_icon_theme_picker(
+    field: SettingField<settings::IconThemeName>,
+    file: SettingsUiFile,
+    _metadata: Option<&SettingsFieldMetadata>,
+    window: &mut Window,
+    cx: &mut App,
+) -> AnyElement {
+    let (_, value) = SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick);
+    let current_value = value
+        .cloned()
+        .map(|icon_theme_name| icon_theme_name.0.into())
+        .unwrap_or_else(|| theme::default_icon_theme().name.clone());
+
+    DropdownMenu::new(
+        "font-picker",
+        current_value.clone(),
+        ContextMenu::build(window, cx, move |mut menu, _, cx| {
+            let all_theme_names = theme::ThemeRegistry::global(cx)
+                .list_icon_themes()
+                .into_iter()
+                .map(|theme| theme.name);
+            for theme_name in all_theme_names {
+                let file = file.clone();
+                let selected = theme_name.as_ref() == current_value.as_ref();
+                menu = menu.toggleable_entry(
+                    theme_name.clone(),
+                    selected,
+                    IconPosition::End,
+                    None,
+                    move |_, cx| {
+                        if selected {
+                            return;
+                        }
+                        let theme_name = theme_name.clone();
+                        update_settings_file(file.clone(), cx, move |settings, _cx| {
+                            (field.write)(
+                                settings,
+                                Some(settings::IconThemeName(theme_name.into())),
+                            );
+                        })
+                        .log_err(); // todo(settings_ui) don't log err
+                    },
+                );
+            }
+            menu
+        }),
+    )
+    .trigger_size(ButtonSize::Medium)
+    .style(DropdownStyle::Outlined)
+    .offset(gpui::Point {
+        x: px(0.0),
+        y: px(2.0),
+    })
+    .tab_index(0)
+    .into_any_element()
+}
+
 #[cfg(test)]
 mod test {