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