use crossterm::event::Event;
#[derive(Debug)]
pub enum FilterResult {
Pass(Event),
Replace(Event),
Block,
}
impl FilterResult {
pub fn pass(event: Event) -> Self {
FilterResult::Pass(event)
}
pub fn replace(event: Event) -> Self {
FilterResult::Replace(event)
}
pub fn block() -> Self {
FilterResult::Block
}
}
pub type FilterFn = Box<dyn Fn(Event) -> FilterResult + Send + Sync>;
pub struct EventFilter {
filter: FilterFn,
priority: i32,
name: String,
}
impl EventFilter {
pub fn new<F>(name: impl Into<String>, filter: F) -> Self
where
F: Fn(Event) -> FilterResult + Send + Sync + 'static,
{
Self {
filter: Box::new(filter),
priority: 0,
name: name.into(),
}
}
pub fn with_priority<F>(name: impl Into<String>, priority: i32, filter: F) -> Self
where
F: Fn(Event) -> FilterResult + Send + Sync + 'static,
{
Self {
filter: Box::new(filter),
priority,
name: name.into(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn priority(&self) -> i32 {
self.priority
}
pub fn apply(&self, event: Event) -> FilterResult {
(self.filter)(event)
}
}
impl std::fmt::Debug for EventFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EventFilter")
.field("name", &self.name)
.field("priority", &self.priority)
.finish()
}
}
#[derive(Default)]
pub struct FilterChain {
filters: Vec<EventFilter>,
}
impl FilterChain {
pub fn new() -> Self {
Self {
filters: Vec::new(),
}
}
pub fn add(&mut self, filter: EventFilter) {
self.filters.push(filter);
self.filters.sort_by(|a, b| b.priority.cmp(&a.priority));
}
pub fn add_fn<F>(&mut self, name: impl Into<String>, filter: F)
where
F: Fn(Event) -> FilterResult + Send + Sync + 'static,
{
self.add(EventFilter::new(name, filter));
}
pub fn apply(&self, mut event: Event) -> Option<Event> {
for filter in &self.filters {
match filter.apply(event) {
FilterResult::Pass(e) => event = e,
FilterResult::Replace(e) => event = e,
FilterResult::Block => return None,
}
}
Some(event)
}
pub fn is_empty(&self) -> bool {
self.filters.is_empty()
}
pub fn len(&self) -> usize {
self.filters.len()
}
pub fn filter_names(&self) -> Vec<&str> {
self.filters.iter().map(|f| f.name()).collect()
}
}
impl std::fmt::Debug for FilterChain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FilterChain")
.field("filters", &self.filter_names())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
fn make_key_event(code: KeyCode) -> Event {
Event::Key(KeyEvent::new(code, KeyModifiers::NONE))
}
#[test]
fn test_filter_result_pass() {
let event = make_key_event(KeyCode::Char('a'));
let result = FilterResult::pass(event.clone());
assert!(matches!(result, FilterResult::Pass(_)));
}
#[test]
fn test_filter_result_replace() {
let event = make_key_event(KeyCode::Char('a'));
let result = FilterResult::replace(event.clone());
assert!(matches!(result, FilterResult::Replace(_)));
}
#[test]
fn test_filter_result_block() {
let result = FilterResult::block();
assert!(matches!(result, FilterResult::Block));
}
#[test]
fn test_event_filter_creation() {
let filter = EventFilter::new("test", |e| FilterResult::Pass(e));
assert_eq!(filter.name(), "test");
assert_eq!(filter.priority(), 0);
}
#[test]
fn test_event_filter_with_priority() {
let filter = EventFilter::with_priority("test", 10, |e| FilterResult::Pass(e));
assert_eq!(filter.name(), "test");
assert_eq!(filter.priority(), 10);
}
#[test]
fn test_event_filter_apply() {
let filter = EventFilter::new("blocker", |_| FilterResult::Block);
let event = make_key_event(KeyCode::Char('a'));
let result = filter.apply(event);
assert!(matches!(result, FilterResult::Block));
}
#[test]
fn test_filter_chain_empty() {
let chain = FilterChain::new();
assert!(chain.is_empty());
assert_eq!(chain.len(), 0);
}
#[test]
fn test_filter_chain_add() {
let mut chain = FilterChain::new();
chain.add(EventFilter::new("test", |e| FilterResult::Pass(e)));
assert!(!chain.is_empty());
assert_eq!(chain.len(), 1);
}
#[test]
fn test_filter_chain_apply_pass() {
let mut chain = FilterChain::new();
chain.add_fn("pass", |e| FilterResult::Pass(e));
let event = make_key_event(KeyCode::Char('a'));
let result = chain.apply(event);
assert!(result.is_some());
}
#[test]
fn test_filter_chain_apply_block() {
let mut chain = FilterChain::new();
chain.add_fn("block", |_| FilterResult::Block);
let event = make_key_event(KeyCode::Char('a'));
let result = chain.apply(event);
assert!(result.is_none());
}
#[test]
fn test_filter_chain_apply_replace() {
let mut chain = FilterChain::new();
chain.add_fn("replace", |_| {
FilterResult::Replace(make_key_event(KeyCode::Char('b')))
});
let event = make_key_event(KeyCode::Char('a'));
let result = chain.apply(event);
assert!(result.is_some());
if let Some(Event::Key(key)) = result {
assert_eq!(key.code, KeyCode::Char('b'));
} else {
panic!("Expected key event");
}
}
#[test]
fn test_filter_chain_priority_order() {
let mut chain = FilterChain::new();
chain.add(EventFilter::with_priority("low", 0, |e| {
FilterResult::Pass(e)
}));
chain.add(EventFilter::with_priority("high", 10, |e| {
FilterResult::Pass(e)
}));
chain.add(EventFilter::with_priority("medium", 5, |e| {
FilterResult::Pass(e)
}));
let names = chain.filter_names();
assert_eq!(names, vec!["high", "medium", "low"]);
}
#[test]
fn test_filter_chain_multiple_filters() {
let mut chain = FilterChain::new();
chain.add_fn("pass1", |e| FilterResult::Pass(e));
chain.add_fn("replace", |e| {
if let Event::Key(key) = &e {
if key.code == KeyCode::Char('a') {
return FilterResult::Replace(make_key_event(KeyCode::Char('b')));
}
}
FilterResult::Pass(e)
});
chain.add_fn("pass2", |e| FilterResult::Pass(e));
let event = make_key_event(KeyCode::Char('a'));
let result = chain.apply(event);
if let Some(Event::Key(key)) = result {
assert_eq!(key.code, KeyCode::Char('b'));
} else {
panic!("Expected key event");
}
}
#[test]
fn test_filter_chain_block_stops_chain() {
let mut chain = FilterChain::new();
chain.add(EventFilter::with_priority("blocker", 10, |_| {
FilterResult::Block
}));
chain.add(EventFilter::with_priority("replacer", 0, |_| {
FilterResult::Replace(make_key_event(KeyCode::Char('x')))
}));
let event = make_key_event(KeyCode::Char('a'));
let result = chain.apply(event);
assert!(result.is_none());
}
}