From 479837d9fee74a7869019f841a5b8b253a727172 Mon Sep 17 00:00:00 2001 From: otto Date: Thu, 26 Feb 2026 16:21:20 -0800 Subject: [PATCH] =?UTF-8?q?Translate=20LF=E2=86=92CR=20on=20PTY=20input=20?= =?UTF-8?q?to=20match=20real=20keyboard=20behavior?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TUIs in raw mode expect CR (0x0d) for Enter, which is what physical keyboards send. API clients naturally send LF (0x0a). This adds server-side translation in the PTY writer loop, matching tmux's approach. Safe for cooked mode since ICRNL converts CR back to LF. --- src/session.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/session.rs b/src/session.rs index 1544c7b..6baeadd 100644 --- a/src/session.rs +++ b/src/session.rs @@ -455,7 +455,18 @@ impl Session { let mut writer = pty_writer; let mut rx = input_rx; while let Some(data) = rx.blocking_recv() { - if writer.write_all(&data).is_err() { + // Translate LF (0x0a) → CR (0x0d) to match real keyboard + // behavior. Physical terminals send CR for Enter; the PTY + // line discipline converts CR→LF in cooked mode. TUIs in + // raw mode expect CR directly. This is safe for all modes: + // in cooked mode, ICRNL converts the CR back to LF. + let mut buf = data.to_vec(); + for b in &mut buf { + if *b == b'\n' { + *b = b'\r'; + } + } + if writer.write_all(&buf).is_err() { break; } let _ = writer.flush();