Skip to content

fix(cli): support Shift+Enter for newline insertion across terminals#87

Open
BingqingLyu wants to merge 11 commits into
mainfrom
pr-3103-fix-shift-enter-newline
Open

fix(cli): support Shift+Enter for newline insertion across terminals#87
BingqingLyu wants to merge 11 commits into
mainfrom
pr-3103-fix-shift-enter-newline

Conversation

@BingqingLyu

@BingqingLyu BingqingLyu commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes QwenLM#241 — Shift+Enter now inserts a newline instead of submitting the message.

Most terminals don't send a distinct escape sequence for Shift+Enter (they send the same \r as plain Enter). This PR adds three complementary mechanisms to make Shift+Enter work across all major terminals:

1. Native modifier detection for Apple Terminal (macOS)

  • New @qwen-code/modifiers-napi native addon calls macOS CGEventSourceFlagsState API to synchronously detect if Shift is physically held down when Enter arrives
  • KeypressContext checks native Shift state for Apple Terminal, which cannot distinguish Shift+Enter from Enter through escape sequences
  • Gracefully degrades on non-macOS platforms (returns false)

2. Expanded /terminal-setup command

  • Alacritty: writes [[keyboard.bindings]] to alacritty.toml
  • Zed: writes shift-enter binding to keymap.json
  • Apple Terminal: enables "Use Option as Meta Key" via PlistBuddy (with plist backup, no shell injection)
  • VSCode Remote SSH: detects remote sessions and provides manual instructions
  • VSCode/Cursor/Windsurf/Trae: sequence changed from \\\r\n to \u001b\r (ESC+CR) for more reliable parsing; old sequence still works for existing users via backslash detection

3. Improved user experience

  • Keyboard shortcuts help shows shift+enter instead of ctrl+j as the primary newline shortcut
  • Unsupported terminal message now lists all available alternatives and supported terminals

Terminal support matrix

Terminal Mechanism Setup needed?
iTerm2, Ghostty, Kitty, WezTerm, Warp CSI-u / Kitty protocol No
Apple Terminal Native modifier detection No (Option+Enter after /terminal-setup)
VS Code, Cursor, Windsurf, Trae Custom keybinding (ESC+CR) /terminal-setup
Alacritty Custom keybinding /terminal-setup
Zed Custom keybinding /terminal-setup
All terminals \ + Enter, Ctrl+Enter, Ctrl+J No

Test plan

  • Native addon compiles and all modifier keys return correct boolean values
  • TypeScript type check passes (0 errors in modified files)
  • All 484 existing tests pass (38 test files, 0 regressions)
  • esbuild bundle succeeds with no new warnings
  • 52-point verification script covering:
    • Native modifier detection (7 tests)
    • Terminal detection for 11 environment combinations
    • VSCode ESC+CR sequence byte correctness and JSON serialization
    • Key binding matching for all Enter variants (Submit vs Newline)
    • Keypress parsing simulation for VSCode, Apple Terminal, and Kitty paths
    • Remote SSH detection (5 cases)
    • PlistBuddy profile name escaping (including single quotes)
  • Pre-commit hooks (prettier + eslint) pass

🤖 Generated with Claude Code

doudouOUC and others added 11 commits April 10, 2026 19:37
Shift+Enter now inserts a newline instead of submitting the message.
This is achieved through three complementary mechanisms:

1. Native modifier detection for Apple Terminal (macOS):
   - New `@qwen-code/modifiers-napi` native addon calls
     CGEventSourceFlagsState to synchronously detect if Shift is held
   - KeypressContext checks native Shift state when Enter arrives
     in Apple Terminal (which can't distinguish Shift+Enter from Enter)

2. Expanded /terminal-setup command:
   - Alacritty: writes [[keyboard.bindings]] to alacritty.toml
   - Zed: writes shift-enter binding to keymap.json
   - Apple Terminal: enables "Use Option as Meta Key" via PlistBuddy
   - VSCode Remote SSH: detects remote sessions and provides manual
     instructions instead of modifying remote filesystem
   - VSCode sequence changed from backslash+CRLF to ESC+CR for
     more reliable parsing (old sequence still works via backslash
     detection for existing users)

3. Improved user experience:
   - Keyboard shortcuts help now shows "shift+enter" instead of
     "ctrl+j" as the primary newline shortcut
   - Unsupported terminal message lists all available alternatives

Closes QwenLM#241

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
- Fix incomplete string escaping in PlistBuddy profile name (CodeQL):
  escape backslashes before single quotes, reject control characters
- Add console.warn when native modifier module fails to load on macOS,
  so developers can diagnose missing builds instead of silent no-op

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
The "os": ["darwin"] field in package.json causes npm ci to fail
with EBADPLATFORM on Linux. Cross-platform safety is already handled
by scripts/install.js (exits 0 on non-darwin) and index.js (returns
no-op functions on non-darwin).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
The previous commit removed "os": ["darwin"] from modifiers-napi's
package.json, but the lock file still had the old constraint cached,
causing npm ci to fail on Linux CI with EBADPLATFORM.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
40 test cases covering:
- Key binding matching (Shift/Ctrl/Meta/Paste+Enter, Ctrl+J)
- VSCode ESC+CR sequence byte correctness and JSON serialization
- Terminal detection for 13 environment combinations
- VSCode Remote SSH detection (5 cases)
- PlistBuddy profile name escaping (quotes, backslashes, control chars, unicode)
- Modifiers wrapper graceful degradation
- Apple Terminal native shift detection simulation
- Backslash+Enter backward compatibility

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Creates scripts/e2e-verify-shift-enter.mjs that:
- Spawns the real CLI via node-pty (no LLM API key required)
- Uses @xterm/headless for ANSI-aware screen state capture
- Tests 5 key sequences: plain Enter, Ctrl+J, VSCode ESC+CR,
  Kitty CSI-u (with protocol handshake simulation), backslash+Enter
- Outputs structured Markdown report via --markdown flag
- All 5 tests pass on darwin arm64

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
- terminalSetup: fix dead code in VSCode configureVSCode() — check our
  own bindings first (idempotency), then check for conflicts; previously
  the any-binding guard blocked the already-configured path entirely
- terminalSetup: fix Alacritty and Zed idempotency — distinguish our
  own added binding from a conflicting third-party binding so /terminal-setup
  can be run multiple times safely
- terminalSetup: fix Zed invalid-JSON handling — return error instead of
  silently overwriting user's keymap with an empty array
- terminalSetup: replace shell exec('ps ... $PPID') with execFileAsync
  to eliminate potential shell expansion and align with security practices
  used elsewhere in the file
- modifiers: cache native module reference in getNativeModule() to avoid
  redundant require() calls on every Enter keypress in Apple Terminal

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
… errors

- eslint.config.js: add './scripts/**/*.mjs' to the scripts file pattern
  so Node.js globals (setTimeout, console, process) are available
- e2e-verify-shift-enter.mjs: add eslint-disable-next-line comments for
  intentional no-control-regex usages in the reporting formatter
- e2e-verify-shift-enter.mjs: add named catch variable to satisfy no-empty

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
… code

- terminalSetup: replace hand-rolled stripJsonComments regex with the
  existing strip-json-comments library — the regex only handled full-line
  comments but VS Code keybindings.json is JSONC (inline comments, block
  comments, trailing commas)
- i18n/zh.js: add ~35 missing Chinese translations for /terminal-setup
  command output (Remote SSH instructions, Alacritty/Zed/Apple Terminal
  messages, supported terminals list)
- i18n/en.js: add 3 missing locale keys from idempotency fix
  (Alacritty/Zed already-configured, Zed invalid-JSON)
- KeyboardShortcuts: simplify getExternalEditorKey — both ternary
  branches returned the same value ('ctrl+x')

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Modern terminals (VSCode, Cursor, iTerm2, Ghostty, Kitty, WezTerm, Warp)
already support Shift+Enter natively via the Kitty keyboard protocol.
The /terminal-setup command and native macOS modifier detection were
over-engineered solutions that either didn't work reliably or were
unnecessary for the target use cases.

Remove:
- packages/modifiers-napi: native C++ addon for Apple Terminal modifier
  detection — unreliable due to race condition between Shift key release
  and Node.js event processing
- terminalSetup.ts + terminalSetupCommand.ts: the /terminal-setup command
  and all terminal-specific configuration (VSCode keybindings, Alacritty
  TOML, Zed keymap, Apple Terminal PlistBuddy)
- All related i18n keys, tests, and build config references

Keep (the only code actually needed):
- Kitty protocol CSI-u sequence parsing (handles most modern terminals)
- ESC+CR (meta=true) detection for terminals sending \x1b\r
- Backslash+Enter fallback for any terminal
- Ctrl+J universal newline (already in keyMatchers, no change needed)

Rename shift-enter-newline.test.ts → newline-insertion.test.ts,
keeping only the key binding matching and backslash+Enter tests.

E2E: 5/5 scenarios pass (plain Enter, Ctrl+J, ESC+CR, Kitty CSI-u,
backslash+Enter).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Node's readline already parses ESC+CR (\x1b\r) as {name:'return',meta:true}.
The explicit override was only needed for the VSCode /terminal-setup
keybinding config, which has been removed.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@BingqingLyu BingqingLyu added conflicting-group-1 conflicting-group-1 Conflicting PR group 1 — review as a batch conflicting-pr Shares at least one cross-PR dependency with other PRs and removed conflicting-group-1 labels May 7, 2026
@BingqingLyu

BingqingLyu commented May 7, 2026

Copy link
Copy Markdown
Owner Author

Conflict Group 1

This PR shares modified functions with 6 other PR(s): #107, #37, #86, #9, #94, #96.

These PRs should be reviewed as a batch — merging one may affect the others.

Function File Also modified by
KeypressProvider KeypressContext.tsx #37, #94
loadCommands BuiltinCommandLoader.ts #107, #86, #9, #96
graph LR
    PR87["PR #87"]
    FKeypressProvider_396["KeypressProvider<br>KeypressContext.tsx"]
    PR87 -->|modifies| FKeypressProvider_396
    PR37["PR #37"]
    PR37 -->|modifies| FKeypressProvider_396
    PR94["PR #94"]
    PR94 -->|modifies| FKeypressProvider_396
    FloadCommands_7884["loadCommands<br>BuiltinCommandLoader.ts"]
    PR87 -->|modifies| FloadCommands_7884
    PR107["PR #107"]
    PR107 -->|modifies| FloadCommands_7884
    PR86["PR #86"]
    PR86 -->|modifies| FloadCommands_7884
    PR9["PR #9"]
    PR9 -->|modifies| FloadCommands_7884
    PR96["PR #96"]
    PR96 -->|modifies| FloadCommands_7884
Loading

Posted by codegraph-ai conflict detection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflicting-group-1 Conflicting PR group 1 — review as a batch conflicting-pr Shares at least one cross-PR dependency with other PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shift+Enter does not insert a newline; sends message instead

2 participants