fix(tui): honor tui.input.submit remap onto ctrl+enter

The multiline editor's key dispatch checked a hardcoded Ctrl/Shift+Enter
-> newline branch before the config-driven tui.input.submit branch, so a
user remap of submit onto Ctrl+Enter was swallowed as a newline and never
submitted. Gate the hardcoded newline fallbacks behind an explicit submit
binding; the bare-LF (iTerm2 Shift+Enter) case stays exempt because its
canonical form is indistinguishable from plain Enter.

Fixes #8906
This commit is contained in:
roboomp
2026-08-19 09:10:10 +00:00
parent d94bdfa1bb
commit caace28295
3 changed files with 45 additions and 7 deletions
+1
View File
@@ -5,6 +5,7 @@
### Fixed
- Fixed images rendering as the `[Image: …]` text card on SIXEL terminals that expose no identifying environment variable (foot, xterm, contour): the graphics probe no longer requires Windows Terminal, and no longer reads an XTSMGRAPHICS success reply as a failure.
- Fixed the multiline editor ignoring a `tui.input.submit` remap onto Ctrl+Enter: the hardcoded Ctrl/Shift+Enter → newline fallbacks now yield to an explicit submit binding, so Ctrl+Enter can be used to submit ([#8906](https://github.com/can1357/oh-my-pi/issues/8906)).
## [17.3.5] - 2026-08-16
+12 -7
View File
@@ -1458,14 +1458,19 @@ export class Editor implements Component, Focusable {
this.#addNewLine();
}
}
// New line
// New line. A key the user explicitly bound to `tui.input.submit` wins
// over these hardcoded newline fallbacks, so Ctrl/Shift+Enter can be
// remapped to submit (#8906). The bare-LF case is exempt: its canonical
// form is "enter" (indistinguishable from plain Enter), so gating it
// would hijack the default Enter=submit binding.
else if (
(data.charCodeAt(0) === 10 && data.length > 1) || // Ctrl+Enter with modifiers
matchesKey(data, "ctrl+enter") || // Ctrl+Enter (Kitty/modifyOtherKeys, including lock bits/keypad Enter)
data === "\x1b\r" || // Option+Enter in some terminals (legacy)
data === "\x1b[13;2~" || // Shift+Enter in some terminals (legacy format)
kb.matchesCanonical(canonical, "tui.input.newLine") || // Shift+Enter (Kitty protocol, handles lock bits)
(data.length > 1 && data.includes("\x1b") && data.includes("\r")) ||
(!kb.matchesCanonical(canonical, "tui.input.submit") &&
((data.charCodeAt(0) === 10 && data.length > 1) || // Ctrl+Enter with modifiers
matchesKey(data, "ctrl+enter") || // Ctrl+Enter (Kitty/modifyOtherKeys, including lock bits/keypad Enter)
data === "\x1b\r" || // Option+Enter in some terminals (legacy)
data === "\x1b[13;2~" || // Shift+Enter in some terminals (legacy format)
kb.matchesCanonical(canonical, "tui.input.newLine") || // Shift+Enter (Kitty protocol, handles lock bits)
(data.length > 1 && data.includes("\x1b") && data.includes("\r")))) ||
(data === "\n" && data.length === 1) // Shift+Enter from iTerm2 mapping
) {
if (this.#shouldSubmitOnBackslashEnter(data, kb)) {
+32
View File
@@ -164,6 +164,38 @@ describe("Editor component", () => {
});
});
describe("Submit/newline keybindings", () => {
it("submits on Ctrl+Enter when tui.input.submit is remapped to it (#8906)", () => {
setKeybindings(
new KeybindingsManager(TUI_KEYBINDINGS, {
"tui.input.submit": "ctrl+enter",
"tui.input.newLine": "enter",
}),
);
const editor = new Editor(defaultEditorTheme);
editor.setText("hello");
let submitted: string | undefined;
editor.onSubmit = text => {
submitted = text;
};
editor.handleInput("\x1b[13;5u"); // kitty CSI-u Ctrl+Enter
expect(submitted).toBe("hello");
expect(editor.getText()).toBe("");
});
it("still inserts a newline on Ctrl+Enter under the default bindings", () => {
const editor = new Editor(defaultEditorTheme);
editor.setText("hello");
let submitted: string | undefined;
editor.onSubmit = text => {
submitted = text;
};
editor.handleInput("\x1b[13;5u"); // kitty CSI-u Ctrl+Enter
expect(submitted).toBeUndefined();
expect(editor.getText()).toBe("hello\n");
});
});
describe("Prompt history navigation", () => {
it("does nothing on Up arrow when history is empty", () => {
const editor = new Editor(defaultEditorTheme);