reovim_plugin_settings_menu/settings_menu/
item.rs1use reovim_core::option::{OptionConstraint, OptionSpec, OptionValue};
4
5#[derive(Debug, Clone)]
7pub struct SettingSection {
8 pub name: String,
10 pub items: Vec<SettingItem>,
12}
13
14#[derive(Debug, Clone)]
16pub struct SettingItem {
17 pub key: String,
21 pub label: String,
23 pub description: Option<String>,
25 pub value: SettingValue,
27}
28
29#[derive(Debug, Clone)]
31pub enum SettingValue {
32 Bool(bool),
34 Choice {
36 options: Vec<String>,
37 selected: usize,
38 },
39 Number {
41 value: i32,
42 min: i32,
43 max: i32,
44 step: i32,
45 },
46 Display(String),
48 Action(ActionType),
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum ActionType {
55 SaveProfile,
56 LoadProfile,
57 ResetToDefault,
58}
59
60#[derive(Debug, Clone)]
62pub enum FlatItem {
63 SectionHeader(String),
65 Setting { section_idx: usize, item_idx: usize },
67}
68
69impl SettingValue {
70 pub const fn toggle(&mut self) {
72 if let Self::Bool(b) = self {
73 *b = !*b;
74 }
75 }
76
77 pub const fn cycle_next(&mut self) {
79 if let Self::Choice { options, selected } = self {
80 *selected = (*selected + 1) % options.len();
81 }
82 }
83
84 pub const fn cycle_prev(&mut self) {
86 if let Self::Choice { options, selected } = self {
87 if *selected == 0 {
88 *selected = options.len() - 1;
89 } else {
90 *selected -= 1;
91 }
92 }
93 }
94
95 pub const fn quick_select(&mut self, index: u8) {
97 if let Self::Choice { options, selected } = self {
98 let idx = (index as usize).saturating_sub(1);
99 if idx < options.len() {
100 *selected = idx;
101 }
102 }
103 }
104
105 pub fn increment(&mut self) {
107 if let Self::Number {
108 value, max, step, ..
109 } = self
110 {
111 *value = (*value + *step).min(*max);
112 }
113 }
114
115 pub fn decrement(&mut self) {
117 if let Self::Number {
118 value, min, step, ..
119 } = self
120 {
121 *value = (*value - *step).max(*min);
122 }
123 }
124
125 #[must_use]
127 pub fn display_value(&self) -> String {
128 match self {
129 Self::Bool(b) => if *b { "on" } else { "off" }.to_string(),
130 Self::Choice { options, selected } => {
131 options.get(*selected).cloned().unwrap_or_default()
132 }
133 Self::Number { value, .. } => value.to_string(),
134 Self::Display(s) => s.clone(),
135 Self::Action(_) => String::new(),
136 }
137 }
138
139 #[must_use]
141 pub const fn is_bool(&self) -> bool {
142 matches!(self, Self::Bool(_))
143 }
144
145 #[must_use]
147 pub const fn is_choice(&self) -> bool {
148 matches!(self, Self::Choice { .. })
149 }
150
151 #[must_use]
153 pub const fn is_number(&self) -> bool {
154 matches!(self, Self::Number { .. })
155 }
156
157 #[must_use]
159 pub const fn is_action(&self) -> bool {
160 matches!(self, Self::Action(_))
161 }
162
163 #[must_use]
165 pub const fn is_display(&self) -> bool {
166 matches!(self, Self::Display(_))
167 }
168
169 #[must_use]
171 pub const fn choice_count(&self) -> Option<usize> {
172 if let Self::Choice { options, .. } = self {
173 Some(options.len())
174 } else {
175 None
176 }
177 }
178}
179
180impl FlatItem {
181 #[must_use]
183 pub const fn is_header(&self) -> bool {
184 matches!(self, Self::SectionHeader(_))
185 }
186
187 #[must_use]
189 pub const fn is_setting(&self) -> bool {
190 matches!(self, Self::Setting { .. })
191 }
192}
193
194impl From<&OptionValue> for SettingValue {
197 fn from(opt: &OptionValue) -> Self {
202 match opt {
203 OptionValue::Bool(b) => Self::Bool(*b),
204 OptionValue::Integer(i) => Self::Number {
205 value: *i as i32,
206 min: i32::MIN,
207 max: i32::MAX,
208 step: 1,
209 },
210 OptionValue::String(s) => Self::Display(s.clone()),
211 OptionValue::Choice { value, choices } => Self::Choice {
212 options: choices.clone(),
213 selected: choices.iter().position(|c| c == value).unwrap_or(0),
214 },
215 }
216 }
217}
218
219impl SettingValue {
220 #[must_use]
224 pub fn from_option_with_constraint(opt: &OptionValue, constraint: &OptionConstraint) -> Self {
225 match opt {
226 OptionValue::Bool(b) => Self::Bool(*b),
227 OptionValue::Integer(i) => Self::Number {
228 value: *i as i32,
229 min: constraint.min.map(|v| v as i32).unwrap_or(i32::MIN),
230 max: constraint.max.map(|v| v as i32).unwrap_or(i32::MAX),
231 step: 1,
232 },
233 OptionValue::String(s) => Self::Display(s.clone()),
234 OptionValue::Choice { value, choices } => Self::Choice {
235 options: choices.clone(),
236 selected: choices.iter().position(|c| c == value).unwrap_or(0),
237 },
238 }
239 }
240
241 #[must_use]
243 pub fn item_from_spec(spec: &OptionSpec, value: &OptionValue) -> SettingItem {
244 SettingItem {
245 key: spec.name.to_string(),
246 label: spec.description.to_string(),
247 description: Some(spec.description.to_string()),
248 value: Self::from_option_with_constraint(value, &spec.constraint),
249 }
250 }
251
252 #[must_use]
256 pub fn to_option_value(&self) -> Option<OptionValue> {
257 match self {
258 Self::Bool(b) => Some(OptionValue::Bool(*b)),
259 Self::Number { value, .. } => Some(OptionValue::Integer(i64::from(*value))),
260 Self::Choice { options, selected } => {
261 let value = options.get(*selected).cloned().unwrap_or_default();
262 Some(OptionValue::Choice {
263 value,
264 choices: options.clone(),
265 })
266 }
267 Self::Display(_) | Self::Action(_) => None,
268 }
269 }
270}