matchmaker/
action.rs

1use std::{mem::discriminant, str::FromStr};
2
3use serde::{Deserialize, Serialize, Serializer};
4
5#[derive(Debug, Clone, Deserialize, Default)]
6pub enum Action {
7    #[default] // used to satisfy enumstring
8    Select,
9    Deselect,
10    Toggle,
11    CycleAll,
12    Accept,
13    Quit(Exit),
14
15    // UI
16    CyclePreview,
17    Preview(String), // if match: hide, else match
18    Help(String), // content is shown in preview, empty for default help display
19    SwitchPreview(Option<u8>), // n => ^ but with layout + layout_cmd, None => just toggle visibility
20    SetPreview(Option<u8>), // n => set layout, None => set current layout cmd
21
22    ToggleWrap,
23    ToggleWrapPreview,
24
25    // Programmable
26    Execute(String),
27    Become(String),
28    Reload(String),
29    Print(String),
30
31    SetInput(String),
32    SetHeader(Option<String>),
33    SetFooter(Option<String>),
34    SetPrompt(Option<String>),
35    Column(usize),
36    CycleColumn,
37    // Unimplemented
38    HistoryUp,
39    HistoryDown,
40    ChangePrompt,
41    ChangeQuery,
42
43    // Edit
44    ForwardChar,
45    BackwardChar,
46    ForwardWord,
47    BackwardWord,
48    DeleteChar,
49    DeleteWord,
50    DeleteLineStart,
51    DeleteLineEnd,
52    Cancel,
53    InputPos(i32),
54
55    // Navigation
56    Up(Count),
57    Down(Count),
58    PreviewUp(Count),
59    PreviewDown(Count),
60    PreviewHalfPageUp,
61    PreviewHalfPageDown,
62    Pos(i32),
63
64    // Experimental/Debugging
65    Redraw,
66}
67
68impl serde::Serialize for Action {
69    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70    where
71    S: serde::Serializer,
72    {
73        serializer.serialize_str(&self.to_string())
74    }
75}
76
77// -----------------------------------------------------------------------------------------------------------------------
78
79
80impl PartialEq for Action {
81    fn eq(&self, other: &Self) -> bool {
82        discriminant(self) == discriminant(other)
83    }
84}
85
86impl Eq for Action {}
87
88impl std::hash::Hash for Action {
89    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
90        discriminant(self).hash(state);
91    }
92}
93
94#[derive(Debug, Clone, PartialEq)]
95pub struct Actions(pub Vec<Action>);
96
97impl<const N: usize> From<[Action; N]> for Actions {
98    fn from(arr: [Action; N]) -> Self {
99        Actions(arr.into())
100    }
101}
102
103impl<'de> Deserialize<'de> for Actions {
104    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
105    where
106    D: serde::Deserializer<'de>,
107    {
108        #[derive(Debug, Deserialize)]
109        #[serde(untagged)]
110        enum Helper {
111            Single(String),
112            Multiple(Vec<String>),
113        }
114
115        let helper = Helper::deserialize(deserializer)?;
116        let strings = match helper {
117            Helper::Single(s) => vec![s],
118            Helper::Multiple(v) => v,
119        };
120
121        let mut actions = Vec::with_capacity(strings.len());
122        for s in strings {
123            let action = Action::from_str(&s).map_err(serde::de::Error::custom)?;
124            actions.push(action);
125        }
126
127        Ok(Actions(actions))
128    }
129}
130
131impl Serialize for Actions {
132    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
133    where
134        S: Serializer,
135    {
136        match self.0.len() {
137            1 => serializer.serialize_str(&self.0[0].to_string()),
138            _ => {
139                let strings: Vec<String> = self.0.iter().map(|a| a.to_string()).collect();
140                strings.serialize(serializer)
141            }
142        }
143    }
144}
145
146macro_rules! impl_display_and_from_str_enum {
147    ($enum:ident,
148        $($unit:ident),*;
149        $($tuple:ident),*;
150        $($tuple_default:ident),*;
151        $($tuple_option:ident),*;
152        $($tuple_string_default:ident),*
153    ) => {
154        impl std::fmt::Display for $enum {
155            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156                match self {
157                    // Unit variants
158                    $( Self::$unit => write!(f, stringify!($unit)), )*
159
160                    // Tuple variants (always show inner)
161                    $( Self::$tuple(inner) => write!(f, concat!(stringify!($tuple), "({})"), inner), )*
162
163                    // Tuple variants with generic default fallback
164                    $( Self::$tuple_default(inner) => {
165                        if *inner == core::default::Default::default() {
166                            write!(f, stringify!($tuple_default))
167                        } else {
168                            write!(f, concat!(stringify!($tuple_default), "({})"), inner)
169                        }
170                    }, )*
171
172                    // Tuple variants with Option<T>
173                    $( Self::$tuple_option(opt) => {
174                        if let Some(inner) = opt {
175                            write!(f, concat!(stringify!($tuple_option), "({})"), inner)
176                        } else {
177                            write!(f, stringify!($tuple_option))
178                        }
179                    }, )*
180
181                    $( Self::$tuple_string_default(inner) => {
182                        if inner.is_empty() {
183                            write!(f, stringify!($tuple_string_default))
184                        } else {
185                            write!(f, concat!(stringify!($tuple_string_default), "({})"), inner)
186                        }
187                    }, )*
188                }
189            }
190        }
191
192        impl std::str::FromStr for $enum {
193            type Err = String;
194
195            fn from_str(s: &str) -> Result<Self, Self::Err> {
196                let (name, data) = if let Some(pos) = s.find('(') {
197                    if s.ends_with(')') {
198                        (&s[..pos], Some(&s[pos + 1..s.len() - 1]))
199                    } else {
200                        (s, None)
201                    }
202                } else {
203                    (s, None)
204                };
205
206                match name {
207                    $( stringify!($unit) => Ok(Self::$unit), )*
208
209                    $( stringify!($tuple) => {
210                        let d = data
211                            .ok_or_else(|| format!("Missing data for {}", stringify!($tuple)))?
212                            .parse()
213                            .map_err(|_| format!("Invalid data for {}", stringify!($tuple)))?;
214                        Ok(Self::$tuple(d))
215                    }, )*
216
217                    $( stringify!($tuple_default) => {
218                        let d = match data {
219                            Some(val) => val.parse()
220                                .map_err(|_| format!("Invalid data for {}", stringify!($tuple_default)))?,
221                            None => Default::default(),
222                        };
223                        Ok(Self::$tuple_default(d))
224                    }, )*
225
226                    $( stringify!($tuple_option) => {
227                        let d = match data {
228                            Some(val) if !val.is_empty() => {
229                                Some(val.parse().map_err(|_| format!("Invalid data for {}", stringify!($tuple_option)))?)
230                            }
231                            _ => None,
232                        };
233                        Ok(Self::$tuple_option(d))
234                    }, )*
235
236                    $( stringify!($tuple_string_default) => {
237                        let d = match data {
238                            Some(val) if !val.is_empty() => val.to_string(),
239                            _ => String::new(),
240                        };
241                        Ok(Self::$tuple_string_default(d))
242                    }, )*
243
244                    _ => Err(format!("Unknown variant {}", name)),
245                }
246            }
247        }
248    };
249}
250
251// call it like:
252impl_display_and_from_str_enum!(
253    Action,
254    Select, Deselect, Toggle, CycleAll, Accept, CyclePreview, CycleColumn,
255    PreviewHalfPageUp, PreviewHalfPageDown, HistoryUp, HistoryDown,
256    ChangePrompt, ChangeQuery, ToggleWrap, ToggleWrapPreview, ForwardChar,
257    BackwardChar, ForwardWord, BackwardWord, DeleteChar, DeleteWord,
258    DeleteLineStart, DeleteLineEnd, Cancel, Redraw;
259    // tuple variants
260    Execute, Become, Reload, Print, Preview, SetInput, Column, Pos, InputPos;
261    // tuple with default
262    Up, Down, PreviewUp, PreviewDown, Quit;
263    // tuple with option
264    SwitchPreview, SetPreview, SetPrompt, SetHeader, SetFooter;
265    //
266    Help
267);
268
269use crate::{impl_int_wrapper};
270impl_int_wrapper!(Exit, i32, 1);
271impl_int_wrapper!(Count, u16, 1);
272