use std::any::Any;
use std::fmt;
use std::sync::Arc;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[derive(Debug, Clone, PartialEq)]
pub enum AppKeyResult {
Handled,
NotHandled,
Action(AppKeyAction),
}
pub enum AppKeyAction {
MoveUp,
MoveDown,
MoveLeft,
MoveRight,
MoveLineStart,
MoveLineEnd,
DeleteCharBefore,
DeleteCharAt,
KillLine,
InsertNewline,
InsertChar(char),
Submit,
Interrupt,
Quit,
RequestExit,
ActivateSlashPopup,
Custom(Arc<dyn Any + Send + Sync>),
}
impl AppKeyAction {
pub fn custom<T: Any + Send + Sync + 'static>(value: T) -> Self {
Self::Custom(Arc::new(value))
}
}
impl Clone for AppKeyAction {
fn clone(&self) -> Self {
match self {
Self::MoveUp => Self::MoveUp,
Self::MoveDown => Self::MoveDown,
Self::MoveLeft => Self::MoveLeft,
Self::MoveRight => Self::MoveRight,
Self::MoveLineStart => Self::MoveLineStart,
Self::MoveLineEnd => Self::MoveLineEnd,
Self::DeleteCharBefore => Self::DeleteCharBefore,
Self::DeleteCharAt => Self::DeleteCharAt,
Self::KillLine => Self::KillLine,
Self::InsertNewline => Self::InsertNewline,
Self::InsertChar(c) => Self::InsertChar(*c),
Self::Submit => Self::Submit,
Self::Interrupt => Self::Interrupt,
Self::Quit => Self::Quit,
Self::RequestExit => Self::RequestExit,
Self::ActivateSlashPopup => Self::ActivateSlashPopup,
Self::Custom(arc) => Self::Custom(Arc::clone(arc)),
}
}
}
impl PartialEq for AppKeyAction {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::MoveUp, Self::MoveUp) => true,
(Self::MoveDown, Self::MoveDown) => true,
(Self::MoveLeft, Self::MoveLeft) => true,
(Self::MoveRight, Self::MoveRight) => true,
(Self::MoveLineStart, Self::MoveLineStart) => true,
(Self::MoveLineEnd, Self::MoveLineEnd) => true,
(Self::DeleteCharBefore, Self::DeleteCharBefore) => true,
(Self::DeleteCharAt, Self::DeleteCharAt) => true,
(Self::KillLine, Self::KillLine) => true,
(Self::InsertNewline, Self::InsertNewline) => true,
(Self::InsertChar(a), Self::InsertChar(b)) => a == b,
(Self::Submit, Self::Submit) => true,
(Self::Interrupt, Self::Interrupt) => true,
(Self::Quit, Self::Quit) => true,
(Self::RequestExit, Self::RequestExit) => true,
(Self::ActivateSlashPopup, Self::ActivateSlashPopup) => true,
(Self::Custom(a), Self::Custom(b)) => Arc::ptr_eq(a, b),
_ => false,
}
}
}
impl fmt::Debug for AppKeyAction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MoveUp => write!(f, "MoveUp"),
Self::MoveDown => write!(f, "MoveDown"),
Self::MoveLeft => write!(f, "MoveLeft"),
Self::MoveRight => write!(f, "MoveRight"),
Self::MoveLineStart => write!(f, "MoveLineStart"),
Self::MoveLineEnd => write!(f, "MoveLineEnd"),
Self::DeleteCharBefore => write!(f, "DeleteCharBefore"),
Self::DeleteCharAt => write!(f, "DeleteCharAt"),
Self::KillLine => write!(f, "KillLine"),
Self::InsertNewline => write!(f, "InsertNewline"),
Self::InsertChar(c) => write!(f, "InsertChar({:?})", c),
Self::Submit => write!(f, "Submit"),
Self::Interrupt => write!(f, "Interrupt"),
Self::Quit => write!(f, "Quit"),
Self::RequestExit => write!(f, "RequestExit"),
Self::ActivateSlashPopup => write!(f, "ActivateSlashPopup"),
Self::Custom(_) => write!(f, "Custom(..)"),
}
}
}
#[derive(Debug, Clone)]
pub struct KeyContext {
pub input_empty: bool,
pub is_processing: bool,
pub widget_blocking: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct KeyCombo {
pub code: KeyCode,
pub modifiers: KeyModifiers,
}
impl KeyCombo {
pub const fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
Self { code, modifiers }
}
pub const fn key(code: KeyCode) -> Self {
Self {
code,
modifiers: KeyModifiers::NONE,
}
}
pub const fn ctrl(c: char) -> Self {
Self {
code: KeyCode::Char(c),
modifiers: KeyModifiers::CONTROL,
}
}
pub const fn alt(c: char) -> Self {
Self {
code: KeyCode::Char(c),
modifiers: KeyModifiers::ALT,
}
}
pub const fn shift(code: KeyCode) -> Self {
Self {
code,
modifiers: KeyModifiers::SHIFT,
}
}
pub const fn ctrl_alt(c: char) -> Self {
Self {
code: KeyCode::Char(c),
modifiers: KeyModifiers::CONTROL.union(KeyModifiers::ALT),
}
}
pub const fn ctrl_shift(c: char) -> Self {
Self {
code: KeyCode::Char(c),
modifiers: KeyModifiers::CONTROL.union(KeyModifiers::SHIFT),
}
}
pub fn matches(&self, event: &KeyEvent) -> bool {
self.code == event.code && self.modifiers == event.modifiers
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_combo_matches() {
let combo = KeyCombo::ctrl('d');
let event = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL);
assert!(combo.matches(&event));
let event2 = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE);
assert!(!combo.matches(&event2));
}
#[test]
fn test_key_combo_key() {
let combo = KeyCombo::key(KeyCode::Enter);
let event = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
assert!(combo.matches(&event));
}
#[test]
fn test_key_combo_alt() {
let combo = KeyCombo::alt('x');
let event = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::ALT);
assert!(combo.matches(&event));
let event_ctrl = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::CONTROL);
assert!(!combo.matches(&event_ctrl));
}
#[test]
fn test_key_combo_shift() {
let combo = KeyCombo::shift(KeyCode::Tab);
let event = KeyEvent::new(KeyCode::Tab, KeyModifiers::SHIFT);
assert!(combo.matches(&event));
let event_none = KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE);
assert!(!combo.matches(&event_none));
}
#[test]
fn test_key_combo_ctrl_alt() {
let combo = KeyCombo::ctrl_alt('a');
let expected_mods = KeyModifiers::CONTROL | KeyModifiers::ALT;
let event = KeyEvent::new(KeyCode::Char('a'), expected_mods);
assert!(combo.matches(&event));
let event_ctrl_only = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
assert!(!combo.matches(&event_ctrl_only));
}
#[test]
fn test_key_combo_ctrl_shift() {
let combo = KeyCombo::ctrl_shift('z');
let expected_mods = KeyModifiers::CONTROL | KeyModifiers::SHIFT;
let event = KeyEvent::new(KeyCode::Char('z'), expected_mods);
assert!(combo.matches(&event));
let event_shift_only = KeyEvent::new(KeyCode::Char('z'), KeyModifiers::SHIFT);
assert!(!combo.matches(&event_shift_only));
}
#[test]
fn test_custom_action_creation() {
#[derive(Debug, Clone, PartialEq)]
struct MyAction {
value: i32,
}
let action = AppKeyAction::custom(MyAction { value: 42 });
if let AppKeyAction::Custom(any) = &action {
let my_action = any.downcast_ref::<MyAction>().unwrap();
assert_eq!(my_action.value, 42);
} else {
panic!("Expected Custom variant");
}
}
#[test]
fn test_custom_action_clone() {
#[derive(Debug)]
struct MyAction(#[allow(dead_code)] String);
let action1 = AppKeyAction::custom(MyAction("test".to_string()));
let action2 = action1.clone();
if let (AppKeyAction::Custom(arc1), AppKeyAction::Custom(arc2)) = (&action1, &action2) {
assert!(Arc::ptr_eq(arc1, arc2));
} else {
panic!("Expected Custom variants");
}
}
#[test]
fn test_custom_action_equality() {
#[derive(Debug)]
struct MyAction;
let action1 = AppKeyAction::custom(MyAction);
let action2 = action1.clone();
let action3 = AppKeyAction::custom(MyAction);
assert_eq!(action1, action2);
assert_ne!(action1, action3);
}
#[test]
fn test_app_key_action_debug() {
let custom = AppKeyAction::custom(42);
let debug_str = format!("{:?}", custom);
assert_eq!(debug_str, "Custom(..)");
let quit = AppKeyAction::Quit;
let debug_str = format!("{:?}", quit);
assert_eq!(debug_str, "Quit");
}
}