diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index f1132ec312cd13a7ad8a12813b8d4927c37c7977..06d681634a8002243ed838f308ea1a5743b5c9e6 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -8,7 +8,7 @@ use feature_flags::FeatureFlag; use fuzzy::StringMatchCandidate; use gpui::{ Action, App, Div, Entity, FocusHandle, Focusable, FontWeight, Global, ReadGlobal as _, - ScrollHandle, Subscription, Task, TitlebarOptions, UniformListScrollHandle, Window, + ScrollHandle, Stateful, Subscription, Task, TitlebarOptions, UniformListScrollHandle, Window, WindowBounds, WindowHandle, WindowOptions, actions, div, point, prelude::*, px, size, uniform_list, }; @@ -82,12 +82,20 @@ actions!( #[action(namespace = settings_editor)] struct FocusFile(pub u32); -#[derive(Clone, Copy)] struct SettingField { pick: fn(&SettingsContent) -> &Option, pick_mut: fn(&mut SettingsContent) -> &mut Option, } +impl Clone for SettingField { + fn clone(&self) -> Self { + *self + } +} + +// manual impl because derive puts a Copy bound on T, which is inaccurate in our case +impl Copy for SettingField {} + /// 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 @@ -98,7 +106,7 @@ impl SettingField { #[allow(unused)] fn unimplemented(self) -> SettingField { SettingField { - pick: |_| &None, + pick: |_| &Some(UnimplementedSettingField), pick_mut: |_| unreachable!(), } } @@ -126,10 +134,6 @@ impl AnySettingField for SettingField { } fn file_set_in(&self, file: SettingsUiFile, cx: &App) -> (settings::SettingsFile, bool) { - if AnySettingField::type_id(self) == TypeId::of::() { - return (file.to_settings(), true); - } - let (file, value) = cx .global::() .get_value_from_file(file.to_settings(), self.pick); @@ -145,12 +149,13 @@ struct SettingFieldRenderer { TypeId, Box< dyn Fn( - &dyn AnySettingField, + &SettingsWindow, + &SettingItem, SettingsUiFile, Option<&SettingsFieldMetadata>, &mut Window, - &mut App, - ) -> AnyElement, + &mut Context, + ) -> Stateful
, >, >, >, @@ -160,62 +165,78 @@ struct SettingFieldRenderer { impl Global for SettingFieldRenderer {} impl SettingFieldRenderer { - fn add_renderer( + fn add_basic_renderer( &mut self, - renderer: impl Fn( - &SettingField, + render_control: impl Fn( + SettingField, SettingsUiFile, Option<&SettingsFieldMetadata>, &mut Window, &mut App, ) -> AnyElement + 'static, + ) -> &mut Self { + self.add_renderer( + move |settings_window: &SettingsWindow, + item: &SettingItem, + field: SettingField, + settings_file: SettingsUiFile, + metadata: Option<&SettingsFieldMetadata>, + window: &mut Window, + cx: &mut Context| { + render_settings_item( + settings_window, + item, + settings_file.clone(), + render_control(field, settings_file, metadata, window, cx), + window, + cx, + ) + }, + ) + } + + fn add_renderer( + &mut self, + renderer: impl Fn( + &SettingsWindow, + &SettingItem, + SettingField, + SettingsUiFile, + Option<&SettingsFieldMetadata>, + &mut Window, + &mut Context, + ) -> Stateful
+ + 'static, ) -> &mut Self { let key = TypeId::of::(); let renderer = Box::new( - move |any_setting_field: &dyn AnySettingField, + move |settings_window: &SettingsWindow, + item: &SettingItem, settings_file: SettingsUiFile, metadata: Option<&SettingsFieldMetadata>, window: &mut Window, - cx: &mut App| { - let field = any_setting_field + cx: &mut Context| { + let field = *item + .field + .as_ref() .as_any() .downcast_ref::>() .unwrap(); - renderer(field, settings_file, metadata, window, cx) + renderer( + settings_window, + item, + field, + settings_file, + metadata, + window, + cx, + ) }, ); self.renderers.borrow_mut().insert(key, renderer); self } - - fn render( - &self, - any_setting_field: &dyn AnySettingField, - settings_file: SettingsUiFile, - metadata: Option<&SettingsFieldMetadata>, - window: &mut Window, - cx: &mut App, - ) -> AnyElement { - let key = any_setting_field.type_id(); - if let Some(renderer) = self.renderers.borrow().get(&key) { - renderer(any_setting_field, settings_file, metadata, window, cx) - } else { - Button::new("no-renderer", "NO RENDERER") - .style(ButtonStyle::Outlined) - .size(ButtonSize::Medium) - .icon(Some(IconName::XCircle)) - .icon_position(IconPosition::Start) - .icon_color(Color::Error) - .tab_index(0_isize) - .tooltip(Tooltip::text(any_setting_field.type_name())) - .into_any_element() - // panic!( - // "No renderer found for type: {}", - // any_setting_field.type_name() - // ) - } - } } struct NonFocusableHandle { @@ -277,7 +298,7 @@ pub fn init(cx: &mut App) { fn init_renderers(cx: &mut App) { cx.default_global::() - .add_renderer::(|_, _, _, _, _| { + .add_basic_renderer::(|_, _, _, _, _| { Button::new("open-in-settings-file", "Edit in settings.json") .style(ButtonStyle::Outlined) .size(ButtonSize::Medium) @@ -287,197 +308,70 @@ fn init_renderers(cx: &mut App) { }) .into_any_element() }) - .add_renderer::(|settings_field, file, _, _, cx| { - render_toggle_button(*settings_field, file, cx).into_any_element() - }) - .add_renderer::(|settings_field, file, metadata, _, cx| { - render_text_field(settings_field.clone(), file, metadata, cx) - }) - .add_renderer::(|settings_field, file, _, _, cx| { - render_toggle_button(*settings_field, file, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - // todo(settings_ui): We need to pass in a validator for this to ensure that users that type in invalid font names - render_font_picker(settings_field.clone(), file, window, cx) - }) + .add_basic_renderer::(render_toggle_button) + .add_basic_renderer::(render_text_field) + .add_basic_renderer::(render_toggle_button) + .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) + .add_basic_renderer::(render_font_picker) // todo(settings_ui): This needs custom ui // .add_renderer::(|settings_field, file, _, window, cx| { // // todo(settings_ui): Do we want to expose the custom variant of buffer line height? // // right now there's a manual impl of strum::VariantArray // render_dropdown(*settings_field, file, window, cx) // }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::( - |settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }, - ) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::( - |settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }, - ) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::( - |settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }, - ) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::( - |settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }, - ) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_number_field(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_number_field(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_number_field(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_number_field(*settings_field, file, window, cx) - }) - .add_renderer::>(|settings_field, file, _, window, cx| { - render_number_field(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_number_field(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_number_field(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_number_field(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_number_field(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }) - .add_renderer::(|settings_field, file, _, window, cx| { - render_dropdown(*settings_field, file, window, cx) - }); - - // todo(settings_ui): Figure out how we want to handle discriminant unions + .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) + .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) + .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) + .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) + .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) + .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) + .add_basic_renderer::(render_dropdown) + .add_basic_renderer::(render_dropdown) + .add_basic_renderer::(render_dropdown) + .add_basic_renderer::(render_number_field) + .add_basic_renderer::(render_number_field) + .add_basic_renderer::(render_number_field) + .add_basic_renderer::(render_number_field) + .add_basic_renderer::>(render_number_field) + .add_basic_renderer::(render_number_field) + .add_basic_renderer::(render_number_field) + .add_basic_renderer::(render_number_field) + .add_basic_renderer::(render_number_field) + .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) + .add_basic_renderer::(render_dropdown) + .add_basic_renderer::(render_dropdown); // .add_renderer::(|settings_field, file, _, window, cx| { // render_dropdown(*settings_field, file, window, cx) // }); @@ -633,14 +527,48 @@ impl SettingsPageItem { .into_any_element(), SettingsPageItem::SettingItem(setting_item) => { let renderer = cx.default_global::().clone(); - let (found_in_file, found) = setting_item.field.file_set_in(file.clone(), cx); - let file_set_in = SettingsUiFile::from_settings(found_in_file); + let (_, found) = setting_item.field.file_set_in(file.clone(), cx); + + let renderers = renderer.renderers.borrow(); + let field_renderer = + renderers.get(&AnySettingField::type_id(setting_item.field.as_ref())); + let field_renderer_or_warning = + field_renderer.ok_or("NO RENDERER").and_then(|renderer| { + if cfg!(debug_assertions) && !found { + Err("NO DEFAULT") + } else { + Ok(renderer) + } + }); - h_flex() - .id(setting_item.title) - .min_w_0() - .gap_2() - .justify_between() + let field = match field_renderer_or_warning { + Ok(field_renderer) => field_renderer( + settings_window, + setting_item, + file, + setting_item.metadata.as_deref(), + window, + cx, + ), + Err(warning) => render_settings_item( + settings_window, + setting_item, + file, + Button::new("error-warning", warning) + .style(ButtonStyle::Outlined) + .size(ButtonSize::Medium) + .icon(Some(IconName::Debug)) + .icon_position(IconPosition::Start) + .icon_color(Color::Error) + .tab_index(0_isize) + .tooltip(Tooltip::text(setting_item.field.type_name())) + .into_any_element(), + window, + cx, + ), + }; + + field .pt_4() .map(|this| { if is_last { @@ -651,58 +579,6 @@ impl SettingsPageItem { .border_color(cx.theme().colors().border_variant) } }) - .child( - v_flex() - .w_full() - .max_w_1_2() - .child( - h_flex() - .w_full() - .gap_1() - .child(Label::new(SharedString::new_static(setting_item.title))) - .when_some( - file_set_in.filter(|file_set_in| file_set_in != &file), - |this, file_set_in| { - this.child( - Label::new(format!( - "— set in {}", - settings_window - .display_name(&file_set_in) - .expect("File name should exist") - )) - .color(Color::Muted) - .size(LabelSize::Small), - ) - }, - ), - ) - .child( - Label::new(SharedString::new_static(setting_item.description)) - .size(LabelSize::Small) - .color(Color::Muted), - ), - ) - .child(if cfg!(debug_assertions) && !found { - Button::new("no-default-field", "NO DEFAULT") - .size(ButtonSize::Medium) - .icon(IconName::XCircle) - .icon_position(IconPosition::Start) - .icon_color(Color::Error) - .icon_size(IconSize::Small) - .style(ButtonStyle::Outlined) - .tooltip(Tooltip::text( - "This warning is only displayed in dev builds.", - )) - .into_any_element() - } else { - renderer.render( - setting_item.field.as_ref(), - file, - setting_item.metadata.as_deref(), - window, - cx, - ) - }) .into_any_element() } SettingsPageItem::SubPageLink(sub_page_link) => h_flex() @@ -744,6 +620,55 @@ impl SettingsPageItem { } } +fn render_settings_item( + settings_window: &SettingsWindow, + setting_item: &SettingItem, + file: SettingsUiFile, + control: AnyElement, + _window: &mut Window, + cx: &mut Context<'_, SettingsWindow>, +) -> Stateful
{ + let (found_in_file, _) = setting_item.field.file_set_in(file.clone(), cx); + let file_set_in = SettingsUiFile::from_settings(found_in_file); + + h_flex() + .id(setting_item.title) + .min_w_0() + .gap_2() + .justify_between() + .child( + v_flex() + .w_1_2() + .child( + h_flex() + .w_full() + .gap_1() + .child(Label::new(SharedString::new_static(setting_item.title))) + .when_some( + file_set_in.filter(|file_set_in| file_set_in != &file), + |this, file_set_in| { + this.child( + Label::new(format!( + "— set in {}", + settings_window + .display_name(&file_set_in) + .expect("File name should exist") + )) + .color(Color::Muted) + .size(LabelSize::Small), + ) + }, + ), + ) + .child( + Label::new(SharedString::new_static(setting_item.description)) + .size(LabelSize::Small) + .color(Color::Muted), + ), + ) + .child(control) +} + struct SettingItem { title: &'static str, description: &'static str, @@ -2133,6 +2058,7 @@ fn render_text_field + Into + AsRef + Clone>( field: SettingField, file: SettingsUiFile, metadata: Option<&SettingsFieldMetadata>, + _window: &mut Window, cx: &mut App, ) -> AnyElement { let (_, initial_text) = @@ -2162,6 +2088,8 @@ fn render_text_field + Into + AsRef + Clone>( fn render_toggle_button + From + Copy>( 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); @@ -2191,6 +2119,7 @@ fn render_toggle_button + From + Copy>( fn render_font_picker( field: SettingField, file: SettingsUiFile, + _metadata: Option<&SettingsFieldMetadata>, window: &mut Window, cx: &mut App, ) -> AnyElement { @@ -2238,6 +2167,7 @@ fn render_font_picker( fn render_number_field( field: SettingField, file: SettingsUiFile, + _metadata: Option<&SettingsFieldMetadata>, window: &mut Window, cx: &mut App, ) -> AnyElement { @@ -2259,6 +2189,7 @@ fn render_number_field( fn render_dropdown( field: SettingField, file: SettingsUiFile, + _metadata: Option<&SettingsFieldMetadata>, window: &mut Window, cx: &mut App, ) -> AnyElement