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.
This commit is contained in:
Miguel Palhas
2026-08-23 20:25:57 +01:00
parent 1199596528
commit cea06f9204
+61 -29
View File
@@ -304,35 +304,7 @@ impl App {
fn forward_to_session(&mut self, key: &InputKey) -> Option<Effect> {
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<Uuid> {
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<Vec<u8>> {
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;