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