mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
Support Ctrl+Insert for copying TUI selections (#50215)
## Why Windows Terminal forwards `Ctrl+Insert` when its Copy action has no native terminal selection. Recognize this shortcut so it can copy text selected within Codex. ## What changed Add `Ctrl+Insert` to the shared copy-key detection used by transcript and composer selections. Use the same detection for transcript input ownership so the selection consumes the shortcut before other input handlers. ## Testing Extend transcript tests to verify `Ctrl+Insert` input ownership and copying. Extend composer tests and snapshots to verify that failed or unconfirmed copies preserve the selection, while confirmed copies clear it without changing the draft or cursor. GitOrigin-RevId: 5a33c72cff4c4395511779a7051fc92f8bd5e5d7
This commit is contained in:
@@ -801,10 +801,11 @@ async fn owned_search_and_selection_consume_input_before_composer_and_backtrack(
|
|||||||
&TuiEvent::Key(key)
|
&TuiEvent::Key(key)
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
let copy = app.transcript_view.handle_key(
|
let copy_key = KeyEvent::new(KeyCode::Insert, KeyModifiers::CONTROL);
|
||||||
KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL),
|
assert!(app.transcript_view.owns_interaction_key(copy_key));
|
||||||
&app.transcript_cells,
|
let copy = app
|
||||||
);
|
.transcript_view
|
||||||
|
.handle_key(copy_key, &app.transcript_cells);
|
||||||
assert!(matches!(copy, Some(ViewAction::Copy(text)) if !text.is_empty()));
|
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.handle_owned_transcript_event(&mut tui, &mut app_server, &escape)?);
|
||||||
assert!(!app.backtrack.overlay_preview_active);
|
assert!(!app.backtrack.overlay_preview_active);
|
||||||
@@ -1414,6 +1415,7 @@ async fn fullscreen_composer_mouse_copy_and_input_ownership() -> Result<()> {
|
|||||||
let copy_events = [
|
let copy_events = [
|
||||||
TuiEvent::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::SUPER)),
|
TuiEvent::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::SUPER)),
|
||||||
TuiEvent::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)),
|
TuiEvent::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)),
|
||||||
|
TuiEvent::Key(KeyEvent::new(KeyCode::Insert, KeyModifiers::CONTROL)),
|
||||||
mouse(Down(Right), x + 2, y),
|
mouse(Down(Right), x + 2, y),
|
||||||
];
|
];
|
||||||
let mut selection_frames = Vec::new();
|
let mut selection_frames = Vec::new();
|
||||||
@@ -1473,6 +1475,7 @@ async fn fullscreen_composer_mouse_copy_and_input_ownership() -> Result<()> {
|
|||||||
);
|
);
|
||||||
let gesture = match event {
|
let gesture = match event {
|
||||||
TuiEvent::Mouse(_) => "right-click",
|
TuiEvent::Mouse(_) => "right-click",
|
||||||
|
TuiEvent::Key(key) if key.code == KeyCode::Insert => "ctrl-insert",
|
||||||
TuiEvent::Key(key) if key.modifiers == KeyModifiers::SUPER => "cmd-c",
|
TuiEvent::Key(key) if key.modifiers == KeyModifiers::SUPER => "cmd-c",
|
||||||
_ => "ctrl-c",
|
_ => "ctrl-c",
|
||||||
};
|
};
|
||||||
|
|||||||
+12
@@ -26,6 +26,18 @@ ctrl-c Ok(Confirmed)
|
|||||||
hello world
|
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")
|
right-click Err("clipboard unavailable")
|
||||||
hello world
|
hello world
|
||||||
^^^^^······
|
^^^^^······
|
||||||
|
|||||||
@@ -67,10 +67,12 @@ pub(crate) fn click_count(
|
|||||||
|
|
||||||
pub(crate) fn is_copy_key(key: KeyEvent) -> bool {
|
pub(crate) fn is_copy_key(key: KeyEvent) -> bool {
|
||||||
// Kitty reports Cmd+C as Super+C, including over SSH. Crossterm may report
|
// 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);
|
let (code, modifiers) = crate::key_hint::normalize_key_parts(key.code, key.modifiers);
|
||||||
key.kind != KeyEventKind::Release
|
key.kind != KeyEventKind::Release
|
||||||
&& code == KeyCode::Char('c')
|
&& ((code == KeyCode::Char('c')
|
||||||
&& (matches!(modifiers, KeyModifiers::CONTROL | KeyModifiers::SUPER)
|
&& (matches!(modifiers, KeyModifiers::CONTROL | KeyModifiers::SUPER)
|
||||||
|| modifiers == (KeyModifiers::CONTROL | KeyModifiers::SHIFT))
|
|| modifiers == (KeyModifiers::CONTROL | KeyModifiers::SHIFT)))
|
||||||
|
|| (code == KeyCode::Insert && modifiers == KeyModifiers::CONTROL))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,10 +136,7 @@ impl TranscriptView {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if self.selection.is_some() {
|
if self.selection.is_some() {
|
||||||
return (code == KeyCode::Char('c')
|
return crate::text_selection::is_copy_key(key)
|
||||||
&& matches!(modifiers, KeyModifiers::CONTROL | KeyModifiers::SUPER))
|
|
||||||
|| (code == KeyCode::Char('c')
|
|
||||||
&& modifiers == (KeyModifiers::CONTROL | KeyModifiers::SHIFT))
|
|
||||||
|| code == KeyCode::Esc
|
|| code == KeyCode::Esc
|
||||||
|| (code == KeyCode::Enter && modifiers == KeyModifiers::NONE)
|
|| (code == KeyCode::Enter && modifiers == KeyModifiers::NONE)
|
||||||
|| (matches!(modifiers, KeyModifiers::NONE | KeyModifiers::SHIFT)
|
|| (matches!(modifiers, KeyModifiers::NONE | KeyModifiers::SHIFT)
|
||||||
|
|||||||
Reference in New Issue
Block a user