Skip to main content

matchmaker/
action.rs

1use std::{
2    fmt::{self, Debug, Display},
3    str::FromStr,
4};
5
6use serde::{Deserialize, Serialize, Serializer};
7
8use crate::{MAX_ACTIONS, SSS, utils::serde::StringOrVec};
9
10/// Bindable actions
11/// # Additional
12/// See [crate::render::render_loop] for the source code definitions.
13#[derive(Debug, Clone, PartialEq)]
14pub enum Action<A: ActionExt = NullActionExt> {
15    /// Add item to selections
16    Select,
17    /// Remove item from selections
18    Deselect,
19    /// Toggle item in selections
20    Toggle,
21    /// Toggle all selections
22    CycleAll,
23    /// Clear all selections
24    ClearSelections,
25    /// Accept current selection
26    Accept,
27    /// Quit with code
28    Quit(i32),
29
30    // UI
31    /// Toggle wrap
32    ToggleWrap,
33
34    // Preview
35    /// Cycle preview layouts
36    CyclePreview,
37    /// Show/hide preview for selection
38    Preview(String),
39    /// Show help in preview
40    Help(String),
41    /// Set preview layout;
42    /// None restores the command of the current layout.
43    SetPreview(Option<u8>),
44    /// Switch or toggle preview;
45    SwitchPreview(Option<u8>),
46    /// Toggle wrap in preview
47    ToggleWrapPreview,
48    /// Horizontally scroll either results or preview based on mouse location and wrapping configuration. (unimplemented)
49    /// 0 to reset.
50    HScroll(i8),
51
52    // experimental
53    /// Persistent horizontal scroll
54    /// 0 to reset.
55    PreviewHScroll(i8),
56    /// Persistent single-line vertical scroll
57    /// 0 to reset.
58    PreviewScroll(i8),
59    /// Jump between start, end, last, and initial locations. (unimplemented).
60    PreviewJump,
61
62    // Set
63    /// Set input query
64    SetInput(String),
65    /// Set header
66    SetHeader(Option<String>),
67    /// Set footer
68    SetFooter(Option<String>),
69    /// Set prompt
70    SetPrompt(Option<String>),
71
72    // Columns
73    /// Set column
74    Column(usize),
75    /// Cycle columns
76    CycleColumn,
77    // todo
78    ColumnLeft,
79    ColumnRight,
80    ScrollLeft,
81    ScrollRight,
82
83    // Programmable
84    /// Execute command and continue
85    Execute(String),
86    /// Exit and become
87    Become(String),
88    /// Reload matcher/worker
89    Reload(String),
90    /// Print via handler
91    Print(String),
92
93    // Unimplemented
94    /// History up (TODO)
95    HistoryUp,
96    /// History down (TODO)
97    HistoryDown,
98    /// Change prompt (TODO)
99    ChangePrompt,
100    /// Change query (TODO)
101    ChangeQuery,
102
103    // Edit (Input)
104    /// Move cursor forward char
105    ForwardChar,
106    /// Move cursor backward char
107    BackwardChar,
108    /// Move cursor forward word
109    ForwardWord,
110    /// Move cursor backward word
111    BackwardWord,
112    /// Delete char
113    DeleteChar,
114    /// Delete word
115    DeleteWord,
116    /// Delete to start of line
117    DeleteLineStart,
118    /// Delete to end of line
119    DeleteLineEnd,
120    /// Clear input
121    Cancel,
122    /// Set input cursor pos
123    InputPos(i32),
124
125    // Navigation
126    /// Move selection index up
127    Up(u16),
128    /// Move selection index down
129    Down(u16),
130    /// Scroll preview up
131    PreviewUp(u16),
132    /// Scroll preview down
133    PreviewDown(u16),
134    /// Scroll preview half page up
135    PreviewHalfPageUp,
136    /// Scroll preview half page down
137    PreviewHalfPageDown,
138    /// Jump to absolute position
139    Pos(i32),
140
141    // Other/Experimental/Debugging
142    /// Insert char into input
143    Input(char),
144    /// Force redraw
145    Redraw,
146    /// Custom action
147    Custom(A),
148    /// Activate the nth overlay
149    Overlay(usize),
150}
151
152// --------------- MACROS ---------------
153
154/// # Example
155/// ```rust
156///     use matchmaker::{action::{Action, Actions, acs}, render::MMState};
157///     pub fn fsaction_aliaser(
158///         a: Action,
159///         state: &MMState<'_, '_, String, String>,
160///     ) -> Actions {
161///         match a {
162///             Action::Custom(_) => {
163///               log::debug!("Ignoring custom action");
164///               acs![]
165///             }
166///             _ => acs![a], // no change
167///         }
168///     }
169/// ```
170#[macro_export]
171macro_rules! acs {
172    ( $( $x:expr ),* $(,)? ) => {
173        {
174            $crate::action::Actions::from([$($x),*])
175        }
176    };
177}
178pub use crate::acs;
179
180/// # Example
181/// ```rust
182/// #[derive(Debug, Clone, PartialEq)]
183/// pub enum FsAction {
184///    Filters
185/// }
186///
187/// use matchmaker::{binds::{BindMap, bindmap, key}, action::Action};
188/// let default_config: BindMap<FsAction> = bindmap!(
189///    key!(alt-enter) => Action::Print("".into()),
190///    key!(alt-f), key!(ctrl-shift-f) => FsAction::Filters, // custom actions can be specified directly
191/// );
192/// ```
193#[macro_export]
194macro_rules! bindmap {
195    ( $( $( $k:expr ),+ => $v:expr ),* $(,)? ) => {{
196        let mut map = $crate::binds::BindMap::new();
197        $(
198            let action = $crate::action::Actions::from($v);
199            $(
200                map.insert($k.into(), action.clone());
201            )+
202        )*
203        map
204    }};
205} // btw, Can't figure out if its possible to support optional meta over inserts
206
207// --------------- ACTION_EXT ---------------
208
209pub trait ActionExt: Debug + Clone + PartialEq + SSS {}
210impl<T: Debug + Clone + PartialEq + SSS> ActionExt for T {}
211
212impl<T> From<T> for Action<T>
213where
214    T: ActionExt,
215{
216    fn from(value: T) -> Self {
217        Self::Custom(value)
218    }
219}
220#[derive(Debug, Clone, Default, PartialEq)]
221pub struct NullActionExt {}
222
223impl fmt::Display for NullActionExt {
224    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
225        Ok(())
226    }
227}
228
229impl std::str::FromStr for NullActionExt {
230    type Err = ();
231
232    fn from_str(_: &str) -> Result<Self, Self::Err> {
233        Err(())
234    }
235}
236
237// --------------- ACTIONS ---------------
238pub use arrayvec::ArrayVec;
239
240#[derive(Debug, Default, Clone, PartialEq)]
241pub struct Actions<A: ActionExt = NullActionExt>(ArrayVec<Action<A>, MAX_ACTIONS>);
242
243macro_rules! repeat_impl {
244    ($($len:expr),*) => {
245        $(
246            impl<A: ActionExt> From<[Action<A>; $len]> for Actions<A> {
247                fn from(arr: [Action<A>; $len]) -> Self {
248                    Actions(ArrayVec::from_iter(arr))
249                }
250            }
251
252            impl<A: ActionExt> From<[A; $len]> for Actions<A> {
253                fn from(arr: [A; $len]) -> Self {
254                    Actions(arr.into_iter().map(Action::Custom).collect())
255                }
256            }
257        )*
258    }
259}
260impl<A: ActionExt> From<[Action<A>; 0]> for Actions<A> {
261    fn from(empty: [Action<A>; 0]) -> Self {
262        Actions(ArrayVec::from_iter(empty))
263    }
264}
265repeat_impl!(1, 2, 3, 4, 5, 6);
266
267impl<A: ActionExt> From<Action<A>> for Actions<A> {
268    fn from(action: Action<A>) -> Self {
269        acs![action]
270    }
271}
272// no conflict because Action is local type
273impl<A: ActionExt> From<A> for Actions<A> {
274    fn from(action: A) -> Self {
275        acs![Action::Custom(action)]
276    }
277}
278
279// ---------- SERDE ----------------
280
281impl<A: ActionExt + Display> serde::Serialize for Action<A> {
282    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
283    where
284        S: serde::Serializer,
285    {
286        serializer.serialize_str(&self.to_string())
287    }
288}
289
290impl<'de, A: ActionExt + FromStr> Deserialize<'de> for Actions<A> {
291    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
292    where
293        D: serde::Deserializer<'de>,
294    {
295        let helper = StringOrVec::deserialize(deserializer)?;
296        let strings = match helper {
297            StringOrVec::String(s) => vec![s],
298            StringOrVec::Vec(v) => v,
299        };
300
301        if strings.len() > MAX_ACTIONS {
302            return Err(serde::de::Error::custom(format!(
303                "Too many actions, max is {MAX_ACTIONS}."
304            )));
305        }
306
307        let mut actions = ArrayVec::new();
308        for s in strings {
309            let action = Action::from_str(&s).map_err(serde::de::Error::custom)?;
310            actions.push(action);
311        }
312
313        Ok(Actions(actions))
314    }
315}
316
317impl<A: ActionExt + Display> Serialize for Actions<A> {
318    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
319    where
320        S: Serializer,
321    {
322        match self.0.len() {
323            1 => serializer.serialize_str(&self.0[0].to_string()),
324            _ => {
325                let strings: Vec<String> = self.0.iter().map(|a| a.to_string()).collect();
326                strings.serialize(serializer)
327            }
328        }
329    }
330}
331
332// ----- action serde
333enum_from_str_display!(
334    units:
335    Select, Deselect, Toggle, CycleAll, ClearSelections, Accept, CyclePreview, CycleColumn, ScrollLeft, ScrollRight, ColumnLeft, ColumnRight, PreviewJump,
336    PreviewHalfPageUp, PreviewHalfPageDown, HistoryUp, HistoryDown,
337    ChangePrompt, ChangeQuery, ToggleWrap, ToggleWrapPreview, ForwardChar,
338    BackwardChar, ForwardWord, BackwardWord, DeleteChar, DeleteWord,
339    DeleteLineStart, DeleteLineEnd, Cancel, Redraw;
340
341    tuples:
342    Execute, Become, Reload, Preview, SetInput, Column, Pos, InputPos;
343
344    defaults:
345    (Up, 1), (Down, 1), (PreviewUp, 1), (PreviewDown, 1), (Quit, 1), (Overlay, 0), (Print, String::new()), (Help, String::new()), (PreviewScroll, 1), (PreviewHScroll, 1), (HScroll, 0);
346
347    options:
348    SwitchPreview, SetPreview, SetPrompt, SetHeader, SetFooter
349);
350
351macro_rules! enum_from_str_display {
352    (
353        units: $($unit:ident),*;
354        tuples: $($tuple:ident),*;
355        defaults: $(($default:ident, $default_value:expr)),*;
356        options: $($optional:ident),*
357    ) => {
358        impl<A: ActionExt + Display> std::fmt::Display for Action<A> {
359            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360                match self {
361                    $( Self::$unit => write!(f, stringify!($unit)), )*
362
363                    $( Self::$tuple(inner) => write!(f, concat!(stringify!($tuple), "({})"), inner), )*
364
365                    $( Self::$default(inner) => {
366                        if *inner == $default_value {
367                            write!(f, stringify!($default))
368                        } else {
369                            write!(f, concat!(stringify!($default), "({})"), inner)
370                        }
371                    }, )*
372
373                    $( Self::$optional(opt) => {
374                        if let Some(inner) = opt {
375                            write!(f, concat!(stringify!($optional), "({})"), inner)
376                        } else {
377                            write!(f, stringify!($optional))
378                        }
379                    }, )*
380
381                    Self::Custom(inner) => {
382                        write!(f, "{}", inner.to_string())
383                    }
384                    Self::Input(c) => {
385                        write!(f, "{c}")
386                    }
387                }
388            }
389        }
390
391        impl<A: ActionExt + FromStr> std::str::FromStr for Action<A> {
392            type Err = String;
393
394            fn from_str(s: &str) -> Result<Self, Self::Err> {
395                let (name, data) = if let Some(pos) = s.find('(') {
396                    if s.ends_with(')') {
397                        (&s[..pos], Some(&s[pos + 1..s.len() - 1]))
398                    } else {
399                        (s, None)
400                    }
401                } else {
402                    (s, None)
403                };
404
405                if let Ok(x) = name.parse::<A>() {
406                    return Ok(Self::Custom(x))
407                }
408                match name {
409                    $( n if n.eq_ignore_ascii_case(stringify!($unit)) => {
410                        if data.is_some() {
411                            Err(format!("Unexpected data for unit variant {}", name))
412                        } else {
413                            Ok(Self::$unit)
414                        }
415                    }, )*
416
417                    $( n if n.eq_ignore_ascii_case(stringify!($tuple)) => {
418                        let d = data
419                        .ok_or_else(|| format!("Missing data for {}", stringify!($tuple)))?
420                        .parse()
421                        .map_err(|_| format!("Invalid data for {}", stringify!($tuple)))?;
422                        Ok(Self::$tuple(d))
423                    }, )*
424
425                    $( n if n.eq_ignore_ascii_case(stringify!($default)) => {
426                        let d = match data {
427                            Some(val) => val
428                            .parse()
429                            .map_err(|_| format!("Invalid data for {}", stringify!($default)))?,
430                            None => $default_value,
431                        };
432                        Ok(Self::$default(d))
433                    }, )*
434
435                    $( n if n.eq_ignore_ascii_case(stringify!($optional)) => {
436                        let d = match data {
437                            Some(val) if !val.is_empty() => {
438                                Some(
439                                    val.parse()
440                                    .map_err(|_| format!("Invalid data for {}", stringify!($optional)))?,
441                                )
442                            }
443                            _ => None,
444                        };
445                        Ok(Self::$optional(d))
446                    }, )*
447
448                    _ => Err(format!("Unknown variant {}", s)),
449                }
450            }
451        }
452    };
453}
454use enum_from_str_display;
455
456impl<A: ActionExt> IntoIterator for Actions<A> {
457    type Item = Action<A>;
458    type IntoIter = <ArrayVec<Action<A>, MAX_ACTIONS> as IntoIterator>::IntoIter;
459
460    fn into_iter(self) -> Self::IntoIter {
461        self.0.into_iter()
462    }
463}
464
465impl<'a, A: ActionExt> IntoIterator for &'a Actions<A> {
466    type Item = &'a Action<A>;
467    type IntoIter = <&'a ArrayVec<Action<A>, MAX_ACTIONS> as IntoIterator>::IntoIter;
468
469    fn into_iter(self) -> Self::IntoIter {
470        self.0.iter()
471    }
472}