Skip to content

Fix Shift+Enter for VS Code terminal with Node.js apps (Claude Code)#132

Closed
ToxMox wants to merge 1 commit into
psmux:masterfrom
ToxMox:fixes/shift-enter-pr
Closed

Fix Shift+Enter for VS Code terminal with Node.js apps (Claude Code)#132
ToxMox wants to merge 1 commit into
psmux:masterfrom
ToxMox:fixes/shift-enter-pr

Conversation

@ToxMox

@ToxMox ToxMox commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Shift+Enter doesn't work for Node.js apps (like Claude Code) running inside psmux in VS Code's terminal. The existing CSI \x1b[13;2~ encoding from a761f3e is dropped by ConPTY (it doesn't recognize code 13 in ~ format), so the keypress never reaches the child process.

This PR fixes both the client-side detection and server-side delivery for the VS Code + ConPTY + Node.js case.

Root cause

Two separate issues in the VS Code terminal path:

Client: VS Code's xterm.js sends \x1b\r (ESC + CR) for Shift+Enter. ConPTY interprets the ESC prefix as Alt, so crossterm reports Alt+Enter instead of Shift+Enter.

Server: Node.js apps use libuv's uv_tty_read_raw, which translates VK_RETURN to \r regardless of modifier flags. Both the CSI \x1b[13;2~ encoding (dropped by ConPTY) and win32-input-mode KEY_EVENT_RECORD with SHIFT_PRESSED (libuv ignores shift on Enter) fail to deliver the modifier.

Fix

Client (platform.rs): augment_enter_shift polls GetAsyncKeyState(VK_SHIFT) to detect the physical Shift key and remaps crossterm's misreported Alt+Enter back to Shift+Enter.

Server (input.rs): Sends \x1b\r (ESC + CR) for Shift+Enter — the same bytes VS Code's xterm.js sends. The round-trip works because libuv does preserve Alt as an ESC prefix:

\x1b\r → ConPTY: Alt+Enter → libuv: \x1b\r → app interprets as Shift+Enter

Trade-offs

  • GetAsyncKeyState is a heuristic — there's a small race window between keypress and event processing. In practice this is reliable for interactive use.
  • ConPTY delivers the key as Alt+Enter, not Shift+Enter. PSReadLine users need to add a keybinding to map Alt+Enter to the same AddLine action (see User Setup below).
  • This is the same limitation that exists in VS Code's terminal without psmux — xterm.js always sends \x1b\r for Shift+Enter, which ConPTY interprets as Alt+Enter.

User setup

For full Shift+Enter support in VS Code, users also need:

VS Code keybindings.json — ensures VS Code sends \x1b\r for Shift+Enter in terminal:

{
    "key": "shift+enter",
    "command": "workbench.action.terminal.sendSequence",
    "args": {
        "text": "\u001b\r"
    },
    "when": "terminalFocus && terminalShellType != 'pwsh'"
}

PowerShell profile ($PROFILE) — maps Alt+Enter to AddLine since ConPTY delivers the key as Alt+Enter:

Set-PSReadLineKeyHandler -Chord 'Alt+Enter' -Function AddLine

Test plan

  • Press Shift+Enter in Claude Code inside psmux (VS Code terminal) — should insert newline
  • Press Shift+Enter in PowerShell with the PSReadLine keybinding — should insert newline
  • Plain Enter still works normally in all apps
  • Alt+char shortcuts (Alt+F, Alt+B) still work in PSReadLine

Client: augment_enter_shift polls GetAsyncKeyState to detect physical
Shift key, remapping crossterm's misreported Alt+Enter back to
Shift+Enter (xterm.js sends ESC+CR which ConPTY interprets as Alt).

Server: send ESC+CR (\x1b\r) for Shift+Enter to ConPTY input pipe,
matching what VS Code's xterm.js sends. The round-trip works because
libuv translates the resulting Alt+Enter KEY_EVENT_RECORD back to
\x1b\r, which Node.js apps interpret as Shift+Enter.

The existing CSI \x1b[13;2~ encoding is dropped by ConPTY (it doesn't
recognize code 13 in ~ format). Win32-input-mode doesn't work for
Node.js because libuv ignores SHIFT on VK_RETURN.
psmux pushed a commit that referenced this pull request Mar 18, 2026
…ncoding (PR #132)

PR #131 - Multi-line paste reverse order in PowerShell:
- Root cause: write_paste_chunked sends clipboard text with bare LF to PTY pipe.
  ConPTY expects CR for Enter; bare LF confuses PSReadLine, causing reversed order.
- Fix: Normalize line endings (LF->CR, CRLF->CR) in write_paste_chunked before
  writing to the PTY pipe.

PR #132 - Shift+Enter not working for VS Code terminal / Node.js apps:
- Root cause (client): VS Code xterm.js sends ESC+CR for Shift+Enter. ConPTY
  interprets ESC as Alt prefix, so crossterm misreports Alt+Enter.
- Root cause (server): CSI 13;2~ encoding is non-standard (code 13 not in VT
  function key table) and silently dropped by ConPTY.
- Fix (client): augment_enter_shift polls GetAsyncKeyState(VK_SHIFT) to detect
  physical Shift key and remaps crossterm's misreported Alt+Enter to Shift+Enter.
- Fix (server): encode_key_event and send_key_to_active send ESC+CR instead of
  CSI 13;mod~ for Shift/Alt+Enter on Windows. This matches VS Code xterm.js
  encoding and round-trips through ConPTY -> libuv correctly.
- Ctrl+Enter variants still use CSI encoding (unaffected by ConPTY).

Tests: 7 new unit tests covering paste normalization (LF, CRLF, mixed, bracketed)
and Shift/Alt+Enter ConPTY encoding. All 68 tests pass.
@psmux

psmux commented Mar 18, 2026

Copy link
Copy Markdown
Owner

Hey @ToxMox, solid work on this one — both root causes you identified are correct.

Client side: VS Code's xterm.js sends \x1b\r\ for Shift+Enter, ConPTY interprets the ESC as an Alt prefix, and crossterm delivers Alt+Enter instead of Shift+Enter. Your \�ugment_enter_shift\ approach using \GetAsyncKeyState(VK_SHIFT)\ is the right call — there's no other way to recover the real modifier once ConPTY has already eaten it.

Server side: CSI \x1b[13;2~\ is non-standard (code 13 isn't in the VT function key table), and ConPTY silently drops it. Sending \x1b\r\ (ESC+CR) instead works because the round-trip preserves the ESC through libuv's Alt-prefix handling, so Node.js apps like Claude Code receive it correctly.

I independently verified both bugs by writing unit tests — Shift+Enter encoding produced \x1b[13;2~\ (7 bytes) instead of \x1b\r\ (2 bytes), and Alt+Enter had the same issue. After applying the fix (same approach as your PR), all new tests pass along with the full 68-test suite. I also extended \�ncode_key_event\ with the same Windows-conditional logic for the embedded-mode path, and added the #[cfg(windows)]\ guard in \send_key_to_active\ for the S-Enter/M-Enter command dispatch.

Fixed in abd5e25. Closing this PR since master already has the fix, but thanks for the thorough analysis — the ConPTY round-trip explanation was particularly helpful.

@ToxMox

ToxMox commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

Awesome! Glad my PRs have been useful. Thanks for the quick implementations! 😎

@psmux

psmux commented Mar 19, 2026

Copy link
Copy Markdown
Owner

@ToxMox Yes they have been. Thank you!

@psmux psmux closed this Mar 19, 2026
ohboyftw pushed a commit to ohboyftw/psmux that referenced this pull request Mar 19, 2026
Brings 17 upstream commits into ohboy-builds:
- fix: warm claim race condition (psmux#136)
- fix: client_prefix flag, window_zoomed_flag (psmux#125, psmux#126)
- fix: Shift+Enter/Ctrl+Enter modifiers, paste normalization (psmux#131, psmux#132)
- fix: backslash escape, 6 client-server bugs (psmux#118, psmux#123)
- fix: -f global option (psmux#119)
- fix: set-hook replace/remove, zoomed navigation wrap (psmux#133, psmux#134)
- fix: TERM env var mapping, hyphenated option leak (psmux#137)
- feat: bell/activity/silence monitoring, allow-rename, update-environment
- feat: vim-style bind-key C-hjkl pane navigation (psmux#130)
- feat: XDG plugin path support (psmux#135)
- feat: auto-generate changelog in release workflow
- refactor: move Rust tests to tests-rs/ directory
- refactor: rich test dashboard

Also applies ohboy-builds stashed changes:
- Remove claude-code-fix-tty (Claude Code now auto-detects $TMUX)
- Migrate env::set_var to safe crate::util::set_env wrappers (Rust 1.83)
- Selection clamp to pane boundaries in copy mode
- Fix clippy warnings in forked crates (vt100-psmux, portable-pty-psmux)
souhaiebtar pushed a commit to souhaiebtar/psmux that referenced this pull request Mar 20, 2026
…PTY encoding (PR psmux#132)

PR psmux#131 - Multi-line paste reverse order in PowerShell:
- Root cause: write_paste_chunked sends clipboard text with bare LF to PTY pipe.
  ConPTY expects CR for Enter; bare LF confuses PSReadLine, causing reversed order.
- Fix: Normalize line endings (LF->CR, CRLF->CR) in write_paste_chunked before
  writing to the PTY pipe.

PR psmux#132 - Shift+Enter not working for VS Code terminal / Node.js apps:
- Root cause (client): VS Code xterm.js sends ESC+CR for Shift+Enter. ConPTY
  interprets ESC as Alt prefix, so crossterm misreports Alt+Enter.
- Root cause (server): CSI 13;2~ encoding is non-standard (code 13 not in VT
  function key table) and silently dropped by ConPTY.
- Fix (client): augment_enter_shift polls GetAsyncKeyState(VK_SHIFT) to detect
  physical Shift key and remaps crossterm's misreported Alt+Enter to Shift+Enter.
- Fix (server): encode_key_event and send_key_to_active send ESC+CR instead of
  CSI 13;mod~ for Shift/Alt+Enter on Windows. This matches VS Code xterm.js
  encoding and round-trips through ConPTY -> libuv correctly.
- Ctrl+Enter variants still use CSI encoding (unaffected by ConPTY).

Tests: 7 new unit tests covering paste normalization (LF, CRLF, mixed, bracketed)
and Shift/Alt+Enter ConPTY encoding. All 68 tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants