fix(coding-agent): respected effective follow-up bindings

This commit is contained in:
roboomp
2026-06-08 20:39:50 +00:00
parent b91b6aa0da
commit bc839d4745
2 changed files with 28 additions and 9 deletions
@@ -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"]);
}
@@ -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)", () => {