use super::{ConfirmClose, Notice, RowState, SessionsOp, SessionsState, Tab};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum InputOutcome {
None,
Close,
Op(SessionsOp),
Past(super::past::PastCmd),
Copy(String),
}
pub(crate) fn handle_event(st: &mut SessionsState, key: KeyEvent) -> InputOutcome {
if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c') {
return InputOutcome::Close;
}
if st.confirm.is_some() {
return confirm_key(st, key);
}
if st.tab == Tab::Past {
if let Some(out) = super::past::input::handle_key(st, key) {
return out;
}
}
match key.code {
KeyCode::Esc => return InputOutcome::Close,
KeyCode::Tab | KeyCode::BackTab => {
st.tab = match st.tab {
Tab::Live => Tab::Past,
Tab::Past => Tab::Live,
};
return InputOutcome::None;
}
KeyCode::Char('/') => return InputOutcome::None,
_ => {}
}
match st.tab {
Tab::Past => InputOutcome::None,
Tab::Live => live_key(st, key),
}
}
fn live_key(st: &mut SessionsState, key: KeyEvent) -> InputOutcome {
match key.code {
KeyCode::Up | KeyCode::Char('k') => st.move_selection(-1),
KeyCode::Down | KeyCode::Char('j') => st.move_selection(1),
KeyCode::Home => st.move_selection(isize::MIN / 2),
KeyCode::End => st.move_selection(isize::MAX / 2),
KeyCode::Char('p') => return pin(st),
KeyCode::Enter => ask_switch(st),
KeyCode::Char('x') => ask_close(st),
_ => {}
}
InputOutcome::None
}
pub(crate) fn busy_notice(st: &mut SessionsState) -> bool {
if st.pending.is_some() {
st.notice = Some(Notice { text: "still waiting on the daemon\u{2026}".into(), error: false });
return true;
}
false
}
pub(crate) fn no_daemon(st: &mut SessionsState) -> bool {
let why = match &st.source {
super::LiveSource::InProcess => "no daemon \u{2014} pin and close need `synaps --attach`",
super::LiveSource::DaemonDown(_) => "the daemon is unreachable \u{2014} try again when it is back",
_ => return false,
};
st.notice = Some(Notice { text: why.into(), error: true });
true
}
fn pin(st: &mut SessionsState) -> InputOutcome {
if no_daemon(st) || busy_notice(st) {
return InputOutcome::None;
}
if let Some(e) = st.selected_pin() {
return InputOutcome::Op(SessionsOp::ClearPin { id: e.journal_id.clone() });
}
let Some(m) = st.selected_meta() else { return InputOutcome::None };
InputOutcome::Op(SessionsOp::Pin { id: m.id.0.clone(), on: !m.pinned })
}
fn ask_close(st: &mut SessionsState) {
if no_daemon(st) || busy_notice(st) {
return;
}
if st.selected_pin().is_some() {
st.notice = Some(Notice { text: "not live \u{2014} p unpins it so it won't come back".into(), error: true });
return;
}
let Some(m) = st.selected_meta() else { return };
let label = super::label(m);
if st.is_own(m) {
st.notice = Some(Notice {
text: "that's this session — /quit or /detach from here".into(),
error: true,
});
return;
}
if RowState::of(m) == RowState::Streaming {
st.notice = Some(Notice {
text: format!("{label} is streaming — close it once the turn ends"),
error: true,
});
return;
}
let (id, clients) = (m.id.0.clone(), m.clients);
st.notice = None;
st.confirm = Some(ConfirmClose { id, label, clients });
}
fn ask_switch(st: &mut SessionsState) {
if no_daemon(st) {
return;
}
if let Some(e) = st.selected_pin() {
st.pending_switch = Some(super::SwitchTarget::Continue { journal_id: e.journal_id.clone(), cwd: e.cwd.clone() });
return;
}
let Some(m) = st.selected_meta() else { return };
if st.is_own(m) {
st.notice = Some(Notice { text: "already on this session".into(), error: false });
return;
}
if m.lifecycle == agent_engine::session::SessionLifecycle::Ending {
st.notice = Some(Notice { text: format!("{} is ending", super::label(m)), error: true });
return;
}
st.pending_switch = Some(super::SwitchTarget::Attach { id: m.id.0.clone() });
}
fn confirm_key(st: &mut SessionsState, key: KeyEvent) -> InputOutcome {
match key.code {
KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter => {
let Some(c) = st.confirm.take() else { return InputOutcome::None };
InputOutcome::Op(SessionsOp::Close { id: c.id, label: c.label })
}
KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
st.confirm = None;
InputOutcome::None
}
_ => InputOutcome::None,
}
}
pub(crate) fn hint(st: &SessionsState) -> String {
if st.confirm.is_some() {
return "y close n cancel".into();
}
if st.tab == Tab::Past {
return super::past::input::hint(st);
}
let mut h = String::from("\u{2191}\u{2193} navigate");
let daemon = !matches!(st.source, super::LiveSource::InProcess | super::LiveSource::DaemonDown(_));
if st.selected_pin().is_some() && daemon {
h.push_str(" enter continue p unpin");
}
if let Some(m) = st.selected_meta().filter(|_| daemon) {
if !st.is_own(m) {
h.push_str(" enter switch");
}
h.push_str(if m.pinned { " p unpin" } else { " p pin" });
if !st.is_own(m) && RowState::of(m) != RowState::Streaming {
h.push_str(" x close");
}
}
h.push_str(" tab past esc close");
h
}