use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use super::ctx::{Effectful, Effects, Notify};
use crate::app::{
App, FormState, KeysState, ProviderState, ProxyJumpCandidate, StatusCenter, UiSelection,
};
use crate::runtime::env::Env;
struct PickerCtx<'a> {
ui: &'a mut UiSelection,
forms: &'a mut FormState,
providers: &'a mut ProviderState,
keys: &'a KeysState,
status: &'a mut StatusCenter,
env: &'a Env,
effects: Effects,
}
impl Notify for PickerCtx<'_> {
fn status_mut(&mut self) -> &mut StatusCenter {
self.status
}
}
impl Effectful for PickerCtx<'_> {
fn effects_mut(&mut self) -> &mut Effects {
&mut self.effects
}
}
impl PickerCtx<'_> {
fn ctx_from_app<'a>(app: &'a mut App) -> PickerCtx<'a> {
PickerCtx {
ui: &mut app.ui,
forms: &mut app.forms,
providers: &mut app.providers,
keys: &app.keys,
status: &mut app.status_center,
env: app.env.as_ref(),
effects: Effects::default(),
}
}
fn close_password_picker(&mut self) {
log::debug!("[purple] close_password_picker");
self.ui.password_picker_mut().open = false;
}
fn close_key_picker(&mut self) {
log::debug!("[purple] close_key_picker");
self.ui.key_picker_mut().open = false;
}
fn close_proxyjump_picker(&mut self) {
log::debug!("[purple] close_proxyjump_picker");
self.ui.proxyjump_picker_mut().open = false;
}
fn close_vault_role_picker(&mut self) {
log::debug!("[purple] close_vault_role_picker");
self.ui.vault_role_picker_mut().open = false;
}
fn step_password_source(&mut self, forward: bool) {
crate::app::cycle_selection(
&mut self.ui.password_picker_mut().list,
crate::askpass::PASSWORD_SOURCES.len(),
forward,
);
}
fn step_picker_key(&mut self, forward: bool) {
crate::app::cycle_selection(
&mut self.ui.key_picker_mut().list,
self.keys.list().len(),
forward,
);
}
fn step_vault_role(&mut self, len: usize, forward: bool) {
crate::app::cycle_selection(&mut self.ui.vault_role_picker_mut().list, len, forward);
}
fn step_proxyjump(&mut self, candidates: &[ProxyJumpCandidate], forward: bool) {
let len = candidates.len();
if len == 0 {
self.ui.proxyjump_picker_mut().list.select(None);
return;
}
let seed: usize = match self.ui.proxyjump_picker().list.selected() {
Some(idx) => idx,
None if forward => len - 1,
None => 0,
};
let mut next = seed;
for _ in 0..len {
next = if forward {
(next + 1) % len
} else {
(next + len - 1) % len
};
if matches!(candidates.get(next), Some(ProxyJumpCandidate::Host { .. })) {
self.ui.proxyjump_picker_mut().list.select(Some(next));
return;
}
}
}
}
pub(super) fn handle_password_picker(app: &mut App, key: KeyEvent) {
let effects = {
let mut ctx = PickerCtx::ctx_from_app(app);
password_picker_key(&mut ctx, key);
ctx.effects
};
effects.apply(app);
}
fn password_picker_key(ctx: &mut PickerCtx, key: KeyEvent) {
if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('d') {
if let Some(index) = ctx.ui.password_picker().list.selected() {
if let Some(source) = crate::askpass::PASSWORD_SOURCES.get(index) {
let is_none = source.label == "None";
let value = if is_none { "" } else { source.value };
match crate::preferences::save_askpass_default(ctx.env.paths(), value) {
Ok(()) => {
if is_none {
ctx.notify(crate::messages::GLOBAL_DEFAULT_CLEARED);
} else {
ctx.notify(crate::messages::global_default_set(source.label));
}
}
Err(e) => {
ctx.notify_error(crate::messages::save_default_failed(&e));
}
}
}
}
ctx.close_password_picker();
return;
}
match key.code {
KeyCode::Esc => {
ctx.close_password_picker();
}
KeyCode::Char('j') | KeyCode::Down => {
ctx.step_password_source(true);
}
KeyCode::Char('k') | KeyCode::Up => {
ctx.step_password_source(false);
}
KeyCode::Enter => {
let mut needs_more_input = false;
if let Some(index) = ctx.ui.password_picker().list.selected() {
if let Some(source) = crate::askpass::PASSWORD_SOURCES.get(index) {
let is_none = source.label == "None";
let is_custom_cmd = source.label == "Custom command";
let is_prefix = source.value.ends_with(':') || source.value.ends_with("//");
if is_none {
ctx.forms
.host_mut()
.apply_password_source(String::new(), false);
ctx.notify(crate::messages::PASSWORD_SOURCE_CLEARED);
} else if is_custom_cmd {
ctx.forms
.host_mut()
.apply_password_source(String::new(), true);
ctx.notify(crate::messages::ASKPASS_CUSTOM_COMMAND_HINT);
needs_more_input = true;
} else if is_prefix {
ctx.forms
.host_mut()
.apply_password_source(source.value.to_string(), true);
ctx.notify(crate::messages::complete_path(source.label));
needs_more_input = true;
} else {
ctx.forms
.host_mut()
.apply_password_source(source.value.to_string(), false);
ctx.notify(crate::messages::password_source_set(source.label));
}
}
}
ctx.close_password_picker();
if !needs_more_input {
ctx.defer(try_auto_submit_after_picker);
}
}
_ => {}
}
}
pub(super) fn handle_key_picker_shared(app: &mut App, key: KeyEvent, for_provider: bool) {
let effects = {
let mut ctx = PickerCtx::ctx_from_app(app);
key_picker_shared_key(&mut ctx, key, for_provider);
ctx.effects
};
effects.apply(app);
}
fn key_picker_shared_key(ctx: &mut PickerCtx, key: KeyEvent, for_provider: bool) {
match key.code {
KeyCode::Esc => {
ctx.close_key_picker();
}
KeyCode::Char('j') | KeyCode::Down => {
ctx.step_picker_key(true);
}
KeyCode::Char('k') | KeyCode::Up => {
ctx.step_picker_key(false);
}
KeyCode::Enter => {
if let Some(index) = ctx.ui.key_picker().list.selected() {
if let Some(key_info) = ctx.keys.list().get(index) {
if for_provider {
ctx.providers.form_mut().identity_file = key_info.display_path.clone();
ctx.providers.form_mut().sync_cursor_to_end();
} else {
ctx.forms.host_mut().identity_file = key_info.display_path.clone();
ctx.forms.host_mut().sync_cursor_to_end();
}
ctx.notify(crate::messages::key_selected(&key_info.name));
}
}
ctx.close_key_picker();
if !for_provider {
ctx.defer(try_auto_submit_after_picker);
}
}
_ => {}
}
}
pub(super) fn handle_proxyjump_picker(app: &mut App, key: KeyEvent) {
let candidates = app.proxyjump_candidates();
let effects = {
let mut ctx = PickerCtx::ctx_from_app(app);
proxyjump_picker_key(&mut ctx, key, &candidates);
ctx.effects
};
effects.apply(app);
}
fn proxyjump_picker_key(ctx: &mut PickerCtx, key: KeyEvent, candidates: &[ProxyJumpCandidate]) {
match key.code {
KeyCode::Esc => {
ctx.close_proxyjump_picker();
}
KeyCode::Char('j') | KeyCode::Down => {
ctx.step_proxyjump(candidates, true);
}
KeyCode::Char('k') | KeyCode::Up => {
ctx.step_proxyjump(candidates, false);
}
KeyCode::Enter => {
if let Some(index) = ctx.ui.proxyjump_picker().list.selected() {
if let Some(crate::app::ProxyJumpCandidate::Host { alias, .. }) =
candidates.get(index)
{
ctx.forms.host_mut().proxy_jump = alias.clone();
ctx.forms.host_mut().sync_cursor_to_end();
ctx.notify(crate::messages::proxy_jump_set(alias));
ctx.close_proxyjump_picker();
ctx.defer(try_auto_submit_after_picker);
}
}
}
_ => {}
}
}
pub(super) fn handle_vault_role_picker(app: &mut App, key: KeyEvent) {
let candidates = app.vault_role_candidates();
let effects = {
let mut ctx = PickerCtx::ctx_from_app(app);
vault_role_picker_key(&mut ctx, key, &candidates);
ctx.effects
};
effects.apply(app);
}
fn vault_role_picker_key(ctx: &mut PickerCtx, key: KeyEvent, candidates: &[String]) {
match key.code {
KeyCode::Esc => {
ctx.close_vault_role_picker();
}
KeyCode::Char('j') | KeyCode::Down => {
ctx.step_vault_role(candidates.len(), true);
}
KeyCode::Char('k') | KeyCode::Up => {
ctx.step_vault_role(candidates.len(), false);
}
KeyCode::Enter => {
if let Some(index) = ctx.ui.vault_role_picker().list.selected() {
if let Some(role) = candidates.get(index) {
ctx.forms.host_mut().vault_ssh = role.clone();
ctx.forms.host_mut().sync_cursor_to_end();
ctx.notify(crate::messages::vault_role_set(role));
}
}
ctx.close_vault_role_picker();
}
_ => {}
}
}
pub(super) fn try_auto_submit_after_picker(app: &mut App) {
if !app.forms.host_mut().alias.is_empty() && !app.forms.host_mut().hostname.is_empty() {
super::host_form::submit_form(app);
}
}