use crate::spectre::jobs::JobStatus;
use crate::tui::app::action::Action;
use crate::tui::app::overlay::{
ConfigFormState, ConfirmAction, ConfirmOverlay, LogOverlay, Overlay, TextInput,
};
use crate::tui::app::state::{App, Tab};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
pub fn on_key(app: &mut App, key: KeyEvent) -> Action {
if app.overlay.is_active() {
return overlay_key(app, key);
}
if let Some(a) = global_key(app, key) {
return a;
}
content_key(app, key)
}
fn global_key(app: &mut App, key: KeyEvent) -> Option<Action> {
match (key.code, key.modifiers) {
(KeyCode::Char('q'), _) | (KeyCode::Char('c'), KeyModifiers::CONTROL) => Some(Action::Quit),
(KeyCode::Char('r'), _) => Some(Action::Refresh),
(KeyCode::Char('l'), _) => {
app.overlay = Overlay::Log(LogOverlay::new(crate::tui::app::data::load_log_lines()));
Some(Action::None)
}
(KeyCode::Char('?'), _) => {
app.overlay = Overlay::Help;
Some(Action::None)
}
(KeyCode::Tab, _) | (KeyCode::Char(']'), _) => {
app.tab = app.tab.next();
Some(Action::None)
}
(KeyCode::BackTab, _) | (KeyCode::Char('['), _) => {
app.tab = app.tab.prev();
Some(Action::None)
}
_ => None,
}
}
fn content_key(app: &mut App, key: KeyEvent) -> Action {
match (key.code, app.tab) {
(KeyCode::Char('j') | KeyCode::Down, _) => {
app.move_selection(1);
Action::None
}
(KeyCode::Char('k') | KeyCode::Up, _) => {
app.move_selection(-1);
Action::None
}
(KeyCode::Enter | KeyCode::Char('i'), Tab::Config) => {
if let Some(field) = app.selected_config_field() {
app.overlay = Overlay::Form(ConfigFormState {
field_idx: app.selected_config,
key: field.key.clone(),
hint: field.hint,
value: TextInput::new(&field.value),
});
}
Action::None
}
(KeyCode::Char('x'), Tab::Jobs) => cancel_job_prompt(app),
_ => Action::None,
}
}
fn cancel_job_prompt(app: &mut App) -> Action {
let Some(job) = app.jobs.get(app.selected_job) else {
return Action::None;
};
if job.status != JobStatus::Running {
return Action::Status(
"Selected job is not running".into(),
crate::tui::app::state::StatusKind::Info,
);
}
app.overlay = Overlay::Confirm(ConfirmOverlay {
title: "Cancel Job".into(),
message: format!("Kill running job {}?", job.id),
action: ConfirmAction::CancelJob(app.selected_job),
});
Action::None
}
fn overlay_key(app: &mut App, key: KeyEvent) -> Action {
match &app.overlay {
Overlay::None => Action::None,
Overlay::Help => {
if matches!(
key.code,
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('?')
) {
app.overlay = Overlay::None;
}
Action::None
}
Overlay::Log(_) => log_overlay_key(app, key),
Overlay::Confirm(_) => confirm_overlay_key(app, key),
Overlay::Form(_) => form_overlay_key(app, key),
}
}
fn log_overlay_key(app: &mut App, key: KeyEvent) -> Action {
let Overlay::Log(log) = &mut app.overlay else {
return Action::None;
};
match key.code {
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('l') => {
app.overlay = Overlay::None;
}
KeyCode::Char('j') | KeyCode::Down if log.scroll + 1 < log.lines.len() => {
log.scroll += 1;
}
KeyCode::Char('k') | KeyCode::Up => {
log.scroll = log.scroll.saturating_sub(1);
}
KeyCode::Char('g') => log.scroll = 0,
KeyCode::Char('G') => log.scroll = log.lines.len().saturating_sub(1),
_ => {}
}
Action::None
}
fn confirm_overlay_key(app: &mut App, key: KeyEvent) -> Action {
let Overlay::Confirm(confirm) = &app.overlay else {
return Action::None;
};
match key.code {
KeyCode::Enter | KeyCode::Char('y') | KeyCode::Char('Y') => {
let action = match confirm.action {
ConfirmAction::CancelJob(idx) => Action::CancelJob(idx),
};
app.overlay = Overlay::None;
action
}
KeyCode::Esc | KeyCode::Char('n') | KeyCode::Char('N') => {
app.overlay = Overlay::None;
Action::None
}
_ => Action::None,
}
}
fn form_overlay_key(app: &mut App, key: KeyEvent) -> Action {
let Overlay::Form(form) = &mut app.overlay else {
return Action::None;
};
match key.code {
KeyCode::Esc => {
app.overlay = Overlay::None;
Action::None
}
KeyCode::Enter => {
let new_value = form.value.as_str().to_string();
let idx = form.field_idx;
if let Some(field) = app.config_fields.get_mut(idx) {
field.value = new_value;
}
app.overlay = Overlay::None;
Action::SaveConfig
}
KeyCode::Backspace => {
form.value.backspace();
Action::None
}
KeyCode::Left => {
form.value.move_left();
Action::None
}
KeyCode::Right => {
form.value.move_right();
Action::None
}
KeyCode::Home => {
form.value.home();
Action::None
}
KeyCode::End => {
form.value.end();
Action::None
}
KeyCode::Char(c) => {
form.value.insert_char(c);
Action::None
}
_ => Action::None,
}
}