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