diff --git a/assets/settings/default.json b/assets/settings/default.json index 331c696bf890ed16762e7702d03257952a30d124..3afa21be2bb4e1e542b2610c70a7672158d274de 100644 --- a/assets/settings/default.json +++ b/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. diff --git a/crates/file_finder/src/file_finder_settings.rs b/crates/file_finder/src/file_finder_settings.rs index 8689e0ad1e3df2c90c2c033953f08eb31aff052d..4d826211c70b24c9f9bad7e23b8981fa8cb7bdd0 100644 --- a/crates/file_finder/src/file_finder_settings.rs +++ b/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, + }, } } } diff --git a/crates/migrator/src/migrations.rs b/crates/migrator/src/migrations.rs index 87a301dcad83127666888a7530ba43488ac5a72f..084a3348b54acd9d2fc6ba043e1fb1648bbb3f8b 100644 --- a/crates/migrator/src/migrations.rs +++ b/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; +} diff --git a/crates/migrator/src/migrations/m_2025_10_17/settings.rs b/crates/migrator/src/migrations/m_2025_10_17/settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..83f17a5b8afb441b4bbf481c781eb1fbfa3d036a --- /dev/null +++ b/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(()) +} diff --git a/crates/migrator/src/migrator.rs b/crates/migrator/src/migrator.rs index 7bb5deb9516668eee699d4078eda7dc74a92be2e..e5f0c584c407284aa175a3ac33f3c9a9e01c1365 100644 --- a/crates/migrator/src/migrator.rs +++ b/crates/migrator/src/migrator.rs @@ -212,6 +212,7 @@ pub fn migrate_settings(text: &str) -> Result> { &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(), + ), + ); + } } diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 045bc21613141ca30f125ab25757a3b3a97307b0..526bb1b5c7ab8134bc53b79d1c37092cd49144b3 100644 --- a/crates/settings/src/settings_content.rs +++ b/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, + /// Default: Smart + pub include_ignored: Option, +} + +#[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( diff --git a/crates/settings/src/settings_content/terminal.rs b/crates/settings/src/settings_content/terminal.rs index e5d3ba60b52073963115934afdd368c582ccfff2..aba2d5209a071b1d8aa50a7feea2965d9cafb948 100644 --- a/crates/settings/src/settings_content/terminal.rs +++ b/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 diff --git a/crates/settings/src/settings_content/theme.rs b/crates/settings/src/settings_content/theme.rs index c988b98a4ef04c7ff5e9b20eaf9e2bebeccb88fb..0228fcbfe832f56c8ad504fd289b273b0a1c0ec7 100644 --- a/crates/settings/src/settings_content/theme.rs +++ b/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 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. diff --git a/crates/settings/src/settings_content/workspace.rs b/crates/settings/src/settings_content/workspace.rs index 78b17eeb883dd83b16f45c0cabfc3be9a7840eae..577f8fa4f996b2a808bdc785c56210e766dab2fb 100644 --- a/crates/settings/src/settings_content/workspace.rs +++ b/crates/settings/src/settings_content/workspace.rs @@ -382,7 +382,19 @@ pub struct StatusBarSettingsContent { pub cursor_position_button: Option, } -#[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. diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index 6683f42daee9832f493878dc3c8721ff3bc9d268..19a495cf770cddd4dd3252e23ef5be8a6c1308eb 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -252,7 +252,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec { description: "How to select the theme", field: Box::new(SettingField { pick: |settings_content| { - Some(&<::Discriminant as strum::VariantArray>::VARIANTS[ + Some(&dynamic_variants::()[ settings_content .theme .theme @@ -263,7 +263,9 @@ pub(crate) fn settings_data(cx: &App) -> Vec { 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 { pick_discriminant: |settings_content| { Some(settings_content.theme.theme.as_ref()?.discriminant() as usize) }, - fields: <::Discriminant as strum::VariantArray>::VARIANTS.into_iter().map(|variant| { + fields: dynamic_variants::().into_iter().map(|variant| { match variant { settings::ThemeSelectionDiscriminants::Static => vec![ SettingItem { @@ -407,20 +409,169 @@ pub(crate) fn settings_data(cx: &App) -> Vec { } }).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_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::().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 { 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_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::().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 { 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_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::().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 { .include_ignored = value; }, } - .unimplemented(), ), metadata: None, files: USER, @@ -3174,8 +3443,8 @@ pub(crate) fn settings_data(cx: &App) -> Vec { }), 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 { .show = value; }, } - .unimplemented(), ), metadata: None, }), @@ -3466,8 +3734,8 @@ pub(crate) fn settings_data(cx: &App) -> Vec { }), 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 { .show = value; }, } - .unimplemented(), ), metadata: None, }), @@ -3959,31 +4226,91 @@ pub(crate) fn settings_data(cx: &App) -> Vec { 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_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::().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() -> &'static [T::Discriminant] +where + T: strum::IntoDiscriminant, + T::Discriminant: strum::VariantArray, +{ + <::Discriminant as strum::VariantArray>::VARIANTS +} diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 7d38ccd367890e2df8dd360e9a15fc9155dd1ed0..d430f719c9476e4216e5fedd42269b13b916f4fe 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -439,6 +439,13 @@ fn init_renderers(cx: &mut App) { .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_theme_picker) + .add_basic_renderer::(render_dropdown) + .add_basic_renderer::(render_icon_theme_picker) + .add_basic_renderer::(render_dropdown) + .add_basic_renderer::(render_dropdown) + .add_basic_renderer::(render_dropdown) + .add_basic_renderer::(render_dropdown) + .add_basic_renderer::(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, + 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 {