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
82 let path = path.clone();
83 cx.background_spawn(async move {
84 rx.await?
85 .with_context(|| format!("Failed to update setting at path `{:?}`", path.join(".")))
86 })
87 .detach_and_log_err(cx);
88 }
89}
90
91impl<T: serde::Serialize> SettingsValue<T> {
92 pub fn write(
93 path: &SmallVec<[&'static str; 1]>,
94 value: T,
95 cx: &mut App,
96 ) -> Result<(), serde_json::Error> {
97 SettingsValue::write_value(path, serde_json::to_value(value)?, cx);
98 Ok(())
99 }
100}
101
102pub struct SettingsUiItemDynamic {
103 pub options: Vec<SettingsUiEntry>,
104 pub determine_option: fn(&serde_json::Value, &App) -> usize,
105}
106
107pub struct SettingsUiItemGroup {
108 pub items: Vec<SettingsUiEntry>,
109}
110
111pub enum SettingsUiItem {
112 Group(SettingsUiItemGroup),
113 Single(SettingsUiItemSingle),
114 Dynamic(SettingsUiItemDynamic),
115 None,
116}
117
118impl SettingsUi for bool {
119 fn settings_ui_item() -> SettingsUiItem {
120 SettingsUiItem::Single(SettingsUiItemSingle::SwitchField)
121 }
122}
123
124impl SettingsUi for Option<bool> {
125 fn settings_ui_item() -> SettingsUiItem {
126 SettingsUiItem::Single(SettingsUiItemSingle::SwitchField)
127 }
128}
129
130#[repr(u8)]
131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
132pub enum NumType {
133 U64 = 0,
134 U32 = 1,
135 F32 = 2,
136 USIZE = 3,
137}
138
139pub static NUM_TYPE_NAMES: std::sync::LazyLock<[&'static str; NumType::COUNT]> =
140 std::sync::LazyLock::new(|| NumType::ALL.map(NumType::type_name));
141pub static NUM_TYPE_IDS: std::sync::LazyLock<[TypeId; NumType::COUNT]> =
142 std::sync::LazyLock::new(|| NumType::ALL.map(NumType::type_id));
143
144impl NumType {
145 const COUNT: usize = 3;
146 const ALL: [NumType; Self::COUNT] = [NumType::U64, NumType::U32, NumType::F32];
147
148 pub fn type_id(self) -> TypeId {
149 match self {
150 NumType::U64 => TypeId::of::<u64>(),
151 NumType::U32 => TypeId::of::<u32>(),
152 NumType::F32 => TypeId::of::<f32>(),
153 NumType::USIZE => TypeId::of::<usize>(),
154 }
155 }
156
157 pub fn type_name(self) -> &'static str {
158 match self {
159 NumType::U64 => std::any::type_name::<u64>(),
160 NumType::U32 => std::any::type_name::<u32>(),
161 NumType::F32 => std::any::type_name::<f32>(),
162 NumType::USIZE => std::any::type_name::<usize>(),
163 }
164 }
165}
166
167macro_rules! numeric_stepper_for_num_type {
168 ($type:ty, $num_type:ident) => {
169 impl SettingsUi for $type {
170 fn settings_ui_item() -> SettingsUiItem {
171 SettingsUiItem::Single(SettingsUiItemSingle::NumericStepper(NumType::$num_type))
172 }
173 }
174
175 impl SettingsUi for Option<$type> {
176 fn settings_ui_item() -> SettingsUiItem {
177 SettingsUiItem::Single(SettingsUiItemSingle::NumericStepper(NumType::$num_type))
178 }
179 }
180 };
181}
182
183numeric_stepper_for_num_type!(u64, U64);
184numeric_stepper_for_num_type!(u32, U32);
185// todo(settings_ui) is there a better ui for f32?
186numeric_stepper_for_num_type!(f32, F32);
187numeric_stepper_for_num_type!(usize, USIZE);