diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index e885fd2df..e55331956 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -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 diff --git a/packages/tui/src/components/editor.ts b/packages/tui/src/components/editor.ts index b85a8b9ff..069d6fbf1 100644 --- a/packages/tui/src/components/editor.ts +++ b/packages/tui/src/components/editor.ts @@ -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)) { diff --git a/packages/tui/test/editor.test.ts b/packages/tui/test/editor.test.ts index f884fb314..437981b81 100644 --- a/packages/tui/test/editor.test.ts +++ b/packages/tui/test/editor.test.ts @@ -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);