use super::{
InputAction, PickerViewport, SessionPickerViewport, SystemPromptViewport,
printable_char_for_text_input,
};
use crate::tui::state::{AuthPickerMode, MissionControlState};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[derive(Debug, Clone, Copy)]
pub(super) struct ModalKeyViewports {
pub(super) system_prompt: SystemPromptViewport,
pub(super) rewind_modal: SystemPromptViewport,
pub(super) usage_modal: SystemPromptViewport,
pub(super) login_picker: PickerViewport,
pub(super) model_picker: PickerViewport,
pub(super) sessions_modal: SessionPickerViewport,
pub(super) models_modal: PickerViewport,
}
pub(super) fn handle_modal_key(
key: KeyEvent,
state: &mut MissionControlState,
viewports: ModalKeyViewports,
) -> Option<InputAction> {
if state.session_picker_visible() {
return Some(handle_session_picker_key(
key,
state,
viewports.sessions_modal,
));
}
if state.skills_modal_visible() {
return Some(handle_skills_modal_key(key, state, viewports.models_modal));
}
if state.mcp_modal_visible() {
return Some(handle_mcp_modal_key(key, state, viewports.models_modal));
}
if state.tools_modal_visible() {
return Some(handle_tools_modal_key(key, state, viewports.models_modal));
}
if state.subagents_modal_visible() {
return Some(handle_subagents_modal_key(
key,
state,
viewports.models_modal,
));
}
if state.models_modal_visible() {
return Some(handle_models_modal_key(key, state, viewports.models_modal));
}
if state.usage_modal_visible() {
return Some(handle_usage_modal_key(key, state, viewports.usage_modal));
}
if state.system_prompt_modal_visible() {
return Some(handle_system_prompt_modal_key(
key,
state,
viewports.system_prompt,
));
}
if state.rewind_modal_visible() {
return Some(handle_rewind_modal_key(key, state, viewports.rewind_modal));
}
if state.modals.logout_confirmation.is_some() {
return Some(handle_logout_confirmation_key(key, state));
}
if state
.modals
.custom_provider_replacement_confirmation
.is_some()
{
return Some(handle_custom_provider_replacement_key(key, state));
}
if state.modals.custom_provider_setup.is_some() {
return Some(handle_custom_provider_setup_key(key, state));
}
if state.modals.active_login.is_some() {
return Some(handle_active_login_key(key, state));
}
if state.modals.login_picker.is_some() {
return Some(handle_login_picker_key(key, state, viewports.login_picker));
}
if state.modals.model_picker.is_some() {
return Some(handle_model_picker_key(key, state, viewports.model_picker));
}
None
}
fn handle_session_picker_key(
key: KeyEvent,
state: &mut MissionControlState,
viewport: SessionPickerViewport,
) -> InputAction {
let focus = state
.modals
.session_picker
.as_ref()
.map(|picker| picker.focus)
.unwrap_or(crate::tui::state::SessionPickerFocus::List);
let focus = if viewport.preview_visible {
focus
} else {
crate::tui::state::SessionPickerFocus::List
};
match (key.code, key.modifiers) {
(KeyCode::Tab, KeyModifiers::NONE) => {
if viewport.preview_visible {
state.session_picker_focus_next();
}
InputAction::None
}
(KeyCode::BackTab, modifiers)
if modifiers.is_empty() || modifiers == KeyModifiers::SHIFT =>
{
if viewport.preview_visible {
state.session_picker_focus_previous();
}
InputAction::None
}
(KeyCode::Up, KeyModifiers::NONE) => {
match focus {
crate::tui::state::SessionPickerFocus::List => {
state.session_picker_up(viewport.list_rows as usize);
}
crate::tui::state::SessionPickerFocus::Preview => {
state.session_picker_preview_up_by(
1,
viewport.preview_rows,
viewport.preview_width,
);
}
}
InputAction::None
}
(KeyCode::Down, KeyModifiers::NONE) => {
match focus {
crate::tui::state::SessionPickerFocus::List => {
state.session_picker_down(viewport.list_rows as usize);
}
crate::tui::state::SessionPickerFocus::Preview => {
state.session_picker_preview_down_by(
1,
viewport.preview_rows,
viewport.preview_width,
);
}
}
InputAction::None
}
(KeyCode::PageUp, KeyModifiers::NONE) => {
match focus {
crate::tui::state::SessionPickerFocus::List => {
state.session_picker_page_up(viewport.list_rows as usize);
}
crate::tui::state::SessionPickerFocus::Preview => {
state.session_picker_preview_up_by(
viewport.preview_rows,
viewport.preview_rows,
viewport.preview_width,
);
}
}
InputAction::None
}
(KeyCode::PageDown, KeyModifiers::NONE) => {
match focus {
crate::tui::state::SessionPickerFocus::List => {
state.session_picker_page_down(viewport.list_rows as usize);
}
crate::tui::state::SessionPickerFocus::Preview => {
state.session_picker_preview_down_by(
viewport.preview_rows,
viewport.preview_rows,
viewport.preview_width,
);
}
}
InputAction::None
}
(KeyCode::Enter, KeyModifiers::NONE) => {
if let Some(session_id) = state.selected_session_id() {
state.close_session_picker();
InputAction::SelectSession(session_id)
} else {
InputAction::None
}
}
(KeyCode::Esc, KeyModifiers::NONE) => {
state.close_session_picker();
InputAction::None
}
_ => InputAction::None,
}
}
fn handle_skills_modal_key(
key: KeyEvent,
state: &mut MissionControlState,
viewport: PickerViewport,
) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Tab, KeyModifiers::NONE) => InputAction::ToggleSkillsSettingsScope,
(KeyCode::Up, KeyModifiers::NONE) => {
state.skills_modal_up(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Down, KeyModifiers::NONE) => {
state.skills_modal_down(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Enter, KeyModifiers::NONE) => state
.toggle_selected_skill()
.map_or(InputAction::None, |(name, enabled)| {
InputAction::SetSkillEnabled { name, enabled }
}),
(KeyCode::Esc, KeyModifiers::NONE) => {
state.close_skills_modal();
InputAction::None
}
_ => InputAction::None,
}
}
fn handle_mcp_modal_key(
key: KeyEvent,
state: &mut MissionControlState,
viewport: PickerViewport,
) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Up, KeyModifiers::NONE) => {
state.mcp_modal_up(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Down, KeyModifiers::NONE) => {
state.mcp_modal_down(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Enter, KeyModifiers::NONE) => state
.toggle_selected_mcp_server()
.map_or(InputAction::None, |(name, enabled)| {
InputAction::SetMcpServerEnabled { name, enabled }
}),
(KeyCode::Esc, KeyModifiers::NONE) => {
state.close_mcp_modal();
InputAction::None
}
_ => InputAction::None,
}
}
fn handle_tools_modal_key(
key: KeyEvent,
state: &mut MissionControlState,
viewport: PickerViewport,
) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Tab, KeyModifiers::NONE) => InputAction::ToggleToolsSettingsScope,
(KeyCode::Up, KeyModifiers::NONE) => {
state.tools_modal_up(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Down, KeyModifiers::NONE) => {
state.tools_modal_down(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Enter, KeyModifiers::NONE) => state
.toggle_selected_tool()
.map_or(InputAction::None, |(name, enabled)| {
InputAction::SetToolEnabled { name, enabled }
}),
(KeyCode::Esc, KeyModifiers::NONE) => {
state.close_tools_modal();
InputAction::None
}
_ => InputAction::None,
}
}
fn handle_subagents_modal_key(
key: KeyEvent,
state: &mut MissionControlState,
viewport: PickerViewport,
) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Tab, KeyModifiers::NONE) => InputAction::ToggleSubagentsSettingsScope,
(KeyCode::Up, KeyModifiers::NONE) => {
state.subagents_modal_up(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Down, KeyModifiers::NONE) => {
state.subagents_modal_down(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Enter, KeyModifiers::NONE) => state
.toggle_selected_subagent_profile()
.map_or(InputAction::None, |(id, enabled)| {
InputAction::SetSubagentProfileEnabled { id, enabled }
}),
(KeyCode::Esc, KeyModifiers::NONE) => {
state.close_subagents_modal();
InputAction::None
}
_ => InputAction::None,
}
}
fn handle_models_modal_key(
key: KeyEvent,
state: &mut MissionControlState,
viewport: PickerViewport,
) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Tab, KeyModifiers::NONE) => InputAction::ToggleModelsSettingsScope,
(KeyCode::Up, KeyModifiers::NONE) => {
state.models_modal_up(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Down, KeyModifiers::NONE) => {
state.models_modal_down(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Enter, KeyModifiers::NONE) => state
.toggle_selected_model_enabled()
.map_or(InputAction::None, |(id, enabled)| {
InputAction::SetModelEnabled { id, enabled }
}),
(KeyCode::Esc, KeyModifiers::NONE) => {
state.close_models_modal();
InputAction::None
}
_ => InputAction::None,
}
}
fn handle_system_prompt_modal_key(
key: KeyEvent,
state: &mut MissionControlState,
viewport: SystemPromptViewport,
) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Esc | KeyCode::Char('q'), KeyModifiers::NONE) => {
state.close_system_prompt_modal();
}
(KeyCode::Up, KeyModifiers::NONE | KeyModifiers::ALT) => {
state.scroll_system_prompt_up_by(1, viewport.visible_rows, viewport.wrap_width);
}
(KeyCode::Down, KeyModifiers::NONE | KeyModifiers::ALT) => {
state.scroll_system_prompt_down_by(1, viewport.visible_rows, viewport.wrap_width);
}
(KeyCode::PageUp, KeyModifiers::NONE) => {
state.scroll_system_prompt_up_by(
viewport.visible_rows,
viewport.visible_rows,
viewport.wrap_width,
);
}
(KeyCode::PageDown, KeyModifiers::NONE) => {
state.scroll_system_prompt_down_by(
viewport.visible_rows,
viewport.visible_rows,
viewport.wrap_width,
);
}
(KeyCode::Home, KeyModifiers::NONE) => {
state.scroll_system_prompt_up_by(u16::MAX, viewport.visible_rows, viewport.wrap_width);
}
(KeyCode::End, KeyModifiers::NONE) => {
state.scroll_system_prompt_down_by(
u16::MAX,
viewport.visible_rows,
viewport.wrap_width,
);
}
_ => {}
}
InputAction::None
}
fn handle_usage_modal_key(
key: KeyEvent,
state: &mut MissionControlState,
viewport: SystemPromptViewport,
) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Esc | KeyCode::Char('q'), KeyModifiers::NONE) => {
state.close_usage_modal();
}
(KeyCode::Up, KeyModifiers::NONE | KeyModifiers::ALT) => {
state.scroll_usage_up_by(1, viewport.visible_rows, viewport.wrap_width);
}
(KeyCode::Down, KeyModifiers::NONE | KeyModifiers::ALT) => {
state.scroll_usage_down_by(1, viewport.visible_rows, viewport.wrap_width);
}
(KeyCode::PageUp, KeyModifiers::NONE) => {
state.scroll_usage_up_by(
viewport.visible_rows,
viewport.visible_rows,
viewport.wrap_width,
);
}
(KeyCode::PageDown, KeyModifiers::NONE) => {
state.scroll_usage_down_by(
viewport.visible_rows,
viewport.visible_rows,
viewport.wrap_width,
);
}
(KeyCode::Home, KeyModifiers::NONE) => {
state.scroll_usage_up_by(u16::MAX, viewport.visible_rows, viewport.wrap_width);
}
(KeyCode::End, KeyModifiers::NONE) => {
state.scroll_usage_down_by(u16::MAX, viewport.visible_rows, viewport.wrap_width);
}
_ => {}
}
InputAction::None
}
fn handle_rewind_modal_key(
key: KeyEvent,
state: &mut MissionControlState,
viewport: SystemPromptViewport,
) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Enter, KeyModifiers::NONE) => {
if state.modals.rewind_modal.as_ref().is_some_and(|modal| {
(modal.plan.is_some() && !modal.dry_run) || !modal.plans.is_empty()
}) {
InputAction::ConfirmRewind
} else {
InputAction::None
}
}
(KeyCode::Esc, KeyModifiers::NONE) => {
state.close_rewind_modal();
InputAction::CancelRewind
}
(KeyCode::Up, KeyModifiers::NONE | KeyModifiers::ALT) => {
if !state.rewind_picker_up() {
state.scroll_rewind_up_by(1, viewport.visible_rows, viewport.wrap_width);
}
InputAction::None
}
(KeyCode::Down, KeyModifiers::NONE | KeyModifiers::ALT) => {
if !state.rewind_picker_down() {
state.scroll_rewind_down_by(1, viewport.visible_rows, viewport.wrap_width);
}
InputAction::None
}
(KeyCode::PageUp, KeyModifiers::NONE) => {
state.scroll_rewind_up_by(
viewport.visible_rows,
viewport.visible_rows,
viewport.wrap_width,
);
InputAction::None
}
(KeyCode::PageDown, KeyModifiers::NONE) => {
state.scroll_rewind_down_by(
viewport.visible_rows,
viewport.visible_rows,
viewport.wrap_width,
);
InputAction::None
}
_ => InputAction::None,
}
}
fn handle_logout_confirmation_key(key: KeyEvent, state: &mut MissionControlState) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Enter, KeyModifiers::NONE) => {
if let Some(confirmation) = state.modals.logout_confirmation.clone() {
state.close_logout_confirmation();
InputAction::ConfirmLogout(confirmation.provider_id)
} else {
InputAction::None
}
}
(KeyCode::Esc, KeyModifiers::NONE) => {
state.close_logout_confirmation();
InputAction::CancelLogout
}
_ => InputAction::None,
}
}
fn handle_custom_provider_replacement_key(
key: KeyEvent,
state: &mut MissionControlState,
) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Enter, KeyModifiers::NONE) => InputAction::ConfirmCustomProviderReplacement,
(KeyCode::Esc, KeyModifiers::NONE) => {
state.close_custom_provider_replacement_confirmation();
InputAction::CancelCustomProviderReplacement
}
_ => InputAction::None,
}
}
fn handle_custom_provider_setup_key(key: KeyEvent, state: &mut MissionControlState) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Esc, KeyModifiers::NONE) => InputAction::CancelLogin,
(KeyCode::Enter, KeyModifiers::NONE) => InputAction::SubmitCustomProviderSetupStep,
(KeyCode::Backspace, KeyModifiers::NONE) => {
state.backspace_custom_provider_setup();
InputAction::None
}
(KeyCode::Char(_), _) => {
if let Some(ch) = printable_char_for_text_input(key) {
state.insert_custom_provider_setup_char(ch);
}
InputAction::None
}
_ => InputAction::None,
}
}
fn handle_active_login_key(key: KeyEvent, state: &mut MissionControlState) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Esc, KeyModifiers::NONE) => InputAction::CancelLogin,
(KeyCode::Char('y'), KeyModifiers::CONTROL) => InputAction::CopyLoginUrl,
(KeyCode::Char('o'), KeyModifiers::CONTROL) => InputAction::OpenLoginUrl,
(KeyCode::Enter, KeyModifiers::NONE) => {
if let Some(input) = state.take_login_fallback_input() {
InputAction::SubmitLoginFallback(input)
} else {
InputAction::None
}
}
(KeyCode::Backspace, KeyModifiers::NONE) => {
state.backspace_login_fallback();
InputAction::None
}
(KeyCode::Char(_), _) => {
if let Some(ch) = printable_char_for_text_input(key) {
state.insert_login_fallback_char(ch);
}
InputAction::None
}
_ => InputAction::None,
}
}
fn handle_login_picker_key(
key: KeyEvent,
state: &mut MissionControlState,
viewport: PickerViewport,
) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Up, KeyModifiers::NONE) => {
state.login_picker_up(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Down, KeyModifiers::NONE) => {
state.login_picker_down(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Enter, KeyModifiers::NONE) => {
let mode = state.modals.login_picker.as_ref().map(|picker| picker.mode);
if let Some(provider_id) = state.selected_login_provider_id() {
state.close_login_picker();
if mode == Some(AuthPickerMode::Logout) {
InputAction::SelectLogoutProvider(provider_id)
} else {
InputAction::SelectLoginProvider(provider_id)
}
} else {
InputAction::None
}
}
(KeyCode::Esc, KeyModifiers::NONE) => {
state.close_login_picker();
InputAction::None
}
_ => InputAction::None,
}
}
fn handle_model_picker_key(
key: KeyEvent,
state: &mut MissionControlState,
viewport: PickerViewport,
) -> InputAction {
match (key.code, key.modifiers) {
(KeyCode::Up, KeyModifiers::NONE) => {
state.model_picker_up(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Down, KeyModifiers::NONE) => {
state.model_picker_down(viewport.visible_rows as usize);
InputAction::None
}
(KeyCode::Enter, KeyModifiers::NONE) => {
if let Some(model_id) = state.selected_model_picker_id() {
state.close_model_picker();
InputAction::SelectModel(model_id)
} else {
InputAction::None
}
}
(KeyCode::Esc, KeyModifiers::NONE) => {
state.close_model_picker();
InputAction::None
}
_ => InputAction::None,
}
}