From cea06f920444d86eb16ab0398e7627fd3e7a547a Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Sun, 23 Aug 2026 20:25:57 +0100 Subject: [PATCH] fix(maestro): forward bracketed pastes into the attached session forward_to_session dropped Paste(_) on the floor, so pastes into a session vanished once terminal.rs enabled bracketed paste. Split the arm: Paste forwards the payload verbatim (the remote readline reassembles it), only Mouse events have no wire encoding. --- crates/maestro-tui/src/app.rs | 90 ++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 29 deletions(-) diff --git a/crates/maestro-tui/src/app.rs b/crates/maestro-tui/src/app.rs index f262b2f..aae1d75 100644 --- a/crates/maestro-tui/src/app.rs +++ b/crates/maestro-tui/src/app.rs @@ -304,35 +304,7 @@ impl App { fn forward_to_session(&mut self, key: &InputKey) -> Option { let sid = self.state.active_session()?; self.emulators.get(sid)?; - let bytes = match key { - InputKey::Char { c, ctrl, alt, .. } => { - let mut out = Vec::with_capacity(8); - if *alt { - out.push(0x1b); - } - if *ctrl && c.is_ascii_alphabetic() { - out.push((c.to_ascii_uppercase() as u8) - b'@'); - } else { - let mut b = [0u8; 4]; - let s = c.encode_utf8(&mut b); - out.extend_from_slice(s.as_bytes()); - } - out - } - InputKey::Enter => b"\r".to_vec(), - InputKey::Tab => b"\t".to_vec(), - InputKey::BackTab => b"\x1b[Z".to_vec(), - InputKey::Backspace => vec![0x7f], - InputKey::Esc => vec![0x1b], - InputKey::Up => b"\x1b[A".to_vec(), - InputKey::Down => b"\x1b[B".to_vec(), - InputKey::Right => b"\x1b[C".to_vec(), - InputKey::Left => b"\x1b[D".to_vec(), - InputKey::Home => b"\x1b[H".to_vec(), - InputKey::End => b"\x1b[F".to_vec(), - InputKey::Delete => b"\x1b[3~".to_vec(), - InputKey::Paste(_) | InputKey::Mouse { .. } => return None, - }; + let bytes = encode_key(key)?; Some(Effect::Forward { id: sid, bytes }) } @@ -460,6 +432,43 @@ fn visible_sidebar_order(snap: &crate::catalog::CatalogSnapshot) -> Vec { snap.sessions.iter().map(|s| s.id).collect() } +/// Wire encoding for a keystroke headed to a session's PTY. `None` for keys +/// with no byte representation (mouse events). A bracketed paste is forwarded +/// verbatim — the remote readline reassembles it, since the pane's PTY also +/// has bracketed paste enabled. +fn encode_key(key: &InputKey) -> Option> { + match key { + InputKey::Char { c, ctrl, alt, .. } => { + let mut out = Vec::with_capacity(8); + if *alt { + out.push(0x1b); + } + if *ctrl && c.is_ascii_alphabetic() { + out.push((c.to_ascii_uppercase() as u8) - b'@'); + } else { + let mut b = [0u8; 4]; + let s = c.encode_utf8(&mut b); + out.extend_from_slice(s.as_bytes()); + } + Some(out) + } + InputKey::Enter => Some(b"\r".to_vec()), + InputKey::Tab => Some(b"\t".to_vec()), + InputKey::BackTab => Some(b"\x1b[Z".to_vec()), + InputKey::Backspace => Some(vec![0x7f]), + InputKey::Esc => Some(vec![0x1b]), + InputKey::Up => Some(b"\x1b[A".to_vec()), + InputKey::Down => Some(b"\x1b[B".to_vec()), + InputKey::Right => Some(b"\x1b[C".to_vec()), + InputKey::Left => Some(b"\x1b[D".to_vec()), + InputKey::Home => Some(b"\x1b[H".to_vec()), + InputKey::End => Some(b"\x1b[F".to_vec()), + InputKey::Delete => Some(b"\x1b[3~".to_vec()), + InputKey::Paste(text) => Some(text.as_bytes().to_vec()), + InputKey::Mouse { .. } => None, + } +} + /// Build the lines shown by the help overlay from the live keymap, so the /// user always sees the bindings that are actually active (defaults + their /// own `[tui.keymap.*]` overrides). @@ -686,6 +695,29 @@ mod tests { assert!(app.forward_to_session(&InputKey::Enter).is_none()); } + /// Regression review fix: `Paste(_)` used to be silently dropped, so a + /// paste into an attached session vanished once bracketed paste was + /// enabled — the payload must be forwarded verbatim. + #[test] + fn a_bracketed_paste_is_forwarded_verbatim_to_the_session() { + assert_eq!( + encode_key(&InputKey::Paste("cd /tmp/foo".into())), + Some(b"cd /tmp/foo".to_vec()) + ); + } + + #[test] + fn mouse_events_have_no_wire_encoding() { + use crate::input::{MouseKind, Region}; + assert!(encode_key(&InputKey::Mouse { + kind: MouseKind::Click, + region: Region::Pane, + col: 3, + row: 4, + }) + .is_none()); + } + #[test] fn kill_highlighted_opens_confirm() { use chrono::Utc;