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