From 1fd0f7a46b9aeb5f50ac0b7b6f3e56270e4c462b Mon Sep 17 00:00:00 2001 From: Anthony Date: Tue, 26 Aug 2025 15:36:53 -0400 Subject: [PATCH] Improve macro to show items Co-authored-by: Ben Kunkle --- crates/settings/src/base_keymap_setting.rs | 10 ++- crates/settings/src/settings_store.rs | 6 +- crates/settings/src/settings_ui.rs | 48 ++++++++++- crates/settings_ui/src/settings_ui.rs | 28 ++++++- .../src/settings_ui_macros.rs | 79 ++++++++++++++++--- 5 files changed, 148 insertions(+), 23 deletions(-) diff --git a/crates/settings/src/base_keymap_setting.rs b/crates/settings/src/base_keymap_setting.rs index 272eb2e78e..4f4ba6e4a4 100644 --- a/crates/settings/src/base_keymap_setting.rs +++ b/crates/settings/src/base_keymap_setting.rs @@ -1,13 +1,17 @@ use std::fmt::{Display, Formatter}; -use crate::{Settings, SettingsSources, SettingsUI, VsCodeSettings}; +use crate as settings; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use settings::{Settings, SettingsSources, VsCodeSettings}; +use settings_ui_macros::SettingsUI; /// Base key bindings scheme. Base keymaps can be overridden with user keymaps. /// /// Default: VSCode -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] +#[derive( + Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default, SettingsUI, +)] pub enum BaseKeymap { #[default] VSCode, @@ -96,8 +100,6 @@ impl BaseKeymap { } } -impl SettingsUI for BaseKeymap {} - impl Settings for BaseKeymap { const KEY: Option<&'static str> = Some("base_keymap"); diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 238df0dda1..cc06e990d3 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -32,8 +32,8 @@ pub type EditorconfigProperties = ec4rs::Properties; use crate::{ ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, SettingsUIItem, - VsCodeSettings, WorktreeId, parse_json_with_comments, settings_ui::SettingsUI, - update_value_in_json_text, + SettingsUIRender, VsCodeSettings, WorktreeId, parse_json_with_comments, + settings_ui::SettingsUI, update_value_in_json_text, }; /// A value that can be defined as a user setting. @@ -1508,7 +1508,7 @@ impl AnySettingValue for SettingValue { } fn settings_ui_item(&self) -> SettingsUIItem { - T::ui_item() + ::settings_ui_item() } } diff --git a/crates/settings/src/settings_ui.rs b/crates/settings/src/settings_ui.rs index e405c5bd8f..de6823f3f5 100644 --- a/crates/settings/src/settings_ui.rs +++ b/crates/settings/src/settings_ui.rs @@ -3,22 +3,23 @@ use std::any::Any; use gpui::{AnyElement, App, Window}; pub trait SettingsUI { - fn ui_item() -> SettingsUIItem { - SettingsUIItem { - item: SettingsUIItemVariant::None, - } + fn settings_ui_render() -> SettingsUIRender { + SettingsUIRender::None } + fn settings_ui_item() -> SettingsUIItem; } pub struct SettingsUIItem { // TODO: move this back here once there isn't a None variant // pub path: &'static str, + // pub title: &'static str, pub item: SettingsUIItemVariant, } pub enum SettingsUIItemVariant { Group { path: &'static str, + title: &'static str, group: SettingsUIItemGroup, }, Item { @@ -35,9 +36,48 @@ pub struct SettingsUIItemGroup { pub enum SettingsUIItemSingle { // TODO: default/builtin variants + SwitchField, Custom(Box AnyElement>), } +pub enum SettingsUIRender { + Group { + title: &'static str, + items: Vec, + }, + Item(SettingsUIItemSingle), + None, +} + +impl SettingsUI for bool { + fn settings_ui_render() -> SettingsUIRender { + SettingsUIRender::Item(SettingsUIItemSingle::SwitchField) + } + + fn settings_ui_item() -> SettingsUIItem { + SettingsUIItem { + item: SettingsUIItemVariant::None, + } + } +} + +/* +#[derive(SettingsUI)] +#[settings_ui(group = "Foo")] +struct Foo { + // #[settings_ui(render = "my_render_function")] + pub toggle: bool, + pub font_size: u32, + + Group(vec![Item {path: "toggle", item: SwitchField}]) +} + +macro code: +settings_ui_item() { + group.items = struct.fields.map((field_name, field_type) => quote! { SettingsUIItem::Item {path: #field_type::settings_ui_path().unwrap_or_else(|| #field_name), item: if field.attrs.render { #render } else field::settings_ui_render()}}) + } + */ + /* NOTES: # Root Group diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 83c9514792..678b8afb56 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -126,11 +126,33 @@ impl Render for SettingsPage { .settings_ui_items() .into_iter() .flat_map(|item| match item.item { - settings::SettingsUIItemVariant::Group { path, group } => Some(path), + settings::SettingsUIItemVariant::Group { title, path, group } => Some( + div() + .child(Label::new(title).size(LabelSize::Large)) + .children(group.items.iter().map(|item| { + match &item.item { + settings::SettingsUIItemVariant::Group { + path, + title, + group, + } => div() + .child(format!("Subgroup: {}", title)) + .into_any_element(), + settings::SettingsUIItemVariant::Item { path, item } => { + div() + .child(format!("Item: {}", path)) + .into_any_element() + } + settings::SettingsUIItemVariant::None => { + div().child("None").into_any_element() + } + } + })), + ), + settings::SettingsUIItemVariant::Item { path, item } => todo!(), settings::SettingsUIItemVariant::None => None, - }) - .map(|group_name| Label::new(group_name).size(LabelSize::Large)), + }), ) .child(Label::new("Settings").size(LabelSize::Large)) .child( diff --git a/crates/settings_ui_macros/src/settings_ui_macros.rs b/crates/settings_ui_macros/src/settings_ui_macros.rs index 1f6f8fdd4f..d83056629d 100644 --- a/crates/settings_ui_macros/src/settings_ui_macros.rs +++ b/crates/settings_ui_macros/src/settings_ui_macros.rs @@ -1,5 +1,5 @@ -use proc_macro::TokenStream; -use quote::quote; +use proc_macro2::TokenStream; +use quote::{ToTokens, quote}; use syn::{DeriveInput, LitStr, Token, parse_macro_input}; /// Derive macro for the `SettingsUI` marker trait. @@ -22,7 +22,7 @@ use syn::{DeriveInput, LitStr, Token, parse_macro_input}; /// } /// ``` #[proc_macro_derive(SettingsUI, attributes(settings_ui))] -pub fn derive_settings_ui(input: TokenStream) -> TokenStream { +pub fn derive_settings_ui(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; @@ -48,23 +48,84 @@ pub fn derive_settings_ui(input: TokenStream) -> TokenStream { } } - let ui_item_fn_body = if let Some(group_name) = group_name { + // let mut root_item = vec![]; + // for field in struct { + // + // match field::settings_ui_render() + // Group(items) => { + // let item = items.map(|item| something); + // item + // items.push(item::settings_ui_render()); + // root_item.push(Group(items)); + // }, + // Leaf(item) => { + // root_item.push(item); + // } + // } + // } + // + // group.items = struct.fields.map((field_name, field_type) => quote! { SettingsUIItem::Item {path: #field_type::settings_ui_path().unwrap_or_else(|| #field_name), item: if field.attrs.render { #render } else field::settings_ui_render()}}) + // } + + fn map_ui_item_to_render(path: &str, ty: TokenStream) -> TokenStream { quote! { - settings::SettingsUIItem { item: settings::SettingsUIItemVariant::Group{ path: #group_name, group: settings::SettingsUIItemGroup{ items: Default::default() } } } + settings::SettingsUIItem { + item: match #ty::settings_ui_render() { + settings::SettingsUIRender::Group{title, items} => settings::SettingsUIItemVariant::Group { + title, + path: #path, + group: settings::SettingsUIItemGroup { items }, + }, + settings::SettingsUIRender::Item(item) => settings::SettingsUIItemVariant::Item { + path: #path, + item, + }, + settings::SettingsUIRender::None => settings::SettingsUIItemVariant::None, + } + } + } + } + + let ui_render_fn_body = if let Some(group_name) = group_name { + let fields = match input.data { + syn::Data::Struct(data_struct) => data_struct + .fields + .iter() + .map(|field| { + ( + field.ident.clone().expect("tuple fields").to_string(), + field.ty.to_token_stream(), + ) + }) + .collect(), + syn::Data::Enum(data_enum) => vec![], // todo! enums + syn::Data::Union(data_union) => unimplemented!("Derive SettingsUI for unions"), + }; + let items = fields + .into_iter() + .map(|(name, ty)| map_ui_item_to_render(&name, ty)); + quote! { + settings::SettingsUIRender::Group{ title: #group_name, items: vec![#(#items),*] } } } else { quote! { - settings::SettingsUIItem { item: settings::SettingsUIItemVariant::None } + settings::SettingsUIRender::None } }; + let settings_ui_item_fn_body = map_ui_item_to_render("todo! define path", quote! { Self }); + let expanded = quote! { impl #impl_generics settings::SettingsUI for #name #ty_generics #where_clause { - fn ui_item() -> settings::SettingsUIItem { - #ui_item_fn_body + fn settings_ui_render() -> settings::SettingsUIRender { + #ui_render_fn_body + } + + fn settings_ui_item() -> settings::SettingsUIItem { + #settings_ui_item_fn_body } } }; - TokenStream::from(expanded) + proc_macro::TokenStream::from(expanded) }