1use std::any::TypeId;
2
3use anyhow::Context as _;
4use fs::Fs;
5use gpui::{AnyElement, App, AppContext as _, ReadGlobal as _, Window};
6use smallvec::SmallVec;
7
8use crate::SettingsStore;
9
10pub trait SettingsUi {
11 fn settings_ui_item() -> SettingsUiItem {
12 // todo(settings_ui): remove this default impl, only entry should have a default impl
13 // because it's expected that the macro or custom impl use the item and the known paths to create the entry
14 SettingsUiItem::None
15 }
16
17 fn settings_ui_entry() -> SettingsUiEntry {
18 SettingsUiEntry {
19 path: None,
20 title: "None entry",
21 item: SettingsUiItem::None,
22 }
23 }
24}
25
26pub struct SettingsUiEntry {
27 /// The path in the settings JSON file for this setting. Relative to parent
28 /// None implies `#[serde(flatten)]` or `Settings::KEY.is_none()` for top level settings
29 pub path: Option<&'static str>,
30 /// What is displayed for the text for this entry
31 pub title: &'static str,
32 pub item: SettingsUiItem,
33}
34
35pub enum SettingsUiItemSingle {
36 SwitchField,
37 /// A numeric stepper for a specific type of number
38 NumericStepper(NumType),
39 ToggleGroup {
40 /// Must be the same length as `labels`
41 variants: &'static [&'static str],
42 /// Must be the same length as `variants`
43 labels: &'static [&'static str],
44 },
45 /// This should be used when toggle group size > 6
46 DropDown {
47 /// Must be the same length as `labels`
48 variants: &'static [&'static str],
49 /// Must be the same length as `variants`
50 labels: &'static [&'static str],
51 },
52 Custom(Box<dyn Fn(SettingsValue<serde_json::Value>, &mut Window, &mut App) -> AnyElement>),
53}
54
55pub struct SettingsValue<T> {
56 pub title: &'static str,
57 pub path: SmallVec<[&'static str; 1]>,
58 pub value: Option<T>,
59 pub default_value: T,
60}
61
62impl<T> SettingsValue<T> {
63 pub fn read(&self) -> &T {
64 match &self.value {
65 Some(value) => value,
66 None => &self.default_value,
67 }
68 }
69}
70
71impl SettingsValue<serde_json::Value> {
72 pub fn write_value(path: &SmallVec<[&'static str; 1]>, value: serde_json::Value, cx: &mut App) {
73 let settings_store = SettingsStore::global(cx);
74 let fs = <dyn Fs>::global(cx);
75
76 let rx = settings_store.update_settings_file_at_path(fs.clone(), path.as_slice(), value);
77 let path = path.clone();
78 cx.background_spawn(async move {
79 rx.await?
80 .with_context(|| format!("Failed to update setting at path `{:?}`", path.join(".")))
81 })
82 .detach_and_log_err(cx);
83 }
84}
85
86impl<T: serde::Serialize> SettingsValue<T> {
87 pub fn write(
88 path: &SmallVec<[&'static str; 1]>,
89 value: T,
90 cx: &mut App,
91 ) -> Result<(), serde_json::Error> {
92 SettingsValue::write_value(path, serde_json::to_value(value)?, cx);
93 Ok(())
94 }
95}
96
97pub struct SettingsUiItemDynamic {
98 pub options: Vec<SettingsUiEntry>,
99 pub determine_option: fn(&serde_json::Value, &App) -> usize,
100}
101
102pub struct SettingsUiItemGroup {
103 pub items: Vec<SettingsUiEntry>,
104}
105
106pub enum SettingsUiItem {
107 Group(SettingsUiItemGroup),
108 Single(SettingsUiItemSingle),
109 Dynamic(SettingsUiItemDynamic),
110 None,
111}
112
113impl SettingsUi for bool {
114 fn settings_ui_item() -> SettingsUiItem {
115 SettingsUiItem::Single(SettingsUiItemSingle::SwitchField)
116 }
117}
118
119impl SettingsUi for Option<bool> {
120 fn settings_ui_item() -> SettingsUiItem {
121 SettingsUiItem::Single(SettingsUiItemSingle::SwitchField)
122 }
123}
124
125#[repr(u8)]
126#[derive(Clone, Copy, Debug, PartialEq, Eq)]
127pub enum NumType {
128 U64 = 0,
129 U32 = 1,
130 F32 = 2,
131}
132pub static NUM_TYPE_NAMES: std::sync::LazyLock<[&'static str; NumType::COUNT]> =
133 std::sync::LazyLock::new(|| NumType::ALL.map(NumType::type_name));
134pub static NUM_TYPE_IDS: std::sync::LazyLock<[TypeId; NumType::COUNT]> =
135 std::sync::LazyLock::new(|| NumType::ALL.map(NumType::type_id));
136
137impl NumType {
138 const COUNT: usize = 3;
139 const ALL: [NumType; Self::COUNT] = [NumType::U64, NumType::U32, NumType::F32];
140
141 pub fn type_id(self) -> TypeId {
142 match self {
143 NumType::U64 => TypeId::of::<u64>(),
144 NumType::U32 => TypeId::of::<u32>(),
145 NumType::F32 => TypeId::of::<f32>(),
146 }
147 }
148
149 pub fn type_name(self) -> &'static str {
150 match self {
151 NumType::U64 => std::any::type_name::<u64>(),
152 NumType::U32 => std::any::type_name::<u32>(),
153 NumType::F32 => std::any::type_name::<f32>(),
154 }
155 }
156}
157
158macro_rules! numeric_stepper_for_num_type {
159 ($type:ty, $num_type:ident) => {
160 impl SettingsUi for $type {
161 fn settings_ui_item() -> SettingsUiItem {
162 SettingsUiItem::Single(SettingsUiItemSingle::NumericStepper(NumType::$num_type))
163 }
164 }
165
166 impl SettingsUi for Option<$type> {
167 fn settings_ui_item() -> SettingsUiItem {
168 SettingsUiItem::Single(SettingsUiItemSingle::NumericStepper(NumType::$num_type))
169 }
170 }
171 };
172}
173
174numeric_stepper_for_num_type!(u64, U64);
175numeric_stepper_for_num_type!(u32, U32);
176// todo(settings_ui) is there a better ui for f32?
177numeric_stepper_for_num_type!(f32, F32);