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