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