Improve macro to show items
Co-authored-by: Ben Kunkle <ben@zed.dev>
This commit is contained in:
parent
c1631b6e8c
commit
1fd0f7a46b
5 changed files with 148 additions and 23 deletions
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue