use crossterm::event::{KeyCode, KeyEvent};
use super::ctx::{Effectful, Effects, Nav, Notify};
use crate::app::{App, HostState, Screen, StatusCenter, TagState, TunnelState, UiSelection};
use crate::ssh_config::model::HostEntry;
struct TagInputCtx<'a> {
tags: &'a mut TagState,
hosts: &'a mut HostState,
status: &'a mut StatusCenter,
effects: Effects,
}
impl Notify for TagInputCtx<'_> {
fn status_mut(&mut self) -> &mut StatusCenter {
self.status
}
}
impl Effectful for TagInputCtx<'_> {
fn effects_mut(&mut self) -> &mut Effects {
&mut self.effects
}
}
pub(super) fn handle_tag_input(app: &mut App, key: KeyEvent) {
let selected = app.selected_host().cloned();
let effects = {
let mut ctx = TagInputCtx {
tags: &mut app.tags,
hosts: &mut app.hosts_state,
status: &mut app.status_center,
effects: Effects::default(),
};
tag_input_key(&mut ctx, key, selected.as_ref());
ctx.effects
};
effects.apply(app);
}
fn tag_input_key(ctx: &mut TagInputCtx, key: KeyEvent, selected: Option<&HostEntry>) {
match key.code {
KeyCode::Enter => {
if let Some(input) = ctx.tags.input() {
let tags: Vec<String> = input
.split(',')
.map(|t| t.trim().to_string())
.filter(|t| !t.is_empty())
.collect();
if let Some(host) = selected {
let alias = host.alias.clone();
let old_tags = host.tags.clone();
let _ = ctx.hosts.ssh_config_mut().set_host_tags(&alias, &tags);
if let Err(e) = ctx.hosts.ssh_config().write() {
let _ = ctx.hosts.ssh_config_mut().set_host_tags(&alias, &old_tags);
ctx.notify_error(crate::messages::failed_to_save(&e));
} else {
let count = tags.len();
ctx.defer(move |app| {
app.update_last_modified();
app.reload_hosts();
app.select_host_by_alias(&alias);
app.notify(crate::messages::tagged_host(&alias, count));
});
}
}
}
ctx.tags.close_tag_input();
}
KeyCode::Esc => {
ctx.tags.close_tag_input();
}
KeyCode::Left => {
ctx.tags.cursor_left();
}
KeyCode::Right => {
ctx.tags.cursor_right();
}
KeyCode::Home => {
ctx.tags.cursor_home();
}
KeyCode::End => {
ctx.tags.cursor_end();
}
KeyCode::Char(c) => {
ctx.tags.insert_char(c);
}
KeyCode::Backspace => {
ctx.tags.backspace();
}
_ => {}
}
}
struct HostDetailCtx<'a> {
hosts: &'a HostState,
tunnels: &'a mut TunnelState,
ui: &'a mut UiSelection,
status: &'a mut StatusCenter,
screen: &'a mut Screen,
effects: Effects,
}
impl Nav for HostDetailCtx<'_> {
fn screen_mut(&mut self) -> &mut Screen {
self.screen
}
}
impl Notify for HostDetailCtx<'_> {
fn status_mut(&mut self) -> &mut StatusCenter {
self.status
}
}
impl Effectful for HostDetailCtx<'_> {
fn effects_mut(&mut self) -> &mut Effects {
&mut self.effects
}
}
pub(super) fn handle_key(app: &mut App, key: KeyEvent) {
let index = match app.screen {
Screen::HostDetail { index } => index,
_ => return,
};
let snippet_indices = app.filtered_snippet_indices();
let effects = {
let mut ctx = HostDetailCtx {
hosts: &app.hosts_state,
tunnels: &mut app.tunnels,
ui: &mut app.ui,
status: &mut app.status_center,
screen: &mut app.screen,
effects: Effects::default(),
};
host_detail_key(&mut ctx, key, index, &snippet_indices);
ctx.effects
};
effects.apply(app);
}
fn host_detail_key(
ctx: &mut HostDetailCtx,
key: KeyEvent,
index: usize,
snippet_indices: &[usize],
) {
match key.code {
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('i') => {
ctx.set_screen(Screen::HostList);
}
KeyCode::Char('?') => {
let old = (*ctx.screen).clone();
ctx.set_screen(Screen::Help {
return_screen: Box::new(old),
});
}
KeyCode::Char('e') => {
if let Some(host) = ctx.hosts.list().get(index).cloned() {
let hint = super::host_form::stale_hint_for(&host);
ctx.defer(move |app| {
app.open_host_edit_form(host, hint);
});
}
}
KeyCode::Char('T') => {
if let Some(host) = ctx.hosts.list().get(index) {
let stale_hint = super::host_form::stale_hint_for(host);
let alias = host.alias.clone();
if let Some(hint) = stale_hint {
ctx.notify_warning(crate::messages::stale_host(&hint));
}
ctx.tunnels.load_directives(ctx.hosts.ssh_config(), &alias);
*ctx.ui.tunnel_list_state_mut() = ratatui::widgets::ListState::default();
if !ctx.tunnels.list().is_empty() {
ctx.ui.tunnel_list_state_mut().select(Some(0));
}
ctx.set_screen(Screen::TunnelList { alias });
}
}
KeyCode::Char('r') => {
if let Some(host) = ctx.hosts.list().get(index) {
let stale_hint = super::host_form::stale_hint_for(host);
let alias = host.alias.clone();
if let Some(hint) = stale_hint {
ctx.notify_warning(crate::messages::stale_host(&hint));
}
ctx.set_screen(Screen::SnippetPicker {
target_aliases: vec![alias],
});
*ctx.ui.snippet_picker_state_mut() = ratatui::widgets::ListState::default();
if !snippet_indices.is_empty() {
ctx.ui.snippet_picker_state_mut().select(Some(0));
}
}
}
_ => {}
}
}