Improve macro to show items

Co-authored-by: Ben Kunkle <ben@zed.dev>
This commit is contained in:
Anthony 2025-08-26 15:36:53 -04:00
parent c1631b6e8c
commit 1fd0f7a46b
5 changed files with 148 additions and 23 deletions

View file

@ -1,13 +1,17 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use crate::{Settings, SettingsSources, SettingsUI, VsCodeSettings}; use crate as settings;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; 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. /// Base key bindings scheme. Base keymaps can be overridden with user keymaps.
/// ///
/// Default: VSCode /// 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 { pub enum BaseKeymap {
#[default] #[default]
VSCode, VSCode,
@ -96,8 +100,6 @@ impl BaseKeymap {
} }
} }
impl SettingsUI for BaseKeymap {}
impl Settings for BaseKeymap { impl Settings for BaseKeymap {
const KEY: Option<&'static str> = Some("base_keymap"); const KEY: Option<&'static str> = Some("base_keymap");

View file

@ -32,8 +32,8 @@ pub type EditorconfigProperties = ec4rs::Properties;
use crate::{ use crate::{
ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, SettingsUIItem, ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, SettingsUIItem,
VsCodeSettings, WorktreeId, parse_json_with_comments, settings_ui::SettingsUI, SettingsUIRender, VsCodeSettings, WorktreeId, parse_json_with_comments,
update_value_in_json_text, settings_ui::SettingsUI, update_value_in_json_text,
}; };
/// A value that can be defined as a user setting. /// A value that can be defined as a user setting.
@ -1508,7 +1508,7 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
} }
fn settings_ui_item(&self) -> SettingsUIItem { fn settings_ui_item(&self) -> SettingsUIItem {
T::ui_item() <T as SettingsUI>::settings_ui_item()
} }
} }

View file

@ -3,22 +3,23 @@ use std::any::Any;
use gpui::{AnyElement, App, Window}; use gpui::{AnyElement, App, Window};
pub trait SettingsUI { pub trait SettingsUI {
fn ui_item() -> SettingsUIItem { fn settings_ui_render() -> SettingsUIRender {
SettingsUIItem { SettingsUIRender::None
item: SettingsUIItemVariant::None,
}
} }
fn settings_ui_item() -> SettingsUIItem;
} }
pub struct SettingsUIItem { pub struct SettingsUIItem {
// TODO: move this back here once there isn't a None variant // TODO: move this back here once there isn't a None variant
// pub path: &'static str, // pub path: &'static str,
// pub title: &'static str,
pub item: SettingsUIItemVariant, pub item: SettingsUIItemVariant,
} }
pub enum SettingsUIItemVariant { pub enum SettingsUIItemVariant {
Group { Group {
path: &'static str, path: &'static str,
title: &'static str,
group: SettingsUIItemGroup, group: SettingsUIItemGroup,
}, },
Item { Item {
@ -35,9 +36,48 @@ pub struct SettingsUIItemGroup {
pub enum SettingsUIItemSingle { pub enum SettingsUIItemSingle {
// TODO: default/builtin variants // TODO: default/builtin variants
SwitchField,
Custom(Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> AnyElement>), Custom(Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> AnyElement>),
} }
pub enum SettingsUIRender {
Group {
title: &'static str,
items: Vec<SettingsUIItem>,
},
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: /* NOTES:
# Root Group # Root Group

View file

@ -126,11 +126,33 @@ impl Render for SettingsPage {
.settings_ui_items() .settings_ui_items()
.into_iter() .into_iter()
.flat_map(|item| match item.item { .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::Item { path, item } => todo!(),
settings::SettingsUIItemVariant::None => None, settings::SettingsUIItemVariant::None => None,
}) }),
.map(|group_name| Label::new(group_name).size(LabelSize::Large)),
) )
.child(Label::new("Settings").size(LabelSize::Large)) .child(Label::new("Settings").size(LabelSize::Large))
.child( .child(

View file

@ -1,5 +1,5 @@
use proc_macro::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::{ToTokens, quote};
use syn::{DeriveInput, LitStr, Token, parse_macro_input}; use syn::{DeriveInput, LitStr, Token, parse_macro_input};
/// Derive macro for the `SettingsUI` marker trait. /// 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))] #[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 input = parse_macro_input!(input as DeriveInput);
let name = &input.ident; 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! { 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 { } else {
quote! { 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! { let expanded = quote! {
impl #impl_generics settings::SettingsUI for #name #ty_generics #where_clause { impl #impl_generics settings::SettingsUI for #name #ty_generics #where_clause {
fn ui_item() -> settings::SettingsUIItem { fn settings_ui_render() -> settings::SettingsUIRender {
#ui_item_fn_body #ui_render_fn_body
}
fn settings_ui_item() -> settings::SettingsUIItem {
#settings_ui_item_fn_body
} }
} }
}; };
TokenStream::from(expanded) proc_macro::TokenStream::from(expanded)
} }