是谁吞掉了Terminal的Ctrl+Enter
是谁吞掉了Terminal的Ctrl+Enter
事由
发现在Windows Terminal中的应用无法识别到我发送的ctrl+enter信号
原因
是历史遗留、兼容性问题。
解释1
Ctrl+Enter is being “swallowed” because traditional terminal protocols don’t have a distinct encoding for it.
In the VT (Virtual Terminal) protocol that terminals use, Ctrl works by clearing bits 5-6 of the keycode (key AND 0x1F). Here’s what happens: |You Press| Byte Sent| Also Sent By| |———|———-|————–| |Enter |0x0D |Ctrl+M| |Ctrl+Enter |0x0D |Enter|
They’re identical. The terminal sends the same ^M (0x0D, carriage return) for both. To Windows Terminal (and most terminals), Ctrl+Enter is Enter.
From Microsoft’s DHowett: > “Alas, ^J is the canonical encoding for Ctrl+Enter in VT. (In the case of control characters, Shift doesn’t matter.)”
The fix in progress: - Windows Terminal merged PR #19817 (Feb 2026) adding Kitty protocol support (CSI u encoding) - This enables distinct sequences like ESC[13;5u for Ctrl+Enter - Targeted for Windows Terminal v1.25 (not yet released)
Workarounds today: 1. Use sendInput in Windows Terminal settings to manually send the CSI u sequence: 1
{ keys: ctrl+enter, command: { action: sendInput, input: \u001b[13;5u } }
Ctrl+J (which sends 0x0A, distinct from Enter)
解释2
Windows Terminal(以及几乎所有传统终端模拟器)“吞掉”或无法正确区分 Ctrl+Enter 的根本原因,是历史遗留的终端输入协议限制,而不是 Windows Terminal 故意设计成这样。
- 核心原因:
ASCII+VT协议的古老设计(1960s–1980s)
ASCII 控制字符时代(Ctrl 键的工作原理):
Ctrl 键本质上是把字符的第 6 和 7 位清零(bit masking)。
例如:Enter 键通常发送 \r( carriage return,0x0D)或 \n(0x0A)。
Ctrl + J(^J)在 ASCII 中就是 0x0A(Line Feed),而 Ctrl + M(^M)是 0x0D。
Ctrl + Enter 在早期协议里没有独立的编码,它经常被映射成和 Ctrl + J(或单纯的 Enter)一样的字节序列。
VT(Virtual Terminal)序列标准(xterm 等 Unix 终端使用的协议): 大多数修饰键 + Enter 的组合(如 Ctrl+Enter、Shift+Enter)没有标准 VT 转义序列 来区分它们。
结果就是:终端收到 Ctrl+Enter 后,要么直接丢弃(swallow),要么降级发送成普通的 \n 或 \r,要么根本不传递给运行中的程序(比如 OpenCode 的 TUI)。
这不是 Windows Terminal 独有的问题,几乎所有传统终端(包括老版 conhost、xterm、gnome-terminal 等)都存在同样限制。
简单说:终端协议从设计之初就没打算让程序可靠地区分“普通 Enter”和“带 Ctrl/Shift 的 Enter”,因为那时候的硬件和协议(电传打字机 → VT100)不需要这么精细的输入。
Windows Terminal为什么会“吞”或映射它?
兼容性优先:Windows Terminal 要同时支持传统 Windows Console 应用(cmd、PowerShell)和现代 VT 应用(WSL、Linux 工具)。如果它把 Ctrl+Enter 完全透传,可能破坏大量老程序的输入处理。
默认行为:对于无法用标准 VT 编码的键组合,Windows Terminal 倾向于“安全降级”——要么发送基础字符,要么不发送(防止意外触发)。
类似问题还包括:Ctrl+;、Ctrl+Shift+字母等很多组合,都会出现“被吃掉”或变成其他字符的情况(GitHub 上有很多相关 issue)。
这属于 By Design(按设计如此),主要是为了历史兼容,而不是 bug。
- 现代解决方案:
CSI u协议(Keyboard Protocol) 近年来,终端社区在推动更现代的输入协议:
CSI u(或 Kitty Keyboard Protocol):能明确编码 “Ctrl+Enter” 为 \u001b[13;5u(13 是 Enter 的 codepoint,5 表示 Ctrl 修饰符)。
支持这个协议的程序(如较新的 Neovim、Zellij、某些 TUI)就能精确区分 Ctrl+Enter 和普通 Enter。
Windows Terminal 已经部分支持(可以用 sendInput 手动转发 CSI u 序列,这就是我们之前让你加的那段配置的作用)。
完整原生支持预计会在未来版本进一步完善。
总结:为什么这么设计?
历史包袱:终端输入协议诞生于 60 年代,Ctrl+Enter 从来不是“标准”组合,所以没有为其预留独立编码。
兼容性 vs 现代性:终端模拟器必须先保证老程序不崩溃,再考虑新功能。 结果:对于像 OpenCode、Neovim、Claude Code 等现代 TUI 应用来说,用户经常需要手动配置(用 sendInput 或启用高级 keyboard protocol)才能让 Ctrl+Enter 正常工作。
这也是为什么我们在 WSL + Windows Terminal 中配 OpenCode 时,必须额外加 sendInput 来“手动”告诉终端该发送什么序列。
解决方案
在
"actions"数组的最后(]之前)添加一个新对象:1
2
3
4
5
6
7{
"command": {
"action": "sendInput",
"input": "\u001b[13;5u"
},
"id": "User.sendInput.CtrlEnter"
}在
"keybindings"数组的最后(]之前)添加对应的绑定:1
2
3
4{
"id": "User.sendInput.CtrlEnter",
"keys": "ctrl+enter"
}
Knighthana
2026/04/19