settings_ui_core.rs

  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);