diff --git a/src/app/events/mod.rs b/src/app/events/mod.rs index 96a0a1f6..7c7b3c09 100644 --- a/src/app/events/mod.rs +++ b/src/app/events/mod.rs @@ -156,7 +156,7 @@ fn should_dispatch_key_event(key: crossterm::event::KeyEvent) -> bool { && matches!(key.code, crossterm::event::KeyCode::Char(ch) if !ch.is_control() && unicode_width::UnicodeWidthChar::width(ch) == Some(0)); key.kind == KeyEventKind::Press - || (key.kind == KeyEventKind::Release && super::keys::is_clipboard_paste_shortcut(key)) + || super::keys::is_clipboard_paste_trigger(key) || zero_width_text_release } diff --git a/src/app/events/tests/mod.rs b/src/app/events/tests/mod.rs index 121261be..8d4d513d 100644 --- a/src/app/events/tests/mod.rs +++ b/src/app/events/tests/mod.rs @@ -3583,16 +3583,21 @@ fn settings_view_ignores_paste_events() { } #[test] +#[cfg(windows)] fn clipboard_paste_shortcut_dispatches_on_release() { - let key = crossterm::event::KeyEvent { - code: KeyCode::Char('v'), - modifiers: KeyModifiers::CONTROL, - kind: KeyEventKind::Release, - state: crossterm::event::KeyEventState::NONE, - }; + let key = + KeyEvent::new_with_kind(KeyCode::Char('v'), KeyModifiers::CONTROL, KeyEventKind::Release); assert!(should_dispatch_key_event(key)); } +#[test] +#[cfg(not(windows))] +fn clipboard_paste_shortcut_release_is_ignored_outside_windows() { + let key = + KeyEvent::new_with_kind(KeyCode::Char('v'), KeyModifiers::CONTROL, KeyEventKind::Release); + assert!(!should_dispatch_key_event(key)); +} + #[test] #[cfg(windows)] fn conpty_release_only_accent_survives_input_and_prompt_dispatch() { diff --git a/src/app/keys.rs b/src/app/keys.rs index e0f4ca28..586182c0 100644 --- a/src/app/keys.rs +++ b/src/app/keys.rs @@ -603,10 +603,7 @@ fn handle_mode_cycle(app: &mut App) -> bool { } fn handle_clipboard_paste_key(app: &mut App, key: KeyEvent) -> bool { - if !is_clipboard_paste_shortcut(key) { - return false; - } - if key.kind != KeyEventKind::Release { + if !is_clipboard_paste_trigger(key) { return false; } @@ -663,8 +660,11 @@ fn handle_clipboard_paste_key(app: &mut App, key: KeyEvent) -> bool { } } -pub(super) fn is_clipboard_paste_shortcut(key: KeyEvent) -> bool { - is_ctrl_char_shortcut(key, 'v') +const CLIPBOARD_PASTE_TRIGGER_KIND: KeyEventKind = + if cfg!(windows) { KeyEventKind::Release } else { KeyEventKind::Press }; + +pub(super) fn is_clipboard_paste_trigger(key: KeyEvent) -> bool { + key.kind == CLIPBOARD_PASTE_TRIGGER_KIND && is_ctrl_char_shortcut(key, 'v') } pub(super) fn reclaim_input_from_inline_prompt_if_needed(app: &mut App) { @@ -979,6 +979,35 @@ mod tests { assert!(is_ctrl_char_shortcut(key, 'v')); } + fn ctrl_v_encodings(kind: KeyEventKind) -> [KeyEvent; 2] { + [ + KeyEvent::new_with_kind(KeyCode::Char('v'), KeyModifiers::CONTROL, kind), + KeyEvent::new_with_kind(KeyCode::Char('\u{16}'), KeyModifiers::NONE, kind), + ] + } + + #[test] + #[cfg(not(windows))] + fn clipboard_paste_triggers_on_ctrl_v_press_outside_windows() { + for key in ctrl_v_encodings(KeyEventKind::Press) { + assert!(is_clipboard_paste_trigger(key), "{key:?}"); + } + for key in ctrl_v_encodings(KeyEventKind::Release) { + assert!(!is_clipboard_paste_trigger(key), "{key:?}"); + } + } + + #[test] + #[cfg(windows)] + fn clipboard_paste_triggers_on_ctrl_v_release_on_windows() { + for key in ctrl_v_encodings(KeyEventKind::Release) { + assert!(is_clipboard_paste_trigger(key), "{key:?}"); + } + for key in ctrl_v_encodings(KeyEventKind::Press) { + assert!(!is_clipboard_paste_trigger(key), "{key:?}"); + } + } + #[test] fn ctrl_shortcut_rejects_raw_control_character_with_alt() { let key = KeyEvent::new(KeyCode::Char('\u{16}'), KeyModifiers::ALT);