Skip to main content

purple_ssh/
handler.rs

1use std::sync::atomic::Ordering;
2use std::sync::mpsc;
3
4use anyhow::Result;
5use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
6
7use crate::app::{App, Screen};
8use crate::event::AppEvent;
9
10mod bulk_tag_editor;
11mod confirm;
12mod container_exec_prompt;
13pub(crate) mod container_host_picker;
14pub(crate) mod container_logs;
15mod containers;
16pub(crate) mod containers_overview;
17mod ctx;
18pub(crate) mod event_loop;
19mod file_browser;
20mod help;
21mod host_detail;
22mod host_form;
23mod host_list;
24mod jump;
25pub(crate) mod key_push_picker;
26mod keys_overview;
27mod picker;
28mod ping;
29mod provider;
30mod snippet;
31mod sync;
32mod tag_picker;
33mod theme_picker;
34mod tunnel;
35pub(crate) mod tunnel_host_picker;
36mod tunnels_overview;
37mod welcome;
38mod whats_new;
39
40pub use confirm::{ConfirmAction, route_confirm_key};
41pub(crate) use provider::zone_data_for;
42pub use sync::spawn_provider_sync;
43
44/// Handle a key event based on the current screen.
45pub fn handle_key_event(
46    app: &mut App,
47    key: KeyEvent,
48    events_tx: &mpsc::Sender<AppEvent>,
49) -> Result<()> {
50    // Global Ctrl+C handler — screen-conditional for SnippetOutput
51    if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c') {
52        if matches!(app.screen, Screen::SnippetOutput { .. }) {
53            if let Some(state) = app.snippets.output() {
54                if !state.all_done {
55                    if state.cancel.load(Ordering::Relaxed) {
56                        // Second Ctrl+C: cancel already pending, force close
57                    } else {
58                        // First Ctrl+C: request cancellation
59                        state.cancel.store(true, Ordering::Relaxed);
60                        return Ok(());
61                    }
62                }
63            }
64            app.snippets.set_output(None);
65            app.set_screen(Screen::HostList);
66            return Ok(());
67        }
68        if let Some(cancel) = app.vault.signing_cancel() {
69            cancel.store(true, std::sync::atomic::Ordering::Relaxed);
70        }
71        app.running = false;
72        return Ok(());
73    }
74
75    // Jump intercept
76    if app.jump.is_some() {
77        jump::handle_key(app, key, events_tx);
78        return Ok(());
79    }
80
81    match &app.screen {
82        Screen::HostList => host_list::handle_key(app, key, events_tx),
83        Screen::AddHost | Screen::EditHost { .. } => host_form::handle_key(app, key),
84        Screen::ConfirmDelete { .. } => confirm::handle_delete_key(app, key),
85        Screen::Help { .. } => help::handle_key(app, key),
86        Screen::KeyList => help::handle_key_list_key(app, key),
87        Screen::KeyDetail { .. } => help::handle_key_detail_key(app, key),
88        Screen::KeyPushPicker { .. } => key_push_picker::handle_key(app, key),
89        Screen::ConfirmKeyPush { .. } => confirm::handle_key_push_key(app, key, events_tx),
90        Screen::HostDetail { .. } => host_detail::handle_key(app, key),
91        Screen::TagPicker => tag_picker::handle_key(app, key),
92        Screen::BulkTagEditor => bulk_tag_editor::handle_key(app, key),
93        Screen::ThemePicker => theme_picker::handle_key(app, key),
94        Screen::Providers => provider::handle_provider_list_key(app, key, events_tx),
95        Screen::ProviderForm { .. } => provider::handle_provider_form_key(app, key, events_tx),
96        Screen::ProviderLabelMigration { .. } => {
97            provider::handle_label_migration_key(app, key, events_tx)
98        }
99        Screen::TunnelList { .. } => tunnel::handle_tunnel_list_key(app, key),
100        Screen::TunnelForm { .. } => tunnel::handle_tunnel_form_key(app, key),
101        Screen::TunnelHostPicker => tunnel_host_picker::handle_key(app, key),
102        Screen::ContainerHostPicker => container_host_picker::handle_key(app, key, events_tx),
103        Screen::SnippetPicker { .. } => snippet::handle_picker_key(app, key, events_tx),
104        Screen::SnippetForm { .. } => snippet::handle_form_key(app, key),
105        Screen::SnippetOutput { .. } => snippet::handle_output_key(app, key),
106        Screen::SnippetParamForm { .. } => snippet::handle_param_form_key(app, key, events_tx),
107        Screen::ConfirmHostKeyReset { .. } => confirm::handle_host_key_reset_key(app, key),
108        Screen::ConfirmVaultSign { .. } => confirm::handle_vault_sign_key(app, key, events_tx),
109        Screen::ConfirmImport { .. } => confirm::handle_import_key(app, key),
110        Screen::ConfirmPurgeStale { .. } => confirm::handle_purge_stale_key(app, key),
111        Screen::FileBrowser { .. } => file_browser::handle_key(app, key, events_tx),
112        Screen::Containers { .. } => containers::handle_key(app, key, events_tx)?,
113        Screen::ContainerLogs { .. } => container_logs::handle_key(app, key, events_tx),
114        Screen::ConfirmContainerRestart { .. } => confirm::handle_container_restart_key(app, key),
115        Screen::ConfirmContainerStop { .. } => confirm::handle_container_stop_key(app, key),
116        Screen::ContainerExecPrompt { .. } => container_exec_prompt::handle_key(app, key),
117        Screen::ConfirmStackRestart { .. } => confirm::handle_stack_restart_key(app, key),
118        Screen::ConfirmHostRestartAll { .. } => confirm::handle_host_restart_all_key(app, key),
119        Screen::ConfirmHostStopAll { .. } => confirm::handle_host_stop_all_key(app, key),
120        Screen::Welcome { .. } => welcome::handle_key(app, key),
121        Screen::WhatsNew(_) => whats_new::handle_key(app, key),
122    }
123    Ok(())
124}
125
126#[cfg(test)]
127mod tests;