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,
},
PasteToSettings,
OpenConfigFile {
layer: crate::config_io::ConfigLayer,
},
CloseMenu,
ExecuteMenuAction {
action: String,
args: std::collections::HashMap<String, serde_json::Value>,
},
ClosePrompt,
ConfirmPrompt,
UpdatePromptSuggestions,
PromptHistoryPrev,
PromptHistoryNext,
PreviewThemeFromPrompt,
PromptSelectionChanged {
selected_index: usize,
},
ClosePopup,
ConfirmPopup,
CompletionEnterKey,
PopupTypeChar(char),
PopupBackspace,
CopyToClipboard(String),
FileBrowserSelectPrev,
FileBrowserSelectNext,
FileBrowserPageUp,
FileBrowserPageDown,
FileBrowserConfirm,
FileBrowserAcceptSuggestion,
FileBrowserGoParent,
FileBrowserUpdateFilter,
FileBrowserToggleHidden,
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 result == InputResult::Ignored {
return InputResult::Ignored;
}
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());
}
struct TestModalHandler {
returns_ignored: bool,
}
impl InputHandler for TestModalHandler {
fn handle_key_event(&mut self, _event: &KeyEvent, _ctx: &mut InputContext) -> InputResult {
if self.returns_ignored {
InputResult::Ignored
} else {
InputResult::Consumed
}
}
fn is_modal(&self) -> bool {
true
}
}
#[test]
fn test_modal_handler_respects_ignored() {
let mut handler = TestModalHandler {
returns_ignored: true,
};
let mut ctx = InputContext::new();
let event = KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL);
let result = handler.dispatch_input(&event, &mut ctx);
assert_eq!(
result,
InputResult::Ignored,
"Modal handler should respect Ignored result"
);
}
#[test]
fn test_modal_handler_consumes_unknown_keys() {
let mut handler = TestModalHandler {
returns_ignored: false,
};
let mut ctx = InputContext::new();
let event = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE);
let result = handler.dispatch_input(&event, &mut ctx);
assert_eq!(
result,
InputResult::Consumed,
"Modal handler should consume handled keys"
);
}
}