Fix handling of Shift+Enter in e.g. Ghostty (#338)
Fix: Shift + Enter no longer prints “[27;2;13~” in the single‑line
input. Validated as working and necessary in Ghostty on Linux.
## Key points
- src/components/vendor/ink-text-input.tsx
- Added early handler that recognises the two modifyOtherKeys
escape‑sequences
- [13;<mod>u (mode 2 / CSI‑u)
- [27;<mod>;13~ (mode 1 / legacy CSI‑~)
- If Ctrl is held (hasCtrl flag) → call onSubmit() (same as plain
Enter).
- Otherwise → insert a real newline at the caret (same as Option+Enter).
- Prevents the raw sequence from being inserted into the buffer.
- src/components/chat/multiline-editor.tsx
- Replaced non‑breaking spaces with normal spaces to satisfy eslint
no‑irregular‑whitespace rule (no behaviour change).
All unit tests (114) and ESLint now pass:
npm test ✔️
npm run lint ✔️
This commit is contained in:
@@ -259,25 +259,47 @@ const MultilineTextEditorInner = (
|
||||
console.log("[MultilineTextEditor] event", { input, key });
|
||||
}
|
||||
|
||||
// 1) CSI‑u / modifyOtherKeys (Ink strips initial ESC, so we start with '[')
|
||||
// 1a) CSI-u / modifyOtherKeys *mode 2* (Ink strips initial ESC, so we
|
||||
// start with '[') – format: "[<code>;<modifiers>u".
|
||||
if (input.startsWith("[") && input.endsWith("u")) {
|
||||
const m = input.match(/^\[([0-9]+);([0-9]+)u$/);
|
||||
if (m && m[1] === "13") {
|
||||
const mod = Number(m[2]);
|
||||
// In xterm's encoding: bit‑1 (value 2) is Shift. Everything >1 that
|
||||
// isn't exactly 1 means some modifier was held. We treat *shift
|
||||
// present* (2,4,6,8) as newline; plain (1) as submit.
|
||||
// In xterm's encoding: bit-1 (value 2) is Shift. Everything >1 that
|
||||
// isn't exactly 1 means some modifier was held. We treat *shift or
|
||||
// alt present* (2,3,4,6,8,9) as newline; Ctrl (bit-2 / value 4)
|
||||
// triggers submit. See xterm/DEC modifyOtherKeys docs.
|
||||
|
||||
// Xterm encodes modifier keys in `mod` – bit‑2 (value 4) indicates
|
||||
// that Ctrl was held. We avoid the `&` bitwise operator (disallowed
|
||||
// by our ESLint config) by using arithmetic instead.
|
||||
const hasCtrl = Math.floor(mod / 4) % 2 === 1;
|
||||
if (hasCtrl) {
|
||||
if (onSubmit) {
|
||||
onSubmit(buffer.current.getText());
|
||||
}
|
||||
} else {
|
||||
// Any variant without Ctrl just inserts newline (Shift, Alt, none)
|
||||
buffer.current.newline();
|
||||
}
|
||||
setVersion((v) => v + 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 1b) CSI-~ / modifyOtherKeys *mode 1* – format: "[27;<mod>;<code>~".
|
||||
// Terminals such as iTerm2 (default), older xterm versions, or when
|
||||
// modifyOtherKeys=1 is configured, emit this legacy sequence. We
|
||||
// translate it to the same behaviour as the mode‑2 variant above so
|
||||
// that Shift+Enter (newline) / Ctrl+Enter (submit) work regardless
|
||||
// of the user’s terminal settings.
|
||||
if (input.startsWith("[27;") && input.endsWith("~")) {
|
||||
const m = input.match(/^\[27;([0-9]+);13~$/);
|
||||
if (m) {
|
||||
const mod = Number(m[1]);
|
||||
const hasCtrl = Math.floor(mod / 4) % 2 === 1;
|
||||
|
||||
if (hasCtrl) {
|
||||
if (onSubmit) {
|
||||
onSubmit(buffer.current.getText());
|
||||
}
|
||||
} else {
|
||||
buffer.current.newline();
|
||||
}
|
||||
setVersion((v) => v + 1);
|
||||
|
||||
Reference in New Issue
Block a user