handler_proptest.rs was committed without ever being declared as a module, and it imported a list_size strategy that did not exist, so it never compiled. The proptest recipe filtered on a test name substring that matched no test. Declare the module, add the two strategies it needs, and pass both property test filters to libtest so the recipe runs the ui and handler property tests.
259 lines
7.4 KiB
Rust
259 lines
7.4 KiB
Rust
use lidarr_handlers::LidarrHandler;
|
|
use radarr_handlers::RadarrHandler;
|
|
use readarr_handlers::ReadarrHandler;
|
|
use sonarr_handlers::SonarrHandler;
|
|
|
|
use crate::app::App;
|
|
use crate::app::context_clues::{
|
|
ContextClueProvider, SERVARR_CONTEXT_CLUES, ServarrContextClueProvider,
|
|
};
|
|
use crate::app::key_binding::KeyBinding;
|
|
use crate::event::Key;
|
|
use crate::handlers::keybinding_handler::KeybindingHandler;
|
|
use crate::matches_key;
|
|
use crate::models::servarr_data::ActiveKeybindingBlock;
|
|
use crate::models::servarr_models::KeybindingItem;
|
|
use crate::models::stateful_table::StatefulTable;
|
|
use crate::models::{HorizontallyScrollableText, Route};
|
|
|
|
mod keybinding_handler;
|
|
mod lidarr_handlers;
|
|
mod radarr_handlers;
|
|
mod readarr_handlers;
|
|
mod sonarr_handlers;
|
|
|
|
#[cfg(test)]
|
|
#[path = "handlers_tests.rs"]
|
|
mod handlers_tests;
|
|
|
|
#[cfg(test)]
|
|
mod handler_proptest;
|
|
|
|
#[cfg(test)]
|
|
#[path = "handler_test_utils.rs"]
|
|
pub mod handler_test_utils;
|
|
mod table_handler;
|
|
|
|
pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
|
|
fn handle_key_event(&mut self) {
|
|
let key = self.get_key();
|
|
match key {
|
|
_ if matches_key!(up, key, self.ignore_special_keys()) => {
|
|
if self.is_ready() {
|
|
self.handle_scroll_up();
|
|
}
|
|
}
|
|
_ if matches_key!(down, key, self.ignore_special_keys()) => {
|
|
if self.is_ready() {
|
|
self.handle_scroll_down();
|
|
}
|
|
}
|
|
_ if matches_key!(home, key) => {
|
|
if self.is_ready() {
|
|
self.handle_home();
|
|
}
|
|
}
|
|
_ if matches_key!(end, key) => {
|
|
if self.is_ready() {
|
|
self.handle_end();
|
|
}
|
|
}
|
|
_ if matches_key!(delete, key) => {
|
|
if self.is_ready() {
|
|
self.handle_delete();
|
|
}
|
|
}
|
|
_ if matches_key!(left, key, self.ignore_special_keys())
|
|
|| matches_key!(right, key, self.ignore_special_keys()) =>
|
|
{
|
|
self.handle_left_right_action()
|
|
}
|
|
_ if matches_key!(submit, key) => {
|
|
if self.is_ready() {
|
|
self.handle_submit();
|
|
}
|
|
}
|
|
_ if matches_key!(esc, key) => self.handle_esc(),
|
|
_ => {
|
|
if self.is_ready() {
|
|
self.handle_char_key_event();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle(&mut self) {
|
|
self.handle_key_event();
|
|
}
|
|
|
|
fn accepts(active_block: T) -> bool;
|
|
fn new(key: Key, app: &'a mut App<'b>, active_block: T, context: Option<T>) -> Self;
|
|
fn get_key(&self) -> Key;
|
|
fn ignore_special_keys(&self) -> bool;
|
|
fn is_ready(&self) -> bool;
|
|
fn handle_scroll_up(&mut self);
|
|
fn handle_scroll_down(&mut self);
|
|
fn handle_home(&mut self);
|
|
fn handle_end(&mut self);
|
|
fn handle_delete(&mut self);
|
|
fn handle_left_right_action(&mut self);
|
|
fn handle_submit(&mut self);
|
|
fn handle_esc(&mut self);
|
|
fn handle_char_key_event(&mut self);
|
|
fn app_mut(&mut self) -> &mut App<'b>;
|
|
fn current_route(&self) -> Route;
|
|
}
|
|
|
|
pub fn handle_events(key: Key, app: &mut App<'_>) {
|
|
if matches_key!(next_servarr, key) {
|
|
app.reset();
|
|
app.server_tabs.next();
|
|
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
|
|
app.cancellation_token.cancel();
|
|
} else if matches_key!(previous_servarr, key) {
|
|
app.reset();
|
|
app.server_tabs.previous();
|
|
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
|
|
app.cancellation_token.cancel();
|
|
} else if matches_key!(help, key) && !app.ignore_special_keys_for_textbox_input {
|
|
if app.keymapping_table.is_none() {
|
|
populate_keymapping_table(app);
|
|
} else {
|
|
app.keymapping_table = None;
|
|
}
|
|
} else if matches_key!(esc, key) && app.notification.is_some() {
|
|
app.notification.take();
|
|
} else {
|
|
match app.get_current_route() {
|
|
_ if app.keymapping_table.is_some() => {
|
|
KeybindingHandler::new(key, app, ActiveKeybindingBlock::Help, None).handle();
|
|
}
|
|
Route::Radarr(active_radarr_block, context) => {
|
|
RadarrHandler::new(key, app, active_radarr_block, context).handle()
|
|
}
|
|
Route::Sonarr(active_sonarr_block, context) => {
|
|
SonarrHandler::new(key, app, active_sonarr_block, context).handle()
|
|
}
|
|
Route::Lidarr(active_lidarr_block, context) => {
|
|
LidarrHandler::new(key, app, active_lidarr_block, context).handle()
|
|
}
|
|
Route::Readarr(active_readarr_block, context) => {
|
|
ReadarrHandler::new(key, app, active_readarr_block, context).handle()
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn populate_keymapping_table(app: &mut App<'_>) {
|
|
let context_clue_to_keybinding_item = |key: &KeyBinding, desc: &&str| {
|
|
let (key, alt_key) = if let Some(key1) = key.alt {
|
|
(key.key.to_string(), key1.to_string())
|
|
} else {
|
|
(key.key.to_string(), String::new())
|
|
};
|
|
KeybindingItem {
|
|
key,
|
|
alt_key,
|
|
desc: desc.to_string(),
|
|
}
|
|
};
|
|
let mut keybindings = Vec::new();
|
|
let global_keybindings = Vec::from(SERVARR_CONTEXT_CLUES)
|
|
.iter()
|
|
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc))
|
|
.collect::<Vec<_>>();
|
|
|
|
if let Some(contextual_help) = app.server_tabs.get_active_route_contextual_help() {
|
|
keybindings.extend(
|
|
contextual_help
|
|
.iter()
|
|
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc)),
|
|
);
|
|
}
|
|
|
|
if let Some(contextual_help) = ServarrContextClueProvider::get_context_clues(app) {
|
|
keybindings.extend(
|
|
contextual_help
|
|
.iter()
|
|
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc)),
|
|
);
|
|
}
|
|
|
|
keybindings.extend(global_keybindings);
|
|
|
|
let mut table = StatefulTable::default();
|
|
table.set_items(keybindings);
|
|
app.keymapping_table = Some(table);
|
|
}
|
|
|
|
fn handle_clear_errors(app: &mut App<'_>) {
|
|
if !app.error.text.is_empty() {
|
|
app.error = HorizontallyScrollableText::default();
|
|
}
|
|
}
|
|
|
|
fn handle_prompt_toggle(app: &mut App<'_>, key: Key) {
|
|
match key {
|
|
_ if matches_key!(left, key) || matches_key!(right, key) => match app.get_current_route() {
|
|
Route::Radarr(_, _) => {
|
|
app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm
|
|
}
|
|
Route::Sonarr(_, _) => {
|
|
app.data.sonarr_data.prompt_confirm = !app.data.sonarr_data.prompt_confirm
|
|
}
|
|
Route::Lidarr(_, _) => {
|
|
app.data.lidarr_data.prompt_confirm = !app.data.lidarr_data.prompt_confirm
|
|
}
|
|
Route::Readarr(_, _) => {
|
|
app.data.readarr_data.prompt_confirm = !app.data.readarr_data.prompt_confirm
|
|
}
|
|
_ => (),
|
|
},
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! handle_text_box_left_right_keys {
|
|
($self:expr, $key:expr, $input:expr) => {
|
|
match $self.key {
|
|
_ if $key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.left.key => {
|
|
$input.scroll_left();
|
|
}
|
|
_ if $key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.right.key => {
|
|
$input.scroll_right();
|
|
}
|
|
_ => (),
|
|
}
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! handle_text_box_keys {
|
|
($self:expr, $key:expr, $input:expr) => {
|
|
match $self.key {
|
|
_ if $crate::matches_key!(backspace, $key) => {
|
|
$input.pop();
|
|
}
|
|
Key::Char(character) => {
|
|
$input.push(character);
|
|
}
|
|
_ => (),
|
|
}
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! handle_prompt_left_right_keys {
|
|
($self:expr, $confirm_prompt:expr, $data:ident) => {
|
|
if $self.app.data.$data.selected_block.get_active_block() == $confirm_prompt {
|
|
handle_prompt_toggle($self.app, $self.key);
|
|
} else if $crate::matches_key!(left, $self.key) {
|
|
$self.app.data.$data.selected_block.left();
|
|
} else {
|
|
$self.app.data.$data.selected_block.right();
|
|
}
|
|
};
|
|
}
|