mirror of
https://github.com/earendil-works/pi.git
synced 2026-06-18 15:54:04 +08:00
The Editor component now accepts TUI as the first constructor parameter, enabling it to query terminal dimensions. When content exceeds available height, the editor scrolls vertically keeping the cursor visible. Features: - Max editor height is 30% of terminal rows (minimum 5 lines) - Page Up/Down keys scroll by page size - Scroll indicators show lines above/below: ─── ↑ 5 more ─── Breaking change: Editor constructor signature changed from new Editor(theme) to new Editor(tui, theme) fixes #732
80 lines
2.2 KiB
TypeScript
80 lines
2.2 KiB
TypeScript
import { Editor, type EditorTheme, type TUI } from "@mariozechner/pi-tui";
|
|
import type { AppAction, KeybindingsManager } from "../../../core/keybindings.js";
|
|
|
|
/**
|
|
* Custom editor that handles app-level keybindings for coding-agent.
|
|
*/
|
|
export class CustomEditor extends Editor {
|
|
private keybindings: KeybindingsManager;
|
|
public actionHandlers: Map<AppAction, () => void> = new Map();
|
|
|
|
// Special handlers that can be dynamically replaced
|
|
public onEscape?: () => void;
|
|
public onCtrlD?: () => void;
|
|
public onPasteImage?: () => void;
|
|
/** Handler for extension-registered shortcuts. Returns true if handled. */
|
|
public onExtensionShortcut?: (data: string) => boolean;
|
|
|
|
constructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) {
|
|
super(tui, theme);
|
|
this.keybindings = keybindings;
|
|
}
|
|
|
|
/**
|
|
* Register a handler for an app action.
|
|
*/
|
|
onAction(action: AppAction, handler: () => void): void {
|
|
this.actionHandlers.set(action, handler);
|
|
}
|
|
|
|
handleInput(data: string): void {
|
|
// Check extension-registered shortcuts first
|
|
if (this.onExtensionShortcut?.(data)) {
|
|
return;
|
|
}
|
|
|
|
// Check for paste image keybinding
|
|
if (this.keybindings.matches(data, "pasteImage")) {
|
|
this.onPasteImage?.();
|
|
return;
|
|
}
|
|
|
|
// Check app keybindings first
|
|
|
|
// Escape/interrupt - only if autocomplete is NOT active
|
|
if (this.keybindings.matches(data, "interrupt")) {
|
|
if (!this.isShowingAutocomplete()) {
|
|
// Use dynamic onEscape if set, otherwise registered handler
|
|
const handler = this.onEscape ?? this.actionHandlers.get("interrupt");
|
|
if (handler) {
|
|
handler();
|
|
return;
|
|
}
|
|
}
|
|
// Let parent handle escape for autocomplete cancellation
|
|
super.handleInput(data);
|
|
return;
|
|
}
|
|
|
|
// Exit (Ctrl+D) - only when editor is empty
|
|
if (this.keybindings.matches(data, "exit")) {
|
|
if (this.getText().length === 0) {
|
|
const handler = this.onCtrlD ?? this.actionHandlers.get("exit");
|
|
if (handler) handler();
|
|
}
|
|
return; // Always consume
|
|
}
|
|
|
|
// Check all other app actions
|
|
for (const [action, handler] of this.actionHandlers) {
|
|
if (action !== "interrupt" && action !== "exit" && this.keybindings.matches(data, action)) {
|
|
handler();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Pass to parent for editor handling
|
|
super.handleInput(data);
|
|
}
|
|
}
|