settings_ui: Implement reset to default button (#40135)

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
This commit is contained in:
Ben Kunkle
2025-10-13 16:24:45 -04:00
committed by GitHub
parent a3bcf6fe21
commit f0d097c66a
3 changed files with 127 additions and 7 deletions
+28 -4
View File
@@ -484,7 +484,7 @@ impl SettingsStore {
files
}
fn get_content_for_file(&self, file: SettingsFile) -> Option<&SettingsContent> {
pub fn get_content_for_file(&self, file: SettingsFile) -> Option<&SettingsContent> {
match file {
SettingsFile::User => self
.user_settings
@@ -538,6 +538,25 @@ impl SettingsStore {
&self,
target_file: SettingsFile,
pick: fn(&SettingsContent) -> &Option<T>,
) -> (SettingsFile, Option<&T>) {
self.get_value_from_file_inner(target_file, pick, true)
}
/// Same as `Self::get_value_from_file` except that it does not include the current file.
/// Therefore it returns the value that was potentially overloaded by the target file.
pub fn get_value_up_to_file<T>(
&self,
target_file: SettingsFile,
pick: fn(&SettingsContent) -> &Option<T>,
) -> (SettingsFile, Option<&T>) {
self.get_value_from_file_inner(target_file, pick, false)
}
fn get_value_from_file_inner<T>(
&self,
target_file: SettingsFile,
pick: fn(&SettingsContent) -> &Option<T>,
include_target_file: bool,
) -> (SettingsFile, Option<&T>) {
// todo(settings_ui): Add a metadata field for overriding the "overrides" tag, for contextually different settings
// e.g. disable AI isn't overridden, or a vec that gets extended instead or some such
@@ -547,10 +566,15 @@ impl SettingsStore {
let mut found_file = false;
for file in all_files.into_iter() {
if !found_file && file != target_file && file != SettingsFile::Default {
continue;
if !found_file && file != SettingsFile::Default {
if file != target_file {
continue;
}
found_file = true;
if !include_target_file {
continue;
}
}
found_file = true;
if let SettingsFile::Project((worktree_id, ref path)) = file
&& let SettingsFile::Project((target_worktree_id, ref target_path)) = target_file
+74 -2
View File
@@ -104,8 +104,15 @@ impl<T: 'static> Copy for SettingField<T> {}
/// Helper for unimplemented settings, used in combination with `SettingField::unimplemented`
/// to keep the setting around in the UI with valid pick and pick_mut implementations, but don't actually try to render it.
/// TODO(settings_ui): In non-dev builds (`#[cfg(not(debug_assertions))]`) make this render as edit-in-json
#[derive(Clone, Copy)]
struct UnimplementedSettingField;
impl PartialEq for UnimplementedSettingField {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl<T: 'static> SettingField<T> {
/// Helper for settings with types that are not yet implemented.
#[allow(unused)]
@@ -123,9 +130,15 @@ trait AnySettingField {
fn type_id(&self) -> TypeId;
// Returns the file this value was set in and true, or File::Default and false to indicate it was not found in any file (missing default)
fn file_set_in(&self, file: SettingsUiFile, cx: &App) -> (settings::SettingsFile, bool);
fn reset_to_default_fn(
&self,
current_file: &SettingsUiFile,
file_set_in: &settings::SettingsFile,
cx: &App,
) -> Option<Box<dyn Fn(&mut App)>>;
}
impl<T> AnySettingField for SettingField<T> {
impl<T: PartialEq + Clone + Send + Sync + 'static> AnySettingField for SettingField<T> {
fn as_any(&self) -> &dyn Any {
self
}
@@ -144,6 +157,47 @@ impl<T> AnySettingField for SettingField<T> {
.get_value_from_file(file.to_settings(), self.pick);
return (file, value.is_some());
}
fn reset_to_default_fn(
&self,
current_file: &SettingsUiFile,
file_set_in: &settings::SettingsFile,
cx: &App,
) -> Option<Box<dyn Fn(&mut App)>> {
if file_set_in == &settings::SettingsFile::Default {
return None;
}
let this = *self;
let store = SettingsStore::global(cx);
let default_value = (this.pick)(store.raw_default_settings());
let is_default = store
.get_content_for_file(file_set_in.clone())
.map_or(&None, this.pick)
== default_value;
if is_default {
return None;
}
let current_file = current_file.clone();
return Some(Box::new(move |cx| {
let store = SettingsStore::global(cx);
let default_value = (this.pick)(store.raw_default_settings());
let is_set_somewhere_other_than_default = store
.get_value_up_to_file(current_file.to_settings(), this.pick)
.0
!= settings::SettingsFile::Default;
let value_to_set = if is_set_somewhere_other_than_default {
default_value.clone()
} else {
None
};
update_settings_file(current_file.clone(), cx, move |settings, _| {
*(this.pick_mut)(settings) = value_to_set;
})
// todo(settings_ui): Don't log err
.log_err();
}));
}
}
#[derive(Default, Clone)]
@@ -658,7 +712,7 @@ fn render_settings_item(
cx: &mut Context<'_, SettingsWindow>,
) -> Stateful<Div> {
let (found_in_file, _) = setting_item.field.file_set_in(file.clone(), cx);
let file_set_in = SettingsUiFile::from_settings(found_in_file);
let file_set_in = SettingsUiFile::from_settings(found_in_file.clone());
h_flex()
.id(setting_item.title)
@@ -673,6 +727,24 @@ fn render_settings_item(
.w_full()
.gap_1()
.child(Label::new(SharedString::new_static(setting_item.title)))
.when_some(
setting_item
.field
.reset_to_default_fn(&file, &found_in_file, cx),
|this, reset_to_default| {
this.child(
IconButton::new("reset-to-default-btn", IconName::Undo)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.on_click({
move |_, _, cx| {
reset_to_default(cx);
}
})
.tooltip(Tooltip::text("Reset to default")),
)
},
)
.when_some(
file_set_in.filter(|file_set_in| file_set_in != &file),
|this, file_set_in| {
+25 -1
View File
@@ -279,7 +279,7 @@ impl Component for Icon {
)
}
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
Some(
v_flex()
.gap_6()
@@ -314,6 +314,30 @@ impl Component for Icon {
),
],
),
example_group_with_title(
"All Icons",
vec![single_example(
"All Icons",
h_flex()
.image_cache(gpui::retain_all("all icons"))
.flex_wrap()
.gap_2()
.children(<IconName as strum::IntoEnumIterator>::iter().map(
|icon_name| {
h_flex()
.gap_1()
.border_1()
.rounded_md()
.px_2()
.py_1()
.border_color(Color::Muted.color(cx))
.child(SharedString::new_static(icon_name.into()))
.child(Icon::new(icon_name).into_any_element())
},
))
.into_any_element(),
)],
),
])
.into_any_element(),
)