mod theme;
mod config;
mod log_action;
mod commit;
mod async_handlers;
use crate::app::{Action, reducer};
use crate::input::InputEvent;
use crate::errors::ApplicationError;
use super::HandlerContext;
use super::init::{preflight_amend, preflight_pick_revert, validate_amend_input};
pub async fn handle_input_event(
ctx: &mut HandlerContext<'_>,
input_event: InputEvent,
) -> Result<(), ApplicationError> {
let action = match ctx.app.component_manager.handle_event(input_event, &ctx.app.state) {
Ok(Some(action)) => action,
Ok(None) => return Ok(()),
Err(e) => {
tracing::error!(error = %e, "Event handling failed");
return Ok(());
}
};
if theme::handle(&action, ctx).await? { return Ok(()); }
if config::handle(&action, ctx).await? { return Ok(()); }
if !run_preflights(&action, ctx) { return Ok(()); }
let action = handle_confirm(action, ctx);
if log_action::handle(&action, ctx).await? { return Ok(()); }
if commit::handle(&action, ctx).await? { return Ok(()); }
if async_handlers::handle(&action, ctx).await? { return Ok(()); }
execute_command(&action, ctx).await?;
ctx.app.state = reducer(ctx.app.state.clone(), action.clone());
post_action_updates(&action, ctx);
Ok(())
}
fn run_preflights(action: &Action, ctx: &mut HandlerContext<'_>) -> bool {
if matches!(action, Action::StartAmend) {
if let Err(msg) = preflight_amend(&ctx.app.state) {
ctx.app.state = reducer(ctx.app.state.clone(), Action::SetStatusError(Some(msg)));
return false;
}
}
if matches!(action, Action::CherryPickCommit(_) | Action::CherryPickSelected) {
if let Err(msg) = preflight_pick_revert(&ctx.app.state, "Cherry-pick") {
ctx.app.state = reducer(ctx.app.state.clone(), Action::SetStatusError(Some(msg)));
return false;
}
}
if matches!(action, Action::RevertCommit(_) | Action::RevertSelected) {
if let Err(msg) = preflight_pick_revert(&ctx.app.state, "Revert") {
ctx.app.state = reducer(ctx.app.state.clone(), Action::SetStatusError(Some(msg)));
return false;
}
}
if matches!(action, Action::CommitSubmit) && ctx.app.state.amend_mode {
if let Err(msg) = validate_amend_input(&ctx.app.state) {
ctx.app.state = reducer(ctx.app.state.clone(), Action::SetStatusError(Some(msg)));
return false;
}
}
true
}
fn handle_confirm(action: Action, ctx: &mut HandlerContext<'_>) -> Action {
if matches!(action, Action::ConfirmExecute) {
if let Some(next) = ctx.app.state.confirm_action.clone() {
ctx.app.state = reducer(ctx.app.state.clone(), Action::ConfirmCancel);
return next;
}
ctx.app.state = reducer(ctx.app.state.clone(), Action::ConfirmCancel);
}
action
}
async fn execute_command(action: &Action, ctx: &mut HandlerContext<'_>) -> Result<(), ApplicationError> {
let was_palette_open = ctx.app.state.command_palette;
if let Some(cmd) = ctx.app.action_to_command(action) {
match cmd.execute(&ctx.app.git_service, &ctx.app.state) {
Ok(result) => {
let needs_commit_refresh = matches!(
action,
Action::Push | Action::ForcePushAfterAmend | Action::Pull
| Action::PullRebase | Action::PullMerge | Action::FetchAllPrune
);
ctx.app.apply_command_result(result)?;
if needs_commit_refresh {
if let Err(e) = ctx.app.component_manager.update(Action::RefreshCommits, &mut ctx.app.state) {
tracing::warn!(error = %e, "Failed to refresh commits after push/pull/fetch");
}
}
if was_palette_open {
ctx.app.state = reducer(ctx.app.state.clone(), Action::HidePalette);
}
}
Err(e) => {
tracing::error!(error = %e, "Command execution failed");
ctx.app.state = reducer(ctx.app.state.clone(), Action::SetStatusError(Some(e.to_string())));
if was_palette_open {
ctx.app.state = reducer(ctx.app.state.clone(), Action::HidePalette);
}
}
}
}
Ok(())
}
fn post_action_updates(action: &Action, ctx: &mut HandlerContext<'_>) {
if matches!(action,
Action::StatusUp | Action::StatusDown | Action::StatusPageUp | Action::StatusPageDown
| Action::StatusTop | Action::StatusBottom | Action::SetStatusEntries(_)
| Action::SetSelectedPath(_) | Action::RefreshStatus | Action::ToggleDiffMode
| Action::SelectConflict(_) | Action::OpenConflictInEditor(_)
) {
ctx.app.component_manager.sync_status_selection(&mut ctx.app.state);
}
if matches!(action,
Action::SetStatusEntries(_) | Action::RefreshStatus | Action::RebaseContinue
| Action::RebaseAbort | Action::RebaseSkip | Action::StartCommit | Action::CancelCommit
| Action::ShowRebaseTodo | Action::ShowConflictsGuided | Action::CherryPickCommit(_)
| Action::CherryPickSelected | Action::RevertCommit(_) | Action::RevertSelected
) {
ctx.app.state = reducer(ctx.app.state.clone(), Action::UpdateWorkflowContext);
}
let is_ui_nav = matches!(action,
Action::PaletteUp | Action::PaletteDown | Action::SetPaletteInput(_)
| Action::ShowLogActionMenu(_) | Action::HideLogActionMenu
| Action::LogActionMenuUp | Action::LogActionMenuDown
);
if !is_ui_nav {
if let Err(e) = ctx.app.component_manager.update(action.clone(), &mut ctx.app.state) {
tracing::error!(error = %e, "Component update failed");
}
}
}