aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-03-31 13:54:23 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-03-31 13:54:23 +0300
commitb2293687afa4ba268eac98bf885bf6e5a5133180 (patch)
tree58fb49e735889ae55fe9f0387ebaf4b511a5aee3 /src
parentf8c452294d6f76ccb264c0d9e205398492f00e18 (diff)
fix: preserve incomplete bracketed paste across read timeouts
Diffstat (limited to 'src')
-rw-r--r--src/raw_input.rs30
1 files changed, 30 insertions, 0 deletions
diff --git a/src/raw_input.rs b/src/raw_input.rs
index 48b9547c..859f4c26 100644
--- a/src/raw_input.rs
+++ b/src/raw_input.rs
@@ -63,6 +63,13 @@ fn flush_incomplete_buffer(buffer: &mut Vec<u8>, tx: &mpsc::Sender<RawInputEvent
return;
}
+ if buffer.starts_with(BRACKETED_PASTE_START)
+ && find_subsequence(buffer, BRACKETED_PASTE_END).is_none()
+ {
+ tracing::trace!(len = buffer.len(), "waiting for bracketed paste terminator");
+ return;
+ }
+
if buffer.as_slice() == [ESC] {
let _ = tx.blocking_send(RawInputEvent::Key(TerminalKey::new(
crossterm::event::KeyCode::Esc,
@@ -662,6 +669,29 @@ mod tests {
}
#[test]
+ fn incomplete_bracketed_paste_is_not_flushed_on_timeout() {
+ let (tx, mut rx) = mpsc::channel(8);
+ let mut buffer = Vec::new();
+
+ drain_chunk(&mut buffer, &tx, b"\x1b[200~hello\nworld");
+ assert_eq!(buffer, b"\x1b[200~hello\nworld");
+ assert!(collect_events(&mut rx).is_empty());
+
+ flush_incomplete_buffer(&mut buffer, &tx);
+ assert_eq!(buffer, b"\x1b[200~hello\nworld");
+ assert!(collect_events(&mut rx).is_empty());
+
+ drain_chunk(&mut buffer, &tx, b"\x1b[201~");
+ assert!(buffer.is_empty());
+ let events = collect_events(&mut rx);
+ assert_eq!(events.len(), 1);
+ let RawInputEvent::Paste(text) = &events[0] else {
+ panic!("expected paste");
+ };
+ assert_eq!(text, "hello\nworld");
+ }
+
+ #[test]
fn chunked_utf8_waits_for_continuation_byte() {
let (tx, mut rx) = mpsc::channel(8);
let mut buffer = Vec::new();