use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputResult {
Consumed,
Ignored,
}
impl InputResult {
pub fn is_consumed(self) -> bool {
self == InputResult::Consumed
}
pub fn or(self, other: InputResult) -> InputResult {
if self == InputResult::Consumed || other == InputResult::Consumed {
InputResult::Consumed
} else {
InputResult::Ignored
}
}
}
#[derive(Default)]
pub struct InputContext {
pub status_message: Option<String>,
pub deferred_actions: Vec<DeferredAction>,
}
impl InputContext {
pub fn new() -> Self {
Self::default()
}
pub fn set_status(&mut self, msg: impl Into<String>) {
self.status_message = Some(msg.into());
}
pub fn defer(&mut self, action: DeferredAction) {
self.deferred_actions.push(action);
}
}
#[derive(Debug, Clone)]
pub enum DeferredAction {
CloseSettings {
save: bool,
},
CloseMenu,
ExecuteMenuAction {
action: String,
args: std::collections::HashMap<String, serde_json::Value>,
},
ClosePrompt,
ConfirmPrompt,
UpdatePromptSuggestions,
PromptHistoryPrev,
PromptHistoryNext,
ClosePopup,
ConfirmPopup,
FileBrowserSelectPrev,
FileBrowserSelectNext,
FileBrowserPageUp,
FileBrowserPageDown,
FileBrowserConfirm,
FileBrowserAcceptSuggestion,
FileBrowserGoParent,
FileBrowserUpdateFilter,
InteractiveReplaceKey(char),
CancelInteractiveReplace,
ToggleKeyboardCapture,
SendTerminalKey(crossterm::event::KeyCode, crossterm::event::KeyModifiers),
ExitTerminalMode {
explicit: bool,
},
EnterScrollbackMode,
EnterTerminalMode,
ExecuteAction(crate::input::keybindings::Action),
InsertCharAndUpdate(char),
}
pub trait InputHandler {
fn handle_key_event(&mut self, event: &KeyEvent, ctx: &mut InputContext) -> InputResult;
fn focused_child(&self) -> Option<&dyn InputHandler> {
None
}
fn focused_child_mut(&mut self) -> Option<&mut dyn InputHandler> {
None
}
fn is_modal(&self) -> bool {
false
}
fn dispatch_input(&mut self, event: &KeyEvent, ctx: &mut InputContext) -> InputResult {
if let Some(child) = self.focused_child_mut() {
let result = child.dispatch_input(event, ctx);
if result == InputResult::Consumed {
return InputResult::Consumed;
}
}
let result = self.handle_key_event(event, ctx);
if result == InputResult::Consumed {
return InputResult::Consumed;
}
if self.is_modal() {
return InputResult::Consumed;
}
InputResult::Ignored
}
}
pub fn is_key(event: &KeyEvent, code: KeyCode) -> bool {
event.code == code && event.modifiers.is_empty()
}
pub fn is_key_with_ctrl(event: &KeyEvent, c: char) -> bool {
event.code == KeyCode::Char(c) && event.modifiers == KeyModifiers::CONTROL
}
pub fn is_key_with_shift(event: &KeyEvent, code: KeyCode) -> bool {
event.code == code && event.modifiers == KeyModifiers::SHIFT
}
pub fn is_key_with_alt(event: &KeyEvent, code: KeyCode) -> bool {
event.code == code && event.modifiers == KeyModifiers::ALT
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_input_result_or() {
assert_eq!(
InputResult::Consumed.or(InputResult::Consumed),
InputResult::Consumed
);
assert_eq!(
InputResult::Consumed.or(InputResult::Ignored),
InputResult::Consumed
);
assert_eq!(
InputResult::Ignored.or(InputResult::Consumed),
InputResult::Consumed
);
assert_eq!(
InputResult::Ignored.or(InputResult::Ignored),
InputResult::Ignored
);
}
#[test]
fn test_is_consumed() {
assert!(InputResult::Consumed.is_consumed());
assert!(!InputResult::Ignored.is_consumed());
}
}