diff --git a/src/controller/input.rs b/src/controller/input.rs index e346404..3a755c3 100644 --- a/src/controller/input.rs +++ b/src/controller/input.rs @@ -132,6 +132,23 @@ pub fn handle_key(key: KeyEvent, state: &mut AppStateRest) -> Vec { KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => { vec![Action::CloseOverlay] } + KeyCode::Char('y') if key.modifiers.contains(KeyModifiers::CONTROL) => { + let last_assistant = state.transcript_cache.messages.iter() + .rev() + .find(|m| m.role == crate::dto::chat::message::Role::Assistant); + match last_assistant { + Some(msg) => { + state.misc.pending_clipboard_copy = Some(msg.content.clone()); + } + None => { + state.push_toast(crate::app::state::types::Toast::new( + crate::app::state::types::ToastKind::Info, + "No assistant message to copy yet".to_string(), + )); + } + } + Vec::new() + } KeyCode::Enter => { if state.input.autocomplete_visible { state.input.select_autocomplete(); @@ -371,3 +388,45 @@ fn handle_overlay_enter(state: &mut AppStateRest) -> Vec { _ => Vec::new(), } } + +#[cfg(test)] +mod tests { + use super::*; + + fn test_state() -> AppStateRest { + let tmp = std::env::temp_dir().join(format!("zesdex-input-test-{}", uuid::Uuid::new_v4())); + std::fs::create_dir_all(&tmp).unwrap(); + AppStateRest::new(vec![tmp.clone()], &tmp, tmp.join("memory")) + } + + #[test] + fn ctrl_y_sets_pending_clipboard_copy_to_last_assistant_message() { + let mut state = test_state(); + state.push_transcript(crate::app::state::rest::ChatMessageDisplay::new( + crate::dto::chat::message::Role::User, + "hi".to_string(), + )); + state.push_transcript(crate::app::state::rest::ChatMessageDisplay::new( + crate::dto::chat::message::Role::Assistant, + "first reply".to_string(), + )); + state.push_transcript(crate::app::state::rest::ChatMessageDisplay::new( + crate::dto::chat::message::Role::Tool, + "tool output".to_string(), + )); + state.push_transcript(crate::app::state::rest::ChatMessageDisplay::new( + crate::dto::chat::message::Role::Assistant, + "second reply".to_string(), + )); + handle_key(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::CONTROL), &mut state); + assert_eq!(state.misc.pending_clipboard_copy, Some("second reply".to_string())); + } + + #[test] + fn ctrl_y_with_no_assistant_message_pushes_info_toast() { + let mut state = test_state(); + handle_key(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::CONTROL), &mut state); + assert!(state.misc.pending_clipboard_copy.is_none()); + assert_eq!(state.misc.toasts.len(), 1); + } +}