diff options
| author | akbash <akbash@herdr.dev> | 2026-09-02 01:02:25 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-02 00:02:25 +0200 |
| commit | 0032c3b42751b6da9c5b1a91546b3c1a425d67f1 (patch) | |
| tree | fc3923b64e7ddd3deec43ae540e2ecae7c71a746 /src/client | |
| parent | 99c23cd1ea7468bd3661f6483c7105396503b417 (diff) | |
fix(windows): preserve remote multiline paste (#3282)
refs #3209
Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/input/windows_vti.rs | 65 |
1 files changed, 59 insertions, 6 deletions
diff --git a/src/client/input/windows_vti.rs b/src/client/input/windows_vti.rs index 2f469161..ce322d27 100644 --- a/src/client/input/windows_vti.rs +++ b/src/client/input/windows_vti.rs @@ -334,13 +334,13 @@ impl WindowsInputPump { fn process_raw_events( &mut self, - events: Vec<crate::raw_input::RawInputEvent>, + mut events: Vec<crate::raw_input::RawInputEvent>, ) -> Vec<crate::protocol::ClientInputEvent> { - if events - .iter() - .any(|event| matches!(event, crate::raw_input::RawInputEvent::Paste(_))) - { - self.paste_from_win32_key_records = false; + for event in &mut events { + if let crate::raw_input::RawInputEvent::Paste(text) = event { + decode_windows_terminal_paste_enters(text); + self.paste_from_win32_key_records = false; + } } Self::raw_events_to_client_events(events) } @@ -387,6 +387,16 @@ impl PlatformInputItem { } } +fn decode_windows_terminal_paste_enters(text: &mut String) { + const ENTER_REPORT_PAIR: &str = "\x1b[13;28;13;1;0;1_\x1b[13;28;13;0;0;1_"; + + // Windows Terminal can encode pasted newlines as an adjacent unmodified + // Enter press/release pair. Keep every other report-shaped payload opaque. + if text.contains(ENTER_REPORT_PAIR) { + *text = text.replace(ENTER_REPORT_PAIR, "\r"); + } +} + #[cfg(test)] #[derive(Default)] struct WindowsInputTranslator { @@ -1368,6 +1378,49 @@ mod tests { } #[test] + fn vti_remote_bracketed_paste_decodes_reported_enter_records() { + let records = concat!( + "\x1b[200~ - line one", + "\x1b[13;28;13;1;0;1_\x1b[13;28;13;0;0;1_", + " - line two", + "\x1b[13;28;13;1;0;1_\x1b[13;28;13;0;0;1_", + " - line three\x1b[201~", + ) + .chars() + .map(key_char); + + assert_eq!( + translate(records), + vec![crate::protocol::ClientInputEvent::Paste { + text: " - line one\r - line two\r - line three".into(), + }] + ); + } + + #[test] + fn vti_remote_paste_keeps_incomplete_enter_report_pairs_opaque() { + let records = concat!( + "\x1b[200~before", + "\x1b[13;28;13;1;0;1_middle\x1b[13;28;13;0;0;1_", + "after\x1b[201~", + ) + .chars() + .map(key_char); + + assert_eq!( + translate(records), + vec![crate::protocol::ClientInputEvent::Paste { + text: concat!( + "before", + "\x1b[13;28;13;1;0;1_middle\x1b[13;28;13;0;0;1_", + "after", + ) + .into(), + }] + ); + } + + #[test] fn vti_win32_input_mode_decoded_paste_handles_shift_repeats_and_releases() { let mut records = win32_input_mode_encoded_key_bytes(b"\x1b[200~"); records.extend(win32_input_mode_encoded_record(WindowsKeyRecord { |
