settings_ui: Add more settings (#40708)
Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ...
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user