use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
use super::keys::{self, KeyChord};
use super::{Confirm, ConfirmPolicy, KeyOutcome, TermCommand};
use crate::app::mode::Mode;
use crate::app::state::AppState;
pub fn handle_key(state: &mut AppState, key: &KeyEvent) -> KeyOutcome {
if key.kind == KeyEventKind::Release {
return if state.terminal.shell_focused() {
KeyOutcome::Handled
} else {
KeyOutcome::NotMine
};
}
if let Some(confirm) = state.terminal.pending.clone() {
state.dirty = true;
return match key.code {
KeyCode::Char('y' | 'Y') | KeyCode::Enter => {
state.terminal.pending = None;
match confirm {
Confirm::Quit { cancel, .. } => KeyOutcome::Quit { cancel },
Confirm::Close { .. } => {
state.terminal.close_active();
KeyOutcome::Handled
}
}
}
KeyCode::Char('n' | 'N') | KeyCode::Esc => {
state.terminal.pending = None;
KeyOutcome::Handled
}
_ => KeyOutcome::Handled,
};
}
let bindings = &state.config.keymap.terminal;
let is = |action: &str| {
bindings
.get(action)
.and_then(|b| KeyChord::parse(b))
.is_some_and(|chord| chord.matches(key))
};
if is("toggle") {
let result = state.terminal.toggle(&state.config.terminal, &state.cwd);
after_focus_change(state, result);
return KeyOutcome::Handled;
}
if is("focus") {
let result = state
.terminal
.switch_focus(&state.config.terminal, &state.cwd);
after_focus_change(state, result);
return KeyOutcome::Handled;
}
if !state.terminal.shell_focused() {
return KeyOutcome::NotMine;
}
state.dirty = true;
let panel = &mut state.terminal;
if is("next_shell") {
panel.cycle(1);
} else if is("prev_shell") {
panel.cycle(-1);
} else if is("scroll_up") || is("scroll_down") {
if let Some(session) = panel.sessions.get(panel.active) {
let page = session.screen().screen().size().0.max(2) as isize / 2;
session.scroll(if is("scroll_up") { page } else { -page });
}
} else if let Some(session) = panel.sessions.get(panel.active) {
let application_cursor = session.screen().screen().application_cursor();
if let Some(bytes) = keys::encode(key, application_cursor) {
session.scroll_to_bottom();
session.write(bytes);
}
}
KeyOutcome::Handled
}
fn after_focus_change(state: &mut AppState, result: Result<(), String>) {
if let Err(e) = result {
state.set_error(format!("terminal: {e}"));
}
if state.terminal.shell_focused() && !matches!(state.mode, Mode::Navigation) {
state.mode = Mode::Navigation;
state.clear_filter();
}
state.pending_nav_key = None;
state.dirty = true;
}
pub fn run_command(state: &mut AppState, command: TermCommand) {
let result = match command {
TermCommand::Toggle => state.terminal.toggle(&state.config.terminal, &state.cwd),
TermCommand::New(profile) => {
state
.terminal
.new_shell(&state.config.terminal, profile.as_deref(), &state.cwd)
}
TermCommand::Close => {
if state.terminal.sessions.is_empty() {
Err("there is no shell to close".to_owned())
} else {
let policy = ConfirmPolicy::parse(&state.config.terminal.confirm_close)
.unwrap_or(ConfirmPolicy::WhenBusy);
state.terminal.request_close(policy);
Ok(())
}
}
TermCommand::Select(n) => state.terminal.select(n),
TermCommand::Max => {
if state.terminal.is_visible() {
state.terminal.toggle_maximized();
Ok(())
} else {
Err("the terminal panel is not open".to_owned())
}
}
};
if let Err(e) = result {
state.set_error(format!("term: {e}"));
}
state.dirty = true;
}
pub fn allow_quit(state: &mut AppState, cancel: bool) -> bool {
let policy = ConfirmPolicy::parse(&state.config.terminal.confirm_quit)
.unwrap_or(ConfirmPolicy::WhenBusy);
match state.terminal.quit_confirmation(policy, cancel) {
Some(confirm) => {
state.terminal.ask(confirm);
state.dirty = true;
false
}
None => true,
}
}