Provide search functionality through the TUI #134

Merged
wojtek merged 35 commits from 24---provide-search-functionality-through-the-tui into main 2024-02-18 22:12:42 +01:00
4 changed files with 815 additions and 645 deletions
Showing only changes of commit c2920aac81 - Show all commits

File diff suppressed because it is too large Load Diff

View File

@ -14,19 +14,21 @@ use crate::tui::{
event::{Event, EventError, EventReceiver},
};
use super::app::app::IAppInteractCritical;
#[cfg_attr(test, automock)]
pub trait IEventHandler<APP: IAppInteract> {
fn handle_next_event(&self, app: &mut APP) -> Result<(), EventError>;
fn handle_next_event(&self, app: APP) -> Result<APP, EventError>;
}
trait IEventHandlerPrivate<APP: IAppInteract> {
fn handle_key_event(app: &mut APP, key_event: KeyEvent);
fn handle_browse_key_event(app: &mut <APP as IAppInteract>::BS, key_event: KeyEvent);
fn handle_info_key_event(app: &mut <APP as IAppInteract>::IS, key_event: KeyEvent);
fn handle_reload_key_event(app: &mut <APP as IAppInteract>::RS, key_event: KeyEvent);
fn handle_search_key_event(app: &mut <APP as IAppInteract>::SS, key_event: KeyEvent);
fn handle_error_key_event(app: &mut <APP as IAppInteract>::ES, key_event: KeyEvent);
fn handle_critical_key_event(app: &mut <APP as IAppInteract>::CS, key_event: KeyEvent);
fn handle_key_event(app: APP, key_event: KeyEvent) -> APP;
fn handle_browse_key_event(app: <APP as IAppInteract>::BS, key_event: KeyEvent) -> APP;
fn handle_info_key_event(app: <APP as IAppInteract>::IS, key_event: KeyEvent) -> APP;
fn handle_reload_key_event(app: <APP as IAppInteract>::RS, key_event: KeyEvent) -> APP;
fn handle_search_key_event(app: <APP as IAppInteract>::SS, key_event: KeyEvent) -> APP;
fn handle_error_key_event(app: <APP as IAppInteract>::ES, key_event: KeyEvent) -> APP;
fn handle_critical_key_event(app: <APP as IAppInteract>::CS, key_event: KeyEvent) -> APP;
}
pub struct EventHandler {
@ -41,58 +43,52 @@ impl EventHandler {
}
impl<APP: IAppInteract> IEventHandler<APP> for EventHandler {
fn handle_next_event(&self, app: &mut APP) -> Result<(), EventError> {
fn handle_next_event(&self, mut app: APP) -> Result<APP, EventError> {
match self.events.recv()? {
Event::Key(key_event) => Self::handle_key_event(app, key_event),
Event::Key(key_event) => app = Self::handle_key_event(app, key_event),
Event::Mouse(_) => {}
Event::Resize(_, _) => {}
};
Ok(())
Ok(app)
}
}
impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
fn handle_key_event(app: &mut APP, key_event: KeyEvent) {
fn handle_key_event(app: APP, key_event: KeyEvent) -> APP {
if key_event.modifiers == KeyModifiers::CONTROL {
match key_event.code {
// Exit application on `Ctrl-C`.
KeyCode::Char('c') | KeyCode::Char('C') => {
app.force_quit();
return;
}
KeyCode::Char('c') | KeyCode::Char('C') => return app.force_quit(),
_ => {}
}
};
}
match app.state() {
AppState::Browse(browse) => {
<Self as IEventHandlerPrivate<APP>>::handle_browse_key_event(browse, key_event);
<Self as IEventHandlerPrivate<APP>>::handle_browse_key_event(browse, key_event)
}
AppState::Info(info) => {
<Self as IEventHandlerPrivate<APP>>::handle_info_key_event(info, key_event);
<Self as IEventHandlerPrivate<APP>>::handle_info_key_event(info, key_event)
}
AppState::Reload(reload) => {
<Self as IEventHandlerPrivate<APP>>::handle_reload_key_event(reload, key_event);
<Self as IEventHandlerPrivate<APP>>::handle_reload_key_event(reload, key_event)
}
AppState::Search(search) => {
<Self as IEventHandlerPrivate<APP>>::handle_search_key_event(search, key_event);
<Self as IEventHandlerPrivate<APP>>::handle_search_key_event(search, key_event)
}
AppState::Error(error) => {
<Self as IEventHandlerPrivate<APP>>::handle_error_key_event(error, key_event);
<Self as IEventHandlerPrivate<APP>>::handle_error_key_event(error, key_event)
}
AppState::Critical(critical) => {
<Self as IEventHandlerPrivate<APP>>::handle_critical_key_event(critical, key_event);
<Self as IEventHandlerPrivate<APP>>::handle_critical_key_event(critical, key_event)
}
}
}
fn handle_browse_key_event(app: &mut <APP as IAppInteract>::BS, key_event: KeyEvent) {
fn handle_browse_key_event(app: <APP as IAppInteract>::BS, key_event: KeyEvent) -> APP {
match key_event.code {
// Exit application on `ESC` or `q`.
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => {
app.save();
app.quit();
}
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.save_and_quit(),
// Category change.
KeyCode::Left => app.decrement_category(),
KeyCode::Right => app.increment_category(),
@ -108,15 +104,17 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
// Toggle search.
KeyCode::Char('s') | KeyCode::Char('S') => {
if key_event.modifiers == KeyModifiers::CONTROL {
app.begin_search();
app.begin_search()
} else {
app.no_op()
}
}
// Othey keys.
_ => {}
_ => app.no_op(),
}
}
fn handle_info_key_event(app: &mut <APP as IAppInteract>::IS, key_event: KeyEvent) {
fn handle_info_key_event(app: <APP as IAppInteract>::IS, key_event: KeyEvent) -> APP {
match key_event.code {
// Toggle overlay.
KeyCode::Esc
@ -125,11 +123,11 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
| KeyCode::Char('m')
| KeyCode::Char('M') => app.hide_info_overlay(),
// Othey keys.
_ => {}
_ => app.no_op(),
}
}
fn handle_reload_key_event(app: &mut <APP as IAppInteract>::RS, key_event: KeyEvent) {
fn handle_reload_key_event(app: <APP as IAppInteract>::RS, key_event: KeyEvent) -> APP {
match key_event.code {
// Reload keys.
KeyCode::Char('l') | KeyCode::Char('L') => app.reload_library(),
@ -141,22 +139,17 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
| KeyCode::Char('g')
| KeyCode::Char('G') => app.hide_reload_menu(),
// Othey keys.
_ => {}
_ => app.no_op(),
}
}
fn handle_search_key_event(app: &mut <APP as IAppInteract>::SS, key_event: KeyEvent) {
fn handle_search_key_event(app: <APP as IAppInteract>::SS, key_event: KeyEvent) -> APP {
if key_event.modifiers == KeyModifiers::CONTROL {
match key_event.code {
KeyCode::Char('s') | KeyCode::Char('S') => {
app.search_next();
}
KeyCode::Char('g') | KeyCode::Char('G') => {
app.cancel_search();
}
_ => {}
}
return;
return match key_event.code {
KeyCode::Char('s') | KeyCode::Char('S') => app.search_next(),
KeyCode::Char('g') | KeyCode::Char('G') => app.cancel_search(),
_ => app.no_op(),
};
}
match key_event.code {
@ -166,17 +159,18 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
// Return.
KeyCode::Esc | KeyCode::Enter => app.finish_search(),
// Othey keys.
_ => {}
_ => app.no_op(),
}
}
fn handle_error_key_event(app: &mut <APP as IAppInteract>::ES, _key_event: KeyEvent) {
fn handle_error_key_event(app: <APP as IAppInteract>::ES, _key_event: KeyEvent) -> APP {
// Any key dismisses the error.
app.dismiss_error();
app.dismiss_error()
}
fn handle_critical_key_event(_app: &mut <APP as IAppInteract>::CS, _key_event: KeyEvent) {
fn handle_critical_key_event(app: <APP as IAppInteract>::CS, _key_event: KeyEvent) -> APP {
// No action is allowed.
app.no_op()
}
}
// GRCOV_EXCL_STOP

View File

@ -75,7 +75,7 @@ impl<B: Backend, UI: IUi, APP: IAppInteract + IAppAccess> Tui<B, UI, APP> {
) -> Result<(), Error> {
while app.is_running() {
self.terminal.draw(|frame| UI::render(&mut app, frame))?;
handler.handle_next_event(&mut app)?;
app = handler.handle_next_event(app)?;
}
Ok(())
@ -219,10 +219,7 @@ mod tests {
let mut handler = MockIEventHandler::new();
handler
.expect_handle_next_event()
.return_once(|app: &mut App<MockIMusicHoard>| {
app.force_quit();
Ok(())
});
.return_once(|app: App<MockIMusicHoard>| Ok(app.force_quit()));
handler
}

View File

@ -681,12 +681,12 @@ impl IUi for Ui {
let selection = app.selection;
let state = app.state;
Self::render_main_frame(collection, selection, state, frame);
Self::render_main_frame(collection, selection, &state, frame);
match state {
AppState::Info(_) => Self::render_info_overlay(collection, selection, frame),
AppState::Reload(_) => Self::render_reload_overlay(frame),
AppState::Error(ref msg) => Self::render_error_overlay("Error", msg, frame),
AppState::Critical(ref msg) => Self::render_error_overlay("Critical Error", msg, frame),
AppState::Error(msg) => Self::render_error_overlay("Error", msg, frame),
AppState::Critical(msg) => Self::render_error_overlay("Critical Error", msg, frame),
_ => {}
}
}
@ -719,26 +719,23 @@ mod tests {
let mut app = AppPublic {
collection,
selection,
state: &AppState::Browse(()),
state: AppState::Browse(()),
};
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
app.state = &AppState::Info(());
app.state = AppState::Info(());
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
app.state = &AppState::Reload(());
app.state = AppState::Reload(());
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
let binding = AppState::Search(String::new());
app.state = &binding;
app.state = AppState::Search("");
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
let binding = AppState::Error(String::from("get rekt scrub"));
app.state = &binding;
app.state = AppState::Error("get rekt scrub");
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
let binding = AppState::Critical(String::from("get critically rekt scrub"));
app.state = &binding;
app.state = AppState::Critical("get critically rekt scrub");
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
}