Files
oh-my-pi/packages/coding-agent/test/custom-editor-keybindings.test.ts
T
roboomp 27f4965e00 fix(coding-agent): stop ESC from aborting agent run while @ autocomplete is open
When the autocomplete popup is visible, ESC unconditionally falls through
to the editor base class so it can dismiss the popup. Only an ESC with no
popup visible reaches onEscape and routes to the global interrupt handler.

Removed the shouldBypassAutocompleteOnEscape callback that paved over the
popup-dismissal path whenever the agent was busy (streaming, bash, /btw,
auto-compaction, etc.) — that is precisely the moment the user is most
likely to hit ESC while the popup is open, and dismissing-then-aborting in
one keystroke is a footgun, not a feature. Two-press semantics now match
the standard TUI/IDE pattern: first ESC closes the popup, second ESC
aborts.

Tests cover both branches in custom-editor-keybindings.test.ts and the
input-controller escape suite no longer asserts the dead callback.

Fixes #1655
2026-06-01 17:26:10 +00:00

91 lines
2.8 KiB
TypeScript

import { describe, expect, it, vi } from "bun:test";
import { defaultEditorTheme } from "../../tui/test/test-themes";
import { CustomEditor } from "../src/modes/components/custom-editor";
function ctrl(key: string): string {
return String.fromCharCode(key.toLowerCase().charCodeAt(0) & 31);
}
function createEditor() {
return new CustomEditor(defaultEditorTheme);
}
describe("CustomEditor literal question mark input", () => {
it("does not reserve ? as a hotkeys shortcut when the editor is empty", () => {
const editor = createEditor();
editor.handleInput("?");
expect(editor.getText()).toBe("?");
});
});
describe("CustomEditor temporary model selector keybinding", () => {
it("triggers the temporary selector from a remapped action key instead of Alt+P", () => {
const editor = createEditor();
const onSelectModelTemporary = vi.fn();
editor.onSelectModelTemporary = onSelectModelTemporary;
editor.setActionKeys("app.model.selectTemporary", ["ctrl+y"]);
editor.handleInput(ctrl("y"));
expect(onSelectModelTemporary).toHaveBeenCalledTimes(1);
editor.handleInput("\x1bp");
expect(onSelectModelTemporary).toHaveBeenCalledTimes(1);
});
it("removes the default Alt+P shortcut when the action is disabled", () => {
const editor = createEditor();
const onSelectModelTemporary = vi.fn();
editor.onSelectModelTemporary = onSelectModelTemporary;
editor.handleInput("\x1bp");
expect(onSelectModelTemporary).toHaveBeenCalledTimes(1);
editor.setActionKeys("app.model.selectTemporary", []);
editor.handleInput("\x1bp");
expect(onSelectModelTemporary).toHaveBeenCalledTimes(1);
});
});
describe("CustomEditor escape key dispatch", () => {
function installAutocompleteProvider(editor: CustomEditor) {
editor.setAutocompleteProvider({
async getSuggestions() {
return { items: [{ label: "src/", value: "src/" }], prefix: "@" };
},
applyCompletion(lines, cursorLine, cursorCol) {
return { lines, cursorLine, cursorCol };
},
});
}
it("dismisses the autocomplete popup on the first ESC and only fires onEscape on the second", async () => {
const editor = createEditor();
const onEscape = vi.fn();
editor.onEscape = onEscape;
installAutocompleteProvider(editor);
editor.handleInput("@");
// Yield so the async provider populates and the popup opens.
await Bun.sleep(0);
expect(editor.isShowingAutocomplete()).toBe(true);
editor.handleInput("\x1b");
expect(editor.isShowingAutocomplete()).toBe(false);
expect(onEscape).not.toHaveBeenCalled();
editor.handleInput("\x1b");
expect(onEscape).toHaveBeenCalledTimes(1);
});
it("fires onEscape immediately when no autocomplete popup is visible", () => {
const editor = createEditor();
const onEscape = vi.fn();
editor.onEscape = onEscape;
editor.handleInput("\x1b");
expect(onEscape).toHaveBeenCalledTimes(1);
});
});