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