use super::{Event, KeyCode, KeyModifiers};
use crate::{key_bindings::KeyBindings, InputOptions, KeyEvent, StandardEvent};
#[derive(Debug)]
pub struct EventHandler<CustomKeybinding: crate::CustomKeybinding, CustomEvent: crate::CustomEvent> {
key_bindings: KeyBindings<CustomKeybinding, CustomEvent>,
}
impl<CustomKeybinding: crate::CustomKeybinding, CustomEvent: crate::CustomEvent>
EventHandler<CustomKeybinding, CustomEvent>
{
#[inline]
#[must_use]
pub const fn new(key_bindings: KeyBindings<CustomKeybinding, CustomEvent>) -> Self {
Self { key_bindings }
}
#[inline]
pub fn read_event<F>(
&self,
event: Event<CustomEvent>,
input_options: &InputOptions,
callback: F,
) -> Event<CustomEvent>
where
F: FnOnce(Event<CustomEvent>, &KeyBindings<CustomKeybinding, CustomEvent>) -> Event<CustomEvent>,
{
if event == Event::None {
return event;
}
if let Some(e) = Self::handle_standard_inputs(event) {
return e;
}
if input_options.contains(InputOptions::RESIZE) {
if let Event::Resize(..) = event {
return event;
}
}
if input_options.contains(InputOptions::MOVEMENT) {
if let Some(evt) = Self::handle_movement_inputs(&self.key_bindings, event) {
return evt;
}
}
if input_options.contains(InputOptions::SEARCH) {
if let Some(evt) = Self::handle_search(&self.key_bindings, event) {
return evt;
}
}
if input_options.contains(InputOptions::HELP) && self.key_bindings.help.contains(&event) {
return Event::from(StandardEvent::Help);
}
if input_options.contains(InputOptions::UNDO_REDO) {
if let Some(evt) = Self::handle_undo_redo(&self.key_bindings, event) {
return evt;
}
}
callback(event, &self.key_bindings)
}
#[allow(clippy::wildcard_enum_match_arm)]
fn handle_standard_inputs(event: Event<CustomEvent>) -> Option<Event<CustomEvent>> {
match event {
Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
}) => Some(Event::from(StandardEvent::Kill)),
_ => None,
}
}
#[allow(clippy::wildcard_enum_match_arm)]
fn handle_movement_inputs(
key_bindings: &KeyBindings<CustomKeybinding, CustomEvent>,
event: Event<CustomEvent>,
) -> Option<Event<CustomEvent>> {
Some(match event {
e if key_bindings.scroll_down.contains(&e) => Event::from(StandardEvent::ScrollDown),
e if key_bindings.scroll_end.contains(&e) => Event::from(StandardEvent::ScrollBottom),
e if key_bindings.scroll_home.contains(&e) => Event::from(StandardEvent::ScrollTop),
e if key_bindings.scroll_left.contains(&e) => Event::from(StandardEvent::ScrollLeft),
e if key_bindings.scroll_right.contains(&e) => Event::from(StandardEvent::ScrollRight),
e if key_bindings.scroll_up.contains(&e) => Event::from(StandardEvent::ScrollUp),
e if key_bindings.scroll_step_down.contains(&e) => Event::from(StandardEvent::ScrollJumpDown),
e if key_bindings.scroll_step_up.contains(&e) => Event::from(StandardEvent::ScrollJumpUp),
Event::Key(KeyEvent {
code: KeyCode::Up,
modifiers: KeyModifiers::NONE,
}) => Event::from(StandardEvent::ScrollUp),
Event::Key(KeyEvent {
code: KeyCode::Down,
modifiers: KeyModifiers::NONE,
}) => Event::from(StandardEvent::ScrollDown),
Event::Key(KeyEvent {
code: KeyCode::Left,
modifiers: KeyModifiers::NONE,
}) => Event::from(StandardEvent::ScrollLeft),
Event::Key(KeyEvent {
code: KeyCode::Right,
modifiers: KeyModifiers::NONE,
}) => Event::from(StandardEvent::ScrollRight),
Event::Key(KeyEvent {
code: KeyCode::PageUp,
modifiers: KeyModifiers::NONE,
}) => Event::from(StandardEvent::ScrollJumpUp),
Event::Key(KeyEvent {
code: KeyCode::PageDown,
modifiers: KeyModifiers::NONE,
}) => Event::from(StandardEvent::ScrollJumpDown),
Event::Key(KeyEvent {
code: KeyCode::Home,
modifiers: KeyModifiers::NONE,
}) => Event::from(StandardEvent::ScrollTop),
Event::Key(KeyEvent {
code: KeyCode::End,
modifiers: KeyModifiers::NONE,
}) => Event::from(StandardEvent::ScrollBottom),
_ => return None,
})
}
fn handle_search(
key_bindings: &KeyBindings<CustomKeybinding, CustomEvent>,
event: Event<CustomEvent>,
) -> Option<Event<CustomEvent>> {
match event {
e if key_bindings.search_next.contains(&e) => Some(Event::from(StandardEvent::SearchNext)),
e if key_bindings.search_previous.contains(&e) => Some(Event::from(StandardEvent::SearchPrevious)),
e if key_bindings.search_start.contains(&e) => Some(Event::from(StandardEvent::SearchStart)),
_ => None,
}
}
fn handle_undo_redo(
key_bindings: &KeyBindings<CustomKeybinding, CustomEvent>,
event: Event<CustomEvent>,
) -> Option<Event<CustomEvent>> {
if key_bindings.undo.contains(&event) {
Some(Event::from(StandardEvent::Undo))
}
else if key_bindings.redo.contains(&event) {
Some(Event::from(StandardEvent::Redo))
}
else {
None
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use crate::{
map_keybindings,
testutil::local::{create_test_keybindings, Event, EventHandler},
};
#[rstest]
#[case::standard(Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
}), true)]
#[case::resize(Event::Resize(100, 100), false)]
#[case::movement(Event::from(KeyCode::Up), false)]
#[case::undo_redo(Event::Key(KeyEvent {
code: KeyCode::Char('z'),
modifiers: KeyModifiers::CONTROL,
}), false)]
#[case::other(Event::from('a'), false)]
fn read_event_options_disabled(#[case] event: Event, #[case] handled: bool) {
let event_handler = EventHandler::new(create_test_keybindings());
let result = event_handler.read_event(event, &InputOptions::empty(), |_, _| Event::from(KeyCode::Null));
if handled {
assert_ne!(result, Event::from(KeyCode::Null));
}
else {
assert_eq!(result, Event::from(KeyCode::Null));
}
}
#[rstest]
#[case::standard(Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
}), true)]
#[case::resize(Event::Resize(100, 100), true)]
#[case::movement(Event::from(KeyCode::Up), true)]
#[case::undo_redo(Event::Key(KeyEvent {
code: KeyCode::Char('z'),
modifiers: KeyModifiers::CONTROL,
}), true)]
#[case::other(Event::from('a'), false)]
fn read_event_enabled(#[case] event: Event, #[case] handled: bool) {
let event_handler = EventHandler::new(create_test_keybindings());
let result = event_handler.read_event(event, &InputOptions::all(), |_, _| Event::from(KeyCode::Null));
if handled {
assert_ne!(result, Event::from(KeyCode::Null));
}
else {
assert_eq!(result, Event::from(KeyCode::Null));
}
}
#[test]
fn none_event() {
let event_handler = EventHandler::new(create_test_keybindings());
let result = event_handler.read_event(Event::None, &InputOptions::empty(), |_, _| Event::from(KeyCode::Null));
assert_eq!(result, Event::None);
}
#[rstest]
#[case::standard(Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
}), Event::from(StandardEvent::Kill))]
#[case::other(Event::from('a'), Event::from(KeyCode::Null))]
fn standard_inputs(#[case] event: Event, #[case] expected: Event) {
let event_handler = EventHandler::new(create_test_keybindings());
let result = event_handler.read_event(event, &InputOptions::empty(), |_, _| Event::from(KeyCode::Null));
assert_eq!(result, expected);
}
#[rstest]
#[case::standard(Event::from(KeyCode::Up), Event::from(StandardEvent::ScrollUp))]
#[case::standard(Event::from(KeyCode::Down), Event::from(StandardEvent::ScrollDown))]
#[case::standard(Event::from(KeyCode::Left), Event::from(StandardEvent::ScrollLeft))]
#[case::standard(Event::from(KeyCode::Right), Event::from(StandardEvent::ScrollRight))]
#[case::standard(Event::from(KeyCode::PageUp), Event::from(StandardEvent::ScrollJumpUp))]
#[case::standard(Event::from(KeyCode::PageDown), Event::from(StandardEvent::ScrollJumpDown))]
#[case::standard(Event::from(KeyCode::Home), Event::from(StandardEvent::ScrollTop))]
#[case::standard(Event::from(KeyCode::End), Event::from(StandardEvent::ScrollBottom))]
#[case::other(Event::from('a'), Event::from(KeyCode::Null))]
fn movement_inputs(#[case] event: Event, #[case] expected: Event) {
let event_handler = EventHandler::new(create_test_keybindings());
let result = event_handler.read_event(event, &InputOptions::MOVEMENT, |_, _| Event::from(KeyCode::Null));
assert_eq!(result, expected);
}
#[rstest]
#[case::standard(Event::from(KeyCode::Up), Event::from(StandardEvent::ScrollUp))]
#[case::standard(Event::from(KeyCode::Down), Event::from(StandardEvent::ScrollDown))]
#[case::standard(Event::from(KeyCode::Left), Event::from(StandardEvent::ScrollLeft))]
#[case::standard(Event::from(KeyCode::Right), Event::from(StandardEvent::ScrollRight))]
#[case::standard(Event::from(KeyCode::PageUp), Event::from(StandardEvent::ScrollJumpUp))]
#[case::standard(Event::from(KeyCode::PageDown), Event::from(StandardEvent::ScrollJumpDown))]
#[case::standard(Event::from(KeyCode::Home), Event::from(StandardEvent::ScrollTop))]
#[case::standard(Event::from(KeyCode::End), Event::from(StandardEvent::ScrollBottom))]
#[case::other(Event::from('a'), Event::from(KeyCode::Null))]
fn default_movement_inputs(#[case] event: Event, #[case] expected: Event) {
let mut bindings = create_test_keybindings();
bindings.scroll_down = map_keybindings(&[String::from("x")]);
bindings.scroll_end = map_keybindings(&[String::from("x")]);
bindings.scroll_home = map_keybindings(&[String::from("x")]);
bindings.scroll_left = map_keybindings(&[String::from("x")]);
bindings.scroll_right = map_keybindings(&[String::from("x")]);
bindings.scroll_up = map_keybindings(&[String::from("x")]);
bindings.scroll_step_down = map_keybindings(&[String::from("x")]);
bindings.scroll_step_up = map_keybindings(&[String::from("x")]);
let event_handler = EventHandler::new(bindings);
let result = event_handler.read_event(event, &InputOptions::MOVEMENT, |_, _| Event::from(KeyCode::Null));
assert_eq!(result, expected);
}
#[rstest]
#[case::search_next(Event::from('n'), Event::from(StandardEvent::SearchNext))]
#[case::search_previous(Event::from('N'), Event::from(StandardEvent::SearchPrevious))]
#[case::search_start(Event::from('/'), Event::from(StandardEvent::SearchStart))]
#[case::other(Event::from('a'), Event::from(KeyCode::Null))]
fn search_inputs(#[case] event: Event, #[case] expected: Event) {
let event_handler = EventHandler::new(create_test_keybindings());
let result = event_handler.read_event(event, &InputOptions::SEARCH, |_, _| Event::from(KeyCode::Null));
assert_eq!(result, expected);
}
#[test]
fn help_event() {
let event_handler = EventHandler::new(create_test_keybindings());
let result = event_handler.read_event(Event::from('?'), &InputOptions::HELP, |_, _| Event::from(KeyCode::Null));
assert_eq!(result, Event::from(StandardEvent::Help));
}
#[rstest]
#[case::standard(Event::Key(KeyEvent {
code: KeyCode::Char('z'),
modifiers: KeyModifiers::CONTROL,
}), Event::from(StandardEvent::Undo))]
#[case::standard(Event::Key(KeyEvent {
code: KeyCode::Char('y'),
modifiers: KeyModifiers::CONTROL,
}), Event::from(StandardEvent::Redo))]
#[case::other(Event::from('a'), Event::from(KeyCode::Null))]
fn undo_redo_inputs(#[case] event: Event, #[case] expected: Event) {
let event_handler = EventHandler::new(create_test_keybindings());
let result = event_handler.read_event(event, &InputOptions::UNDO_REDO, |_, _| Event::from(KeyCode::Null));
assert_eq!(result, expected);
}
}