use crossterm::event::{KeyCode, KeyEvent};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
type EventHandler = dyn Fn() -> bool + Send + Sync + 'static;
static GLOBAL_EVENT_HANDLERS: Lazy<Mutex<HashMap<KeyCode, Vec<Arc<EventHandler>>>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub fn on_global_event<F>(key: KeyCode, handler: F)
where
F: Fn() -> bool + Send + Sync + 'static,
{
let mut handlers = GLOBAL_EVENT_HANDLERS.lock();
let handlers_for_key = handlers.entry(key).or_default();
handlers_for_key.push(Arc::new(handler));
}
pub fn process_global_event(event: &KeyEvent) -> bool {
if event.kind != crossterm::event::KeyEventKind::Press {
return false;
}
let handlers = GLOBAL_EVENT_HANDLERS.lock();
if let Some(handlers_for_key) = handlers.get(&event.code) {
for handler in handlers_for_key {
if handler() {
return true;
}
}
}
false
}
#[cfg(test)]
mod test_util {
use std::sync::{Arc, Condvar, Mutex};
#[derive(Debug)]
pub struct TestMutex<T> {
inner: Arc<(Mutex<T>, Condvar)>,
}
impl<T> TestMutex<T> {
pub fn new(value: T) -> Self {
TestMutex {
inner: Arc::new((Mutex::new(value), Condvar::new())),
}
}
pub fn lock(&self) -> std::sync::MutexGuard<'_, T> {
self.inner.0.lock().unwrap()
}
pub fn wait_for<F>(&self, mut condition: F) -> std::sync::MutexGuard<'_, T>
where
F: FnMut(&T) -> bool,
{
let (lock, cvar) = &*self.inner;
let mut guard = lock.lock().unwrap();
while !condition(&*guard) {
guard = cvar.wait(guard).unwrap();
}
guard
}
pub fn notify_all(&self) {
self.inner.1.notify_all();
}
}
impl<T> Clone for TestMutex<T> {
fn clone(&self) -> Self {
TestMutex {
inner: self.inner.clone(),
}
}
}
}
#[cfg(test)]
mod tests {
use super::test_util::TestMutex;
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
fn test_global_event_handling() {
use crate::test_utils::TEST_MUTEX;
let _lock = TEST_MUTEX.lock();
GLOBAL_EVENT_HANDLERS.lock().clear();
let test_state = TestMutex::new((false, false)); let test_state_clone = test_state.clone();
on_global_event(KeyCode::Char('a'), move || {
let mut state = test_state_clone.lock();
state.0 = true; state.1 = true; test_state_clone.notify_all();
true
});
let event = KeyEvent {
code: KeyCode::Char('a'),
modifiers: crossterm::event::KeyModifiers::NONE,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE,
};
let result = process_global_event(&event);
let state = test_state.wait_for(|s| s.1);
assert!(state.0, "Handler was not called. Event: {:?}", event);
assert!(result, "Event should be marked as handled");
}
#[test]
fn test_event_propagation_stop() {
use crate::test_utils::TEST_MUTEX;
let _lock = TEST_MUTEX.lock();
GLOBAL_EVENT_HANDLERS.lock().clear();
let test_key = KeyCode::Char('y');
let first_handler_called = Arc::new(AtomicBool::new(false));
let second_handler_called = Arc::new(AtomicBool::new(false));
{
let first_called = first_handler_called.clone();
on_global_event(test_key, move || {
first_called.store(true, Ordering::SeqCst);
true });
}
{
let second_called = second_handler_called.clone();
on_global_event(test_key, move || {
second_called.store(true, Ordering::SeqCst);
true
});
}
{
let handlers = GLOBAL_EVENT_HANDLERS.lock();
let handlers_for_key = handlers.get(&test_key).unwrap_or(&vec![]).len();
assert_eq!(handlers_for_key, 2, "Expected 2 handlers to be registered");
}
let event = KeyEvent {
code: test_key,
modifiers: crossterm::event::KeyModifiers::NONE,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE,
};
let mut result = false;
for _ in 0..5 {
result = process_global_event(&event);
if result {
break;
}
}
assert!(
first_handler_called.load(Ordering::SeqCst),
"First handler was not called"
);
assert!(
!second_handler_called.load(Ordering::SeqCst),
"Second handler was called but should have been skipped"
);
assert!(result, "Event should be marked as handled");
}
#[test]
fn test_event_propagation_continue() {
use crate::test_utils::TEST_MUTEX;
let _lock = TEST_MUTEX.lock();
GLOBAL_EVENT_HANDLERS.lock().clear();
let handler_call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let test_key = KeyCode::Char('z');
{
let counter = handler_call_count.clone();
on_global_event(test_key, move || {
counter.fetch_add(1, Ordering::SeqCst);
false });
}
{
let counter = handler_call_count.clone();
on_global_event(test_key, move || {
counter.fetch_add(1, Ordering::SeqCst);
false });
}
{
let handlers = GLOBAL_EVENT_HANDLERS.lock();
let handlers_for_key = handlers.get(&test_key).unwrap_or(&vec![]).len();
assert_eq!(handlers_for_key, 2, "Expected 2 handlers to be registered");
}
let event = KeyEvent {
code: test_key,
modifiers: crossterm::event::KeyModifiers::NONE,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE,
};
let mut result = false;
for _ in 0..5 {
result = process_global_event(&event);
if result {
break;
}
}
let count = handler_call_count.load(Ordering::SeqCst);
assert!(
count >= 2,
"Expected both handlers to be called at least once, but got {} calls",
count
);
assert!(
!result,
"Event should not be marked as handled when all handlers return false"
);
}
#[test]
fn test_no_handlers() {
use crate::test_utils::TEST_MUTEX;
let _lock = TEST_MUTEX.lock();
GLOBAL_EVENT_HANDLERS.lock().clear();
let event = KeyEvent::new(KeyCode::Char('x'), crossterm::event::KeyModifiers::NONE);
assert!(!process_global_event(&event));
}
#[test]
fn test_multiple_handlers_same_key() {
use crate::test_utils::TEST_MUTEX;
let _lock = TEST_MUTEX.lock();
GLOBAL_EVENT_HANDLERS.lock().clear();
let counter1 = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let counter2 = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let c1 = counter1.clone();
on_global_event(KeyCode::Char('a'), move || {
c1.fetch_add(1, Ordering::SeqCst);
false });
let c2 = counter2.clone();
on_global_event(KeyCode::Char('a'), move || {
c2.fetch_add(1, Ordering::SeqCst);
true });
let event = KeyEvent::new(KeyCode::Char('a'), crossterm::event::KeyModifiers::NONE);
let result = process_global_event(&event);
assert_eq!(
counter1.load(Ordering::SeqCst),
1,
"First handler was not called or called multiple times"
);
assert_eq!(
counter2.load(Ordering::SeqCst),
1,
"Second handler was not called or called multiple times"
);
assert!(
result,
"Event should be marked as handled when any handler returns true"
);
}
#[test]
fn test_release_handlers() {
use crate::test_utils::TEST_MUTEX;
let _lock = TEST_MUTEX.lock();
GLOBAL_EVENT_HANDLERS.lock().clear();
let counter = Arc::new(std::sync::atomic::AtomicUsize::new(0));
{
let c = counter.clone();
on_global_event(KeyCode::Char('z'), move || {
c.fetch_add(1, Ordering::SeqCst);
true
});
let event = KeyEvent::new(KeyCode::Char('z'), crossterm::event::KeyModifiers::NONE);
assert!(
process_global_event(&event),
"Handler should process the event when in scope"
);
assert_eq!(
counter.load(Ordering::SeqCst),
1,
"Counter should be incremented when handler is called"
);
}
let event = KeyEvent::new(KeyCode::Char('z'), crossterm::event::KeyModifiers::NONE);
assert!(
process_global_event(&event),
"Handler should still process the event"
);
let final_count = counter.load(Ordering::SeqCst);
assert!(
final_count == 1 || final_count == 2,
"Counter should be 1 or 2, got {}",
final_count
);
}
#[test]
fn test_key_modifiers() {
use crate::test_utils::TEST_MUTEX;
let _lock = TEST_MUTEX.lock();
GLOBAL_EVENT_HANDLERS.lock().clear();
let counter = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let c = counter.clone();
on_global_event(KeyCode::Char('c'), move || {
c.fetch_add(1, Ordering::SeqCst);
true
});
let events = [
KeyEvent::new(KeyCode::Char('c'), crossterm::event::KeyModifiers::CONTROL),
KeyEvent::new(KeyCode::Char('c'), crossterm::event::KeyModifiers::SHIFT),
KeyEvent::new(KeyCode::Char('c'), crossterm::event::KeyModifiers::ALT),
KeyEvent::new(KeyCode::Char('c'), crossterm::event::KeyModifiers::NONE),
];
for event in &events {
let result = process_global_event(event);
assert!(
result,
"Handler should have been called for event: {:?}",
event
);
}
assert_eq!(
counter.load(Ordering::SeqCst),
events.len(),
"Handler should have been called {} times",
events.len()
);
}
#[test]
fn test_remove_handlers() {
use crate::test_utils::TEST_MUTEX;
let _lock = TEST_MUTEX.lock();
GLOBAL_EVENT_HANDLERS.lock().clear();
let counter = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let c = counter.clone();
on_global_event(KeyCode::Char('x'), move || {
c.fetch_add(1, Ordering::SeqCst);
true
});
GLOBAL_EVENT_HANDLERS.lock().clear();
let event = KeyEvent::new(KeyCode::Char('x'), crossterm::event::KeyModifiers::NONE);
assert!(!process_global_event(&event));
assert_eq!(counter.load(Ordering::SeqCst), 0);
}
}