diff --git a/packages/coding-agent/src/modes/utils/keybinding-matchers.ts b/packages/coding-agent/src/modes/utils/keybinding-matchers.ts index 654f3d967..4befdee2c 100644 --- a/packages/coding-agent/src/modes/utils/keybinding-matchers.ts +++ b/packages/coding-agent/src/modes/utils/keybinding-matchers.ts @@ -1,4 +1,4 @@ -import { getKeybindings, matchesKey } from "@oh-my-pi/pi-tui"; +import { getKeybindings, type KeyId, matchesKey } from "@oh-my-pi/pi-tui"; /** * Match the coding-agent interrupt key. @@ -50,6 +50,20 @@ export function matchesAppExternalEditor(data: string): boolean { return matchesKey(data, "ctrl+g"); } +function matchesEffectiveKey(data: string, key: KeyId): boolean { + if ((key === "ctrl+enter" || key === "ctrl+return") && data.charCodeAt(0) === 10 && data.length > 1) { + return true; + } + return matchesKey(data, key); +} + +function matchesEffectiveKeys(data: string, keys: readonly KeyId[]): boolean { + for (const key of keys) { + if (matchesEffectiveKey(data, key)) return true; + } + return false; +} + /** * Match the "submit multi-line text input" keybinding (`app.message.followUp`). * @@ -59,16 +73,14 @@ export function matchesAppExternalEditor(data: string): boolean { * `["ctrl+q", "ctrl+enter"]` so Windows Terminal (which can't deliver a distinct * Ctrl+Enter event; #1903) still has a working chord without user remapping. * - * Also recognizes a modifier-tagged LF (e.g. modifyOtherKeys legacy encoding for - * Ctrl+Enter), which the keybinding matcher itself does not cover. + * Also recognizes modifier-tagged LF as Ctrl+Enter only when Ctrl+Enter is an + * effective follow-up binding. */ export function matchesAppFollowUp(data: string): boolean { - // Modifier-tagged LF: terminals that send `\n` followed by the CSI modifier - // payload (legacy modifyOtherKeys) report Ctrl+Enter this way. - if (data.charCodeAt(0) === 10 && data.length > 1) return true; const keybindings = getKeybindings(); - if (keybindings.getKeys("app.message.followUp").length > 0) { - return keybindings.matches(data, "app.message.followUp"); + const keys = keybindings.getKeys("app.message.followUp"); + if (keys.length > 0) { + return matchesEffectiveKeys(data, keys); } - return matchesKey(data, "ctrl+enter") || matchesKey(data, "ctrl+q"); + return matchesEffectiveKeys(data, ["ctrl+enter", "ctrl+q"]); } diff --git a/packages/coding-agent/test/keybindings-migration.test.ts b/packages/coding-agent/test/keybindings-migration.test.ts index 6cca930c5..0197dd77e 100644 --- a/packages/coding-agent/test/keybindings-migration.test.ts +++ b/packages/coding-agent/test/keybindings-migration.test.ts @@ -3,9 +3,13 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { KeybindingsManager } from "@oh-my-pi/pi-coding-agent/config/keybindings"; +import { matchesAppFollowUp } from "@oh-my-pi/pi-coding-agent/modes/utils/keybinding-matchers"; import { setKeybindings } from "@oh-my-pi/pi-tui"; import { YAML } from "bun"; +function ctrl(key: string): string { + return String.fromCharCode(key.toLowerCase().charCodeAt(0) & 31); +} describe("KeybindingsManager.create", () => { beforeEach(() => { setKeybindings(KeybindingsManager.inMemory()); @@ -151,11 +155,14 @@ describe("KeybindingsManager.create", () => { const manager = KeybindingsManager.inMemory({ "app.plan.toggle": "ctrl+q", }); + setKeybindings(manager); expect(manager.getKeys("app.plan.toggle")).toEqual(["ctrl+q"]); expect(manager.getKeys("app.message.followUp")).toEqual(["ctrl+enter"]); expect(manager.getDisplayString("app.message.followUp")).toBe("Ctrl+Enter"); expect(manager.getEffectiveConfig()["app.message.followUp"]).toBe("ctrl+enter"); + expect(matchesAppFollowUp(ctrl("q"))).toBe(false); + expect(matchesAppFollowUp("\x1b[13;5u")).toBe(true); }); it("keeps the Ctrl+Q follow-up default when only an unknown config key claims it (#1903)", () => {