Skip to main content

minus/input/
hashed_event_register.rs

1//! Provides the [`HashedEventRegister`] and related items
2//!
3//! This module holds the [`HashedEventRegister`] which is a [`HashMap`] that stores events and their associated
4//! callbacks. When the user does an action on the terminal, the event is scanned and matched against this register.
5//! If their is a match related to that event, the associated callback is called
6
7use super::{InputClassifier, InputEvent};
8use crate::PagerState;
9use crossterm::event::{Event, MouseEvent};
10use std::{
11    collections::HashMap, collections::hash_map::RandomState, hash::BuildHasher, hash::Hash,
12    sync::Arc,
13};
14
15use std::borrow::Cow;
16
17/// A convenient type for the return type of [`HashedEventRegister::get`]
18type EventReturnType = Arc<dyn Fn(Event, &PagerState) -> InputEvent + Send + Sync>;
19
20#[derive(Clone)]
21struct EventCallback {
22    cb: EventReturnType,
23    desc: Cow<'static, str>,
24}
25
26// //////////////////////////////
27// EVENTWRAPPER TYPE
28// //////////////////////////////
29
30#[derive(Clone, Eq)]
31enum EventWrapper {
32    ExactMatchEvent(Event),
33    WildEvent,
34}
35
36impl From<Event> for EventWrapper {
37    fn from(e: Event) -> Self {
38        Self::ExactMatchEvent(e)
39    }
40}
41
42impl From<&Event> for EventWrapper {
43    fn from(e: &Event) -> Self {
44        Self::ExactMatchEvent(e.clone())
45    }
46}
47
48impl PartialEq for EventWrapper {
49    fn eq(&self, other: &Self) -> bool {
50        match (self, other) {
51            (
52                Self::ExactMatchEvent(Event::Mouse(MouseEvent {
53                    kind, modifiers, ..
54                })),
55                Self::ExactMatchEvent(Event::Mouse(MouseEvent {
56                    kind: o_kind,
57                    modifiers: o_modifiers,
58                    ..
59                })),
60            ) => kind == o_kind && modifiers == o_modifiers,
61            (
62                Self::ExactMatchEvent(Event::Resize(..)),
63                Self::ExactMatchEvent(Event::Resize(..)),
64            )
65            | (Self::WildEvent, Self::WildEvent) => true,
66            (Self::ExactMatchEvent(ev), Self::ExactMatchEvent(o_ev)) => ev == o_ev,
67            _ => false,
68        }
69    }
70}
71
72impl Hash for EventWrapper {
73    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
74        let tag = std::mem::discriminant(self);
75        tag.hash(state);
76        match self {
77            Self::ExactMatchEvent(Event::Mouse(MouseEvent {
78                kind, modifiers, ..
79            })) => {
80                kind.hash(state);
81                modifiers.hash(state);
82            }
83            Self::WildEvent | Self::ExactMatchEvent(Event::Resize(..)) => {}
84            Self::ExactMatchEvent(v) => {
85                v.hash(state);
86            }
87        }
88    }
89}
90
91// /////////////////////////////////////////////////
92// HASHED EVENT REGISTER TYPE AND ITS APIs
93// ////////////////////////////////////////////////
94
95/// A hash store for events and it's related callback
96///
97/// Each item is a key value pair, where the key is a event and it's value is a callback. When a
98/// event occurs, it is matched inside and when the related match is found, it's related callback
99/// is called.
100pub struct HashedEventRegister<S>(HashMap<EventWrapper, EventCallback, S>);
101
102impl HashedEventRegister<RandomState> {
103    /// Create a new [`HashedEventRegister`] with the default hasher
104    #[must_use]
105    pub fn with_default_hasher() -> Self {
106        Self::new(RandomState::new())
107    }
108}
109
110impl Default for HashedEventRegister<RandomState> {
111    /// Create a new [`HashedEventRegister`] with the default hasher and insert the default bindings
112    fn default() -> Self {
113        let mut event_register = Self::new(RandomState::new());
114        super::generate_default_bindings(&mut event_register);
115        event_register
116    }
117}
118
119impl<S> InputClassifier for HashedEventRegister<S>
120where
121    S: BuildHasher,
122{
123    fn classify_input(&self, ev: Event, ps: &crate::PagerState) -> Option<InputEvent> {
124        self.get(&ev).map(|c| c(ev, ps))
125    }
126
127    fn format_help(&self) -> Option<String> {
128        let h = self.format_help();
129        if h.is_empty() {
130            None
131        } else {
132            Some(h)
133        }
134    }
135}
136
137// ####################
138// GENERAL FUNCTIONS
139// ####################
140impl<S> HashedEventRegister<S>
141where
142    S: BuildHasher,
143{
144    /// Create a new `HashedEventRegister` with the Hasher `s`
145    pub const fn new(s: S) -> Self {
146        Self(HashMap::with_hasher(s))
147    }
148
149    /// Format dynamic help table from all registered key bindings that have non-empty descriptions.
150    #[must_use]
151    pub fn format_help(&self) -> String {
152        let entries = self.0.iter().filter_map(|(k, v)| match k {
153            EventWrapper::ExactMatchEvent(Event::Key(ke)) => Some((ke, v.desc.as_ref())),
154            _ => None,
155        });
156        crate::help::format_help_table_from_entries(entries)
157    }
158
159    /// Adds a callback to handle all events that failed to match
160    ///
161    /// Sometimes there are bunch of keys having equal importance that should have the same
162    /// callback, for instance all the numbers on the keyboard. To handle these types of scenerios
163    /// this is extremely useful. This callback is called when no event matches the incoming event,
164    /// then we just match whether the event is a keyboard number and perform the required action.
165    ///
166    /// This is also helpful when you need to do some action, like sending a message when the user
167    /// presses wrong keyboard/mouse buttons.
168    pub fn insert_wild_event_matcher(
169        &mut self,
170        cb: impl Fn(Event, &PagerState) -> InputEvent + Send + Sync + 'static,
171    ) {
172        self.0.insert(
173            EventWrapper::WildEvent,
174            EventCallback {
175                cb: Arc::new(cb),
176                desc: Cow::Borrowed(""),
177            },
178        );
179    }
180
181    fn get(&self, k: &Event) -> Option<&EventReturnType> {
182        self.0
183            .get(&k.into())
184            .map_or_else(|| self.0.get(&EventWrapper::WildEvent), Some)
185            .map(|entry| &entry.cb)
186    }
187
188    /// Adds a callback for handling resize events
189    ///
190    /// # Example
191    /// These are from the original sources
192    /// ```
193    /// use minus::input::{InputEvent, HashedEventRegister, crossterm_event::Event};
194    ///
195    /// let mut input_register = HashedEventRegister::default();
196    ///
197    /// input_register.add_resize_event(|ev, _| {
198    ///     let (cols, rows) = if let Event::Resize(cols, rows) = ev {
199    ///         (cols, rows)
200    ///     } else {
201    ///         unreachable!();
202    ///     };
203    ///     InputEvent::UpdateTermArea(cols as usize, rows as usize)
204    /// });
205    /// ```
206    pub fn add_resize_event(
207        &mut self,
208        cb: impl Fn(Event, &PagerState) -> InputEvent + Send + Sync + 'static,
209    ) {
210        let v = Arc::new(cb);
211        // The 0, 0 are present just to ensure everything compiles and they can be anything.
212        // These values are never hashed or stored into the HashedEventRegister
213        self.0.insert(
214            EventWrapper::ExactMatchEvent(Event::Resize(0, 0)),
215            EventCallback {
216                cb: v,
217                desc: Cow::Borrowed(""),
218            },
219        );
220    }
221
222    /// Removes the currently active resize event callback
223    pub fn remove_resize_event(&mut self) {
224        self.0
225            .remove(&EventWrapper::ExactMatchEvent(Event::Resize(0, 0)));
226    }
227}
228
229// ###############################
230// KEYBOARD SPECIFIC FUNCTIONS
231// ###############################
232impl<S> HashedEventRegister<S>
233where
234    S: BuildHasher,
235{
236    /// Add all elements of `desc` as key bindings that minus should respond to with the callback `cb`
237    ///
238    /// You should prefer using the [`add_key_events_checked`](HashedEventRegister::add_key_events_checked)
239    /// over this one.
240    ///
241    /// # Example
242    /// ```
243    /// use minus::input::{InputEvent, HashedEventRegister, crossterm_event};
244    ///
245    /// let mut input_register = HashedEventRegister::default();
246    ///
247    /// input_register.add_key_events(&["down"], |_, ps| {
248    ///     InputEvent::UpdateUpperMark(ps.upper_mark.saturating_sub(1))
249    /// });
250    /// ```
251    pub fn add_key_events(
252        &mut self,
253        desc: &[&str],
254        cb: impl Fn(Event, &PagerState) -> InputEvent + Send + Sync + 'static,
255    ) {
256        self.add_described_key_events(desc, "", cb);
257    }
258
259    /// Add all elements of `keys` as key bindings with a description that minus should respond to with the callback `cb`.
260    pub fn add_described_key_events(
261        &mut self,
262        keys: &[&str],
263        desc: impl Into<Cow<'static, str>>,
264        cb: impl Fn(Event, &PagerState) -> InputEvent + Send + Sync + 'static,
265    ) {
266        let v = Arc::new(cb);
267        let d = desc.into();
268        for k in keys {
269            self.0.insert(
270                Event::Key(super::definitions::keydefs::parse_key_event(k)).into(),
271                EventCallback {
272                    cb: v.clone(),
273                    desc: d.clone(),
274                },
275            );
276        }
277    }
278
279    /// Add all elements of `desc` as key bindings that minus should respond to with the callback `cb`.
280    ///
281    /// Prefer using this over [`add_key_events`](HashedEventRegister::add_key_events).
282    ///
283    /// # Panics
284    ///
285    /// This will panic if you the keybinding has been previously defined, unless the `remap`
286    /// is set to true. This helps preventing accidental overrides of your keybindings.
287    ///
288    /// # Example
289    /// ```should_panic
290    /// use minus::input::{InputEvent, HashedEventRegister, crossterm_event};
291    ///
292    /// let mut input_register = HashedEventRegister::default();
293    ///
294    /// input_register.add_key_events_checked(&["down"], |_, ps| {
295    ///     InputEvent::UpdateUpperMark(ps.upper_mark.saturating_sub(1))
296    /// }, false);
297    /// ```
298    pub fn add_key_events_checked(
299        &mut self,
300        desc: &[&str],
301        cb: impl Fn(Event, &PagerState) -> InputEvent + Send + Sync + 'static,
302        remap: bool,
303    ) {
304        self.add_described_key_events_checked(desc, "", cb, remap);
305    }
306
307    /// Add all elements of `keys` as key bindings with a description that minus should respond to with the callback `cb`, with conflict checking.
308    ///
309    /// # Panics
310    /// Panics if a key already exists and `remap` is `false`.
311    pub fn add_described_key_events_checked(
312        &mut self,
313        keys: &[&str],
314        desc: impl Into<Cow<'static, str>>,
315        cb: impl Fn(Event, &PagerState) -> InputEvent + Send + Sync + 'static,
316        remap: bool,
317    ) {
318        let v = Arc::new(cb);
319        let d = desc.into();
320        for k in keys {
321            let def: EventWrapper =
322                Event::Key(super::definitions::keydefs::parse_key_event(k)).into();
323            assert!(self.0.contains_key(&def) && remap, "");
324            self.0.insert(
325                def,
326                EventCallback {
327                    cb: v.clone(),
328                    desc: d.clone(),
329                },
330            );
331        }
332    }
333
334    /// Removes the callback associated with the all the elements of `desc`.
335    ///
336    /// ```
337    /// use minus::input::{InputEvent, HashedEventRegister, crossterm_event};
338    ///
339    /// let mut input_register = HashedEventRegister::default();
340    ///
341    /// input_register.remove_key_events(&["down"])
342    /// ```
343    pub fn remove_key_events(&mut self, desc: &[&str]) {
344        for k in desc {
345            self.0
346                .remove(&Event::Key(super::definitions::keydefs::parse_key_event(k)).into());
347        }
348    }
349
350    /// Add key binding(s) to show help in the pager prompt.
351    ///
352    /// If `desc` is empty, defaults to `&["m-h"]`.
353    ///
354    /// # Example
355    /// ```
356    /// use minus::input::HashedEventRegister;
357    ///
358    /// let mut input_register = HashedEventRegister::default();
359    /// // Bind default Meta/Alt-h key to show help
360    /// input_register.add_help_key(&[]);
361    /// // Or specify custom keys
362    /// input_register.add_help_key(&["f1"]);
363    /// ```
364    pub fn add_help_key(&mut self, desc: &[&str]) {
365        let keys = if desc.is_empty() { &["m-h"][..] } else { desc };
366        self.add_described_key_events(keys, "help", |_, _| InputEvent::ShowHelp);
367    }
368
369    /// Add key binding(s) to show help in the pager prompt with conflict checking.
370    ///
371    /// If `desc` is empty, defaults to `&["m-h"]`.
372    ///
373    /// # Panics
374    /// This will panic if any of the keybindings has been previously defined, unless `remap`
375    /// is set to true.
376    pub fn add_help_key_checked(&mut self, desc: &[&str], remap: bool) {
377        let keys = if desc.is_empty() { &["m-h"][..] } else { desc };
378        self.add_described_key_events_checked(keys, "help", |_, _| InputEvent::ShowHelp, remap);
379    }
380}
381
382// ###############################
383// MOUSE SPECIFIC FUNCTIONS
384// ###############################
385impl<S> HashedEventRegister<S>
386where
387    S: BuildHasher,
388{
389    /// Add all elemnts of `desc` as mouse bindings that minus should respond to with the callback `cb`
390    ///
391    /// You should prefer using the [`add_mouse_events_checked`](HashedEventRegister::add_mouse_events_checked)
392    /// over this one.
393    ///
394    /// # Example
395    /// ```
396    /// use minus::input::{InputEvent, HashedEventRegister};
397    ///
398    /// let mut input_register = HashedEventRegister::default();
399    ///
400    /// input_register.add_mouse_events(&["scroll:down"], |_, ps| {
401    ///     InputEvent::UpdateUpperMark(ps.upper_mark.saturating_sub(5))
402    /// });
403    /// ```
404    pub fn add_mouse_events(
405        &mut self,
406        desc: &[&str],
407        cb: impl Fn(Event, &PagerState) -> InputEvent + Send + Sync + 'static,
408    ) {
409        let v = Arc::new(cb);
410        for k in desc {
411            self.0.insert(
412                Event::Mouse(super::definitions::mousedefs::parse_mouse_event(k)).into(),
413                EventCallback {
414                    cb: v.clone(),
415                    desc: Cow::Borrowed(""),
416                },
417            );
418        }
419    }
420
421    /// Add all elemnts of `desc` as mouse bindings that minus should respond to with the callback `cb`.
422    ///
423    /// Prefer using this over [`add_mouse_events`](HashedEventRegister::add_mouse_events).
424    ///
425    /// # Panics
426    /// This will panic if you the keybinding has been previously defined, unless the `remap`
427    /// is set to true. This helps preventing accidental overrides of your keybindings.
428    ///
429    /// # Example
430    /// ```should_panic
431    /// use minus::input::{InputEvent, HashedEventRegister};
432    ///
433    /// let mut input_register = HashedEventRegister::default();
434    ///
435    /// input_register.add_mouse_events_checked(&["scroll:down"], |_, ps| {
436    ///     InputEvent::UpdateUpperMark(ps.upper_mark.saturating_sub(5))
437    /// }, false);
438    /// ```
439    pub fn add_mouse_events_checked(
440        &mut self,
441        desc: &[&str],
442        cb: impl Fn(Event, &PagerState) -> InputEvent + Send + Sync + 'static,
443        remap: bool,
444    ) {
445        let v = Arc::new(cb);
446        for k in desc {
447            let def: EventWrapper =
448                Event::Mouse(super::definitions::mousedefs::parse_mouse_event(k)).into();
449            assert!(self.0.contains_key(&def) && remap, "");
450            self.0.insert(
451                def,
452                EventCallback {
453                    cb: v.clone(),
454                    desc: Cow::Borrowed(""),
455                },
456            );
457        }
458    }
459
460    /// Removes the callback associated with the all the elements of `desc`.
461    ///
462    /// ```
463    /// use minus::input::{InputEvent, HashedEventRegister, crossterm_event};
464    ///
465    /// let mut input_register = HashedEventRegister::default();
466    ///
467    /// input_register.remove_mouse_events(&["scroll:down"])
468    /// ```
469    pub fn remove_mouse_events(&mut self, mouse: &[&str]) {
470        for k in mouse {
471            self.0
472                .remove(&Event::Mouse(super::definitions::mousedefs::parse_mouse_event(k)).into());
473        }
474    }
475}