matchmaker/
action.rs

1use std::{fmt::{self, Debug, Display}, mem::discriminant, str::FromStr};
2
3use cli_boilerplate_automation::impl_transparent_wrapper;
4use serde::{Deserialize, Serialize, Serializer};
5
6use crate::{MAX_ACTIONS, SSS, utils::serde::StringOrVec, render::{Effects, MMState}};
7
8#[derive(Debug, Clone, Default)]
9pub enum Action<A: ActionExt = NullActionExt> {
10    #[default] // used to satisfy enumstring
11    /// Add item to selections
12    Select,
13    /// Remove item from selections
14    Deselect,
15    /// Toggle item in selections
16    Toggle,
17    CycleAll,
18    ClearAll,
19    Accept,
20    // Returns MatchError::Abort
21    Quit(Exit),
22
23    // UI
24    CyclePreview,
25    Preview(String), // if match: hide, else match
26    Help(String), // content is shown in preview, empty for default help display
27    SwitchPreview(Option<u8>), // n => ^ but with layout + layout_cmd, None => just toggle visibility
28    SetPreview(Option<u8>), // n => set layout, None => set current layout cmd
29
30    ToggleWrap,
31    ToggleWrapPreview,
32
33    // Programmable
34    /// Pauses the tui display and the event loop, and invokes the handler for [`crate::message::Interrupt::Execute`]
35    /// The remaining actions in the buffer are still processed
36    Execute(String),
37    /// Exits the tui and invokes the handler for [`crate::message::Interrupt::Become`]
38    Become(String),
39    /// Restarts the matcher-worker and invokes the handler for [`crate::message::Interrupt::Reload`]
40    Reload(String),
41
42    /// Invokes the handler for [`crate::message::Interrupt::Print`]
43    /// See also: [`crate::Matchmaker::register_print_handler`]
44    Print(String),
45
46    SetInput(String),
47    SetHeader(Option<String>),
48    SetFooter(Option<String>),
49    SetPrompt(Option<String>),
50    Column(usize),
51    CycleColumn,
52
53    // Unimplemented
54    HistoryUp,
55    HistoryDown,
56    ChangePrompt,
57    ChangeQuery,
58
59    // Edit
60    ForwardChar,
61    BackwardChar,
62    ForwardWord,
63    BackwardWord,
64    DeleteChar,
65    DeleteWord,
66    DeleteLineStart,
67    DeleteLineEnd,
68    Cancel, // clear input
69    InputPos(i32),
70
71    // Navigation
72    Up(Count),
73    Down(Count),
74    PreviewUp(Count),
75    PreviewDown(Count),
76    PreviewHalfPageUp,
77    PreviewHalfPageDown,
78    Pos(i32),
79
80    // Other/Experimental/Debugging
81    Redraw,
82    Custom(A),
83    Overlay(usize)
84}
85
86impl<A: ActionExt> serde::Serialize for Action<A> {
87    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
88    where
89    S: serde::Serializer,
90    {
91        serializer.serialize_str(&self.to_string())
92    }
93}
94
95impl<A: ActionExt> PartialEq for Action<A> {
96    fn eq(&self, other: &Self) -> bool {
97        discriminant(self) == discriminant(other)
98    }
99}
100impl<A: ActionExt> Eq for Action<A> {}
101
102// --------- ACTION_EXT ------------------
103#[derive(Debug, Clone, Default, PartialEq)]
104pub struct NullActionExt {}
105
106impl ActionExt for NullActionExt {}
107
108impl fmt::Display for NullActionExt {
109    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
110        Ok(())
111    }
112}
113
114impl std::str::FromStr for NullActionExt {
115    type Err = ();
116
117    fn from_str(_: &str) -> Result<Self, Self::Err> {
118        Err(())
119    }
120}
121
122pub trait ActionExt: Debug + Clone + FromStr + Display + PartialEq + SSS
123{}
124
125pub type ActionExtHandler<T, S, A> = fn(A, &mut MMState<'_, T, S>) -> Effects;
126pub type ActionAliaser<T, S, A> = fn(Action<A>, &MMState<'_, T, S>) -> Actions<A>;
127pub use arrayvec::ArrayVec;
128/// # Example
129/// ```rust
130///     use matchmaker::{action::{Action, Actions, acs}, render::MMState};
131///     pub fn fsaction_aliaser(
132///         a: Action,
133///         state: &MMState<'_, String, String>,
134///     ) -> Actions {
135///         match a {
136///             Action::Custom(_) => {
137///               log::debug!("Ignoring custom action");
138///               acs![]
139///             }
140///             _ => acs![a], // no change
141///         }
142///     }
143/// ```
144#[macro_export]
145macro_rules! acs {
146    ( $( $x:expr ),* $(,)? ) => {
147        {
148            $crate::action::Actions::from([$($x),*])
149        }
150    };
151}
152pub use crate::acs;
153
154/// # Example
155/// ```rust
156///     use matchmaker::{binds::{BindMap, bindmap, key}, action::Action};
157///     let default_config: BindMap = bindmap!(
158///        key!(alt-enter) => Action::Print("".into())
159///        // custom actions can be specified directly: key!(ctrl-c) => FsAction::Enter
160///    );
161/// ```
162#[macro_export]
163macro_rules! bindmap {
164    ( $( $k:expr => $v1:expr ),* $(,)? ) => {{
165        let mut map = $crate::binds::BindMap::new();
166        $(
167            map.insert($k.into(), $crate::action::Actions::from($v1));
168        )*
169        map
170    }};
171}
172// ----------- ACTIONS ---------------
173#[derive(Debug, Clone, PartialEq)]
174pub struct Actions<A: ActionExt = NullActionExt>(pub ArrayVec<Action<A>, MAX_ACTIONS>);
175impl<A: ActionExt> Default for Actions<A> {
176    fn default() -> Self {
177        Self(ArrayVec::new())
178    }
179}
180
181
182macro_rules! repeat_impl {
183    ($($len:expr),*) => {
184        $(
185            impl<A: ActionExt> From<[Action<A>; $len]> for Actions<A> {
186                fn from(arr: [Action<A>; $len]) -> Self {
187                    Actions(ArrayVec::from_iter(arr))
188                }
189            }
190
191            impl<A: ActionExt> From<[A; $len]> for Actions<A> {
192                fn from(arr: [A; $len]) -> Self {
193                    Actions(arr.into_iter().map(Action::Custom).collect())
194                }
195            }
196        )*
197    }
198}
199impl<A: ActionExt> From<[Action<A>; 0]> for Actions<A> {
200    fn from(empty: [Action<A>; 0]) -> Self {
201        Actions(ArrayVec::from_iter(empty))
202    }
203}
204repeat_impl!(1, 2, 3, 4, 5, 6);
205
206impl<A: ActionExt> From<Action<A>> for Actions<A> {
207    fn from(action: Action<A>) -> Self {
208        acs![action]
209    }
210}
211// no conflict because Action is local type
212impl<A: ActionExt> From<A> for Actions<A> {
213    fn from(action: A) -> Self {
214        acs![Action::Custom(action)]
215    }
216}
217
218// ---------- SERDE ----------------
219
220impl<'de, A: ActionExt> Deserialize<'de> for Actions<A> {
221    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222    where
223    D: serde::Deserializer<'de>,
224    {
225        let helper = StringOrVec::deserialize(deserializer)?;
226        let strings = match helper {
227            StringOrVec::String(s) => vec![s],
228            StringOrVec::Vec(v) => v,
229        };
230
231        if strings.len() > MAX_ACTIONS {
232            return Err(
233                serde::de::Error::custom(format!("Too many actions, max is {MAX_ACTIONS}.")
234            )
235            );
236        }
237
238        let mut actions = ArrayVec::new();
239        for s in strings {
240            let action = Action::from_str(&s).map_err(serde::de::Error::custom)?;
241            actions.push(action);
242        }
243
244        Ok(Actions(actions))
245    }
246}
247
248impl<A: ActionExt> Serialize for Actions<A> {
249    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
250    where
251    S: Serializer,
252    {
253        match self.0.len() {
254            1 => serializer.serialize_str(&self.0[0].to_string()),
255            _ => {
256                let strings: Vec<String> = self.0.iter().map(|a| a.to_string()).collect();
257                strings.serialize(serializer)
258            }
259        }
260    }
261}
262
263// ----- action serde
264
265macro_rules! impl_display_and_from_str_enum {
266    (
267        $($unit:ident),*;
268        $($tuple:ident),*;
269        $($tuple_default:ident),*;
270        $($tuple_option:ident),*;
271        $($tuple_string_default:ident),*
272    ) => {
273        impl<A: ActionExt> std::fmt::Display for Action<A> {
274            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275                match self {
276                    // Unit variants
277                    $( Self::$unit => write!(f, stringify!($unit)), )*
278
279                    // Tuple variants (always show inner)
280                    $( Self::$tuple(inner) => write!(f, concat!(stringify!($tuple), "({})"), inner), )*
281
282                    // Tuple variants with generic default fallback
283                    $( Self::$tuple_default(inner) => {
284                        if *inner == core::default::Default::default() {
285                            write!(f, stringify!($tuple_default))
286                        } else {
287                            write!(f, concat!(stringify!($tuple_default), "({})"), inner)
288                        }
289                    }, )*
290
291                    // Tuple variants with Option<T>
292                    $( Self::$tuple_option(opt) => {
293                        if let Some(inner) = opt {
294                            write!(f, concat!(stringify!($tuple_option), "({})"), inner)
295                        } else {
296                            write!(f, stringify!($tuple_option))
297                        }
298                    }, )*
299
300                    $( Self::$tuple_string_default(inner) => {
301                        if inner.is_empty() {
302                            write!(f, stringify!($tuple_string_default))
303                        } else {
304                            write!(f, concat!(stringify!($tuple_string_default), "({})"), inner)
305                        }
306                    }, )*
307
308                    Self::Custom(inner) => {
309                        write!(f, "{}", inner.to_string())
310                    }
311                }
312            }
313        }
314
315        impl<A: ActionExt>  std::str::FromStr for Action<A> {
316            type Err = String;
317
318            fn from_str(s: &str) -> Result<Self, Self::Err> {
319                let (name, data) = if let Some(pos) = s.find('(') {
320                    if s.ends_with(')') {
321                        (&s[..pos], Some(&s[pos + 1..s.len() - 1]))
322                    } else {
323                        (s, None)
324                    }
325                } else {
326                    (s, None)
327                };
328
329                if let Ok(x) = name.parse::<A>() {
330                    return Ok(Self::Custom(x))
331                }
332                match name {
333                    $( stringify!($unit) => {
334                        if data.is_some() {
335                            Err(format!("Unexpected data for unit variant {}", name))
336                        } else
337                        {
338                            Ok(Self::$unit)
339                        }
340                    }, )*
341
342                    $( stringify!($tuple) => {
343                        let d = data
344                        .ok_or_else(|| format!("Missing data for {}", stringify!($tuple)))?
345                        .parse()
346                        .map_err(|_| format!("Invalid data for {}", stringify!($tuple)))?;
347                        Ok(Self::$tuple(d))
348                    }, )*
349
350                    $( stringify!($tuple_default) => {
351                        let d = match data {
352                            Some(val) => val.parse()
353                            .map_err(|_| format!("Invalid data for {}", stringify!($tuple_default)))?,
354                            None => Default::default(),
355                        };
356                        Ok(Self::$tuple_default(d))
357                    }, )*
358
359                    $( stringify!($tuple_option) => {
360                        let d = match data {
361                            Some(val) if !val.is_empty() => {
362                                Some(val.parse().map_err(|_| format!("Invalid data for {}", stringify!($tuple_option)))?)
363                            }
364                            _ => None,
365                        };
366                        Ok(Self::$tuple_option(d))
367                    }, )*
368
369                    $( stringify!($tuple_string_default) => {
370                        let d = match data {
371                            Some(val) if !val.is_empty() => val.to_string(),
372                            _ => String::new(),
373                        };
374                        Ok(Self::$tuple_string_default(d))
375                    }, )*
376
377                    _ => Err(format!("Unknown variant {}", s))
378                }
379            }
380        }
381    };
382}
383
384// call it like:
385impl_display_and_from_str_enum!(
386    Select, Deselect, Toggle, CycleAll, ClearAll, Accept, CyclePreview, CycleColumn,
387    PreviewHalfPageUp, PreviewHalfPageDown, HistoryUp, HistoryDown,
388    ChangePrompt, ChangeQuery, ToggleWrap, ToggleWrapPreview, ForwardChar,
389    BackwardChar, ForwardWord, BackwardWord, DeleteChar, DeleteWord,
390    DeleteLineStart, DeleteLineEnd, Cancel, Redraw;
391    // tuple variants
392    Execute, Become, Reload, Preview, SetInput, Column, Pos, InputPos;
393    // tuple with default
394    Up, Down, PreviewUp, PreviewDown, Quit, Overlay;
395    // tuple with option
396    SwitchPreview, SetPreview, SetPrompt, SetHeader, SetFooter;
397    // tuple_string_default
398    Print, Help
399);
400
401impl_transparent_wrapper!(Exit, i32, 1);
402impl_transparent_wrapper!(Count, u16, 1; derive(Copy));
403