1use anyhow::Context as _;
2use fs::Fs;
3use gpui::{AnyElement, App, AppContext as _, ReadGlobal as _, Window};
4use smallvec::SmallVec;
5
6use crate::SettingsStore;
7
8pub trait SettingsUi {
9 fn settings_ui_item() -> SettingsUiItem {
10 // todo(settings_ui): remove this default impl, only entry should have a default impl
11 // because it's expected that the macro or custom impl use the item and the known paths to create the entry
12 SettingsUiItem::None
13 }
14
15 fn settings_ui_entry() -> SettingsUiEntry {
16 SettingsUiEntry {
17 item: SettingsUiEntryVariant::None,
18 }
19 }
20}
21
22pub struct SettingsUiEntry {
23 // todo(settings_ui): move this back here once there isn't a None variant
24 // pub path: &'static str,
25 // pub title: &'static str,
26 pub item: SettingsUiEntryVariant,
27}
28
29pub enum SettingsUiEntryVariant {
30 Group {
31 path: &'static str,
32 title: &'static str,
33 items: Vec<SettingsUiEntry>,
34 },
35 Item {
36 path: &'static str,
37 item: SettingsUiItemSingle,
38 },
39 Dynamic {
40 path: &'static str,
41 options: Vec<SettingsUiEntry>,
42 determine_option: fn(&serde_json::Value, &mut App) -> usize,
43 },
44 // todo(settings_ui): remove
45 None,
46}
47
48pub enum SettingsUiItemSingle {
49 SwitchField,
50 NumericStepper,
51 ToggleGroup(&'static [&'static str]),
52 /// This should be used when toggle group size > 6
53 DropDown(&'static [&'static str]),
54 Custom(Box<dyn Fn(SettingsValue<serde_json::Value>, &mut Window, &mut App) -> AnyElement>),
55}
56
57pub struct SettingsValue<T> {
58 pub title: &'static str,
59 pub path: SmallVec<[&'static str; 1]>,
60 pub value: Option<T>,
61 pub default_value: T,
62}
63
64impl<T> SettingsValue<T> {
65 pub fn read(&self) -> &T {
66 match &self.value {
67 Some(value) => value,
68 None => &self.default_value,
69 }
70 }
71}
72
73impl SettingsValue<serde_json::Value> {
74 pub fn write_value(path: &SmallVec<[&'static str; 1]>, value: serde_json::Value, cx: &mut App) {
75 let settings_store = SettingsStore::global(cx);
76 let fs = <dyn Fs>::global(cx);
77
78 let rx = settings_store.update_settings_file_at_path(fs.clone(), path.as_slice(), value);
79 let path = path.clone();
80 cx.background_spawn(async move {
81 rx.await?
82 .with_context(|| format!("Failed to update setting at path `{:?}`", path.join(".")))
83 })
84 .detach_and_log_err(cx);
85 }
86}
87
88impl<T: serde::Serialize> SettingsValue<T> {
89 pub fn write(
90 path: &SmallVec<[&'static str; 1]>,
91 value: T,
92 cx: &mut App,
93 ) -> Result<(), serde_json::Error> {
94 SettingsValue::write_value(path, serde_json::to_value(value)?, cx);
95 Ok(())
96 }
97}
98
99pub enum SettingsUiItem {
100 Group {
101 title: &'static str,
102 items: Vec<SettingsUiEntry>,
103 },
104 Single(SettingsUiItemSingle),
105 Dynamic {
106 options: Vec<SettingsUiEntry>,
107 determine_option: fn(&serde_json::Value, &mut App) -> usize,
108 },
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
124impl SettingsUi for u64 {
125 fn settings_ui_item() -> SettingsUiItem {
126 SettingsUiItem::Single(SettingsUiItemSingle::NumericStepper)
127 }
128}