diff --git a/codex-rs/tui/src/app/owned_transcript_tests.rs b/codex-rs/tui/src/app/owned_transcript_tests.rs index 1548028051..009b47fdc7 100644 --- a/codex-rs/tui/src/app/owned_transcript_tests.rs +++ b/codex-rs/tui/src/app/owned_transcript_tests.rs @@ -801,10 +801,11 @@ async fn owned_search_and_selection_consume_input_before_composer_and_backtrack( &TuiEvent::Key(key) )?); } - let copy = app.transcript_view.handle_key( - KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL), - &app.transcript_cells, - ); + let copy_key = KeyEvent::new(KeyCode::Insert, KeyModifiers::CONTROL); + assert!(app.transcript_view.owns_interaction_key(copy_key)); + let copy = app + .transcript_view + .handle_key(copy_key, &app.transcript_cells); assert!(matches!(copy, Some(ViewAction::Copy(text)) if !text.is_empty())); assert!(app.handle_owned_transcript_event(&mut tui, &mut app_server, &escape)?); assert!(!app.backtrack.overlay_preview_active); @@ -1414,6 +1415,7 @@ async fn fullscreen_composer_mouse_copy_and_input_ownership() -> Result<()> { let copy_events = [ TuiEvent::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::SUPER)), TuiEvent::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)), + TuiEvent::Key(KeyEvent::new(KeyCode::Insert, KeyModifiers::CONTROL)), mouse(Down(Right), x + 2, y), ]; let mut selection_frames = Vec::new(); @@ -1473,6 +1475,7 @@ async fn fullscreen_composer_mouse_copy_and_input_ownership() -> Result<()> { ); let gesture = match event { TuiEvent::Mouse(_) => "right-click", + TuiEvent::Key(key) if key.code == KeyCode::Insert => "ctrl-insert", TuiEvent::Key(key) if key.modifiers == KeyModifiers::SUPER => "cmd-c", _ => "ctrl-c", }; diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__owned_transcript__tests__fullscreen_composer_right_click_copy.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__owned_transcript__tests__fullscreen_composer_right_click_copy.snap index ebc424a3b0..ef78c11d19 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__owned_transcript__tests__fullscreen_composer_right_click_copy.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__owned_transcript__tests__fullscreen_composer_right_click_copy.snap @@ -26,6 +26,18 @@ ctrl-c Ok(Confirmed) hello world ··········· +ctrl-insert Err("clipboard unavailable") +hello world +^^^^^······ + +ctrl-insert Ok(Unconfirmed) +hello world +^^^^^······ + +ctrl-insert Ok(Confirmed) +hello world +··········· + right-click Err("clipboard unavailable") hello world ^^^^^······ diff --git a/codex-rs/tui/src/text_selection.rs b/codex-rs/tui/src/text_selection.rs index cdd3491109..b2fbfb1073 100644 --- a/codex-rs/tui/src/text_selection.rs +++ b/codex-rs/tui/src/text_selection.rs @@ -67,10 +67,12 @@ pub(crate) fn click_count( pub(crate) fn is_copy_key(key: KeyEvent) -> bool { // Kitty reports Cmd+C as Super+C, including over SSH. Crossterm may report - // Ctrl+Shift+C as uppercase C with only Control set. + // Ctrl+Shift+C as uppercase C with only Control set. Windows Terminal forwards + // Ctrl+Insert when its Copy action has no native terminal selection. let (code, modifiers) = crate::key_hint::normalize_key_parts(key.code, key.modifiers); key.kind != KeyEventKind::Release - && code == KeyCode::Char('c') - && (matches!(modifiers, KeyModifiers::CONTROL | KeyModifiers::SUPER) - || modifiers == (KeyModifiers::CONTROL | KeyModifiers::SHIFT)) + && ((code == KeyCode::Char('c') + && (matches!(modifiers, KeyModifiers::CONTROL | KeyModifiers::SUPER) + || modifiers == (KeyModifiers::CONTROL | KeyModifiers::SHIFT))) + || (code == KeyCode::Insert && modifiers == KeyModifiers::CONTROL)) } diff --git a/codex-rs/tui/src/transcript_view/input.rs b/codex-rs/tui/src/transcript_view/input.rs index d3d5be9f9c..e2923e55e2 100644 --- a/codex-rs/tui/src/transcript_view/input.rs +++ b/codex-rs/tui/src/transcript_view/input.rs @@ -136,10 +136,7 @@ impl TranscriptView { return true; } if self.selection.is_some() { - return (code == KeyCode::Char('c') - && matches!(modifiers, KeyModifiers::CONTROL | KeyModifiers::SUPER)) - || (code == KeyCode::Char('c') - && modifiers == (KeyModifiers::CONTROL | KeyModifiers::SHIFT)) + return crate::text_selection::is_copy_key(key) || code == KeyCode::Esc || (code == KeyCode::Enter && modifiers == KeyModifiers::NONE) || (matches!(modifiers, KeyModifiers::NONE | KeyModifiers::SHIFT)