pi-mono/packages/coding-agent/test/custom-editor-keybindings.test.ts
Petr Houška 3bae85274a fix(tui,coding-agent): handle selection page keys in all scrolling lists
tui.select.pageUp/pageDown were configurable but ignored by SelectList,
SettingsList, editor autocomplete, and most coding-agent selectors. In
fullscreen mode, the default PageUp/PageDown also scrolled the transcript
instead of reaching the focused list.

- add getSelectPageIndex() and handle page keys in SelectList, SettingsList,
  editor autocomplete, and the model, scoped models, auth, user message,
  settings submenu, llama search, and /mcp selectors
- add Component.capturesViewportInput: a focused component that sets it
  receives fullscreen viewport keys instead of the transcript, as a focused
  overlay already does
- let page keys move an open autocomplete menu before app actions, so a
  remapped Ctrl+V pages the menu instead of pasting an image

closes #7629

Co-authored-by: muyiyr <940955635@qq.com>
2026-10-28 19:05:21 +01:00

79 lines
2.7 KiB
TypeScript

import { setKeybindings, TuiMainScreen } from "@earendil-works/pi-tui";
import { afterEach, describe, expect, it, vi } from "vitest";
import { defaultEditorTheme } from "../../tui/test/test-themes.ts";
import { VirtualTerminal } from "../../tui/test/virtual-terminal.ts";
import { KeybindingsManager } from "../src/core/keybindings.ts";
import { CustomEditor } from "../src/modes/interactive/components/custom-editor.ts";
afterEach(() => {
setKeybindings(new KeybindingsManager());
});
describe("CustomEditor prompt history keybindings", () => {
it("gives an explicit history binding precedence over model cycling", () => {
const keybindings = new KeybindingsManager({
"tui.editor.historyPrevious": "ctrl+p",
"tui.editor.historyNext": "ctrl+n",
});
setKeybindings(keybindings);
const editor = new CustomEditor(new TuiMainScreen(new VirtualTerminal()), defaultEditorTheme, keybindings);
let modelCycles = 0;
editor.onAction("app.model.cycleForward", () => {
modelCycles++;
});
editor.addToHistory("previous prompt");
editor.setText("draft");
editor.handleInput("\x10"); // Ctrl+P
expect(editor.getText()).toBe("previous prompt");
expect(modelCycles).toBe(0);
editor.handleInput("\x0e"); // Ctrl+N
expect(editor.getText()).toBe("draft");
});
});
describe("CustomEditor selection page keybindings", () => {
it("pages an open autocomplete menu before pasting an image with the same key (#7629)", async () => {
// Bind both explicitly: the paste default is Alt+V on Windows and WSL.
const keybindings = new KeybindingsManager({
"tui.select.pageDown": "ctrl+v",
"app.clipboard.pasteImage": "ctrl+v",
});
setKeybindings(keybindings);
const editor = new CustomEditor(new TuiMainScreen(new VirtualTerminal()), defaultEditorTheme, keybindings);
let pastes = 0;
editor.onPasteImage = () => {
pastes++;
};
let submitted = "";
editor.onSubmit = (text) => {
submitted = text;
};
editor.setAutocompleteProvider({
getSuggestions: async () => ({
items: Array.from({ length: 12 }, (_, index) => ({
value: `/command-${index}`,
label: `command-${index}`,
})),
prefix: "/",
}),
applyCompletion: (lines, cursorLine, _cursorCol, item) => ({
lines: lines.map((line, index) => (index === cursorLine ? item.value : line)),
cursorLine,
cursorCol: item.value.length,
}),
});
editor.handleInput("/");
await vi.waitFor(() => expect(editor.isShowingAutocomplete()).toBe(true));
editor.handleInput("\x16"); // Ctrl+V
editor.handleInput("\r");
expect(pastes).toBe(0);
expect(submitted).toBe("/command-5");
editor.handleInput("\x16"); // Ctrl+V with autocomplete closed pastes again
expect(pastes).toBe(1);
});
});