Fix two editor input bugs: 1. Arrow key cursor movement not visually updating (fixes #464) The layout cache key only included {width, textVersion}. Cursor-only moves don't change textVersion, so stale cached layout was returned and the diff renderer skipped repaint. Added cursorLine and cursorCol to the cache key so cursor movements invalidate the cache. 2. Shift+Enter not inserting newlines in non-kitty terminals (Zed, VS Code, etc.) The /terminal-setup command configures terminals to send ESC+CR (\x1b\r) for Shift+Enter. But the followUp app action (bound to alt+enter) was intercepting \x1b\r in CustomEditor.handleInput before the editor's newLine handler could see it — because in non-kitty terminals, \x1b\r matches alt+enter. Now when kitty protocol is not active and \x1b\r is received, the followUp match is skipped so it falls through to newLine. Alt+Enter followUp still works in kitty-protocol terminals (iTerm2, Ghostty, Kitty, WezTerm) where the key combos are distinguishable. Co-authored-by: TÂCHES <afromanguy@me.com>
87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
import { Editor, type EditorOptions, type EditorTheme, type TUI, isKittyProtocolActive } from "@gsd/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, options?: EditorOptions) {
|
|
super(tui, theme, options);
|
|
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;
|
|
}
|
|
// Fall through to editor handling for delete-char-forward when not empty
|
|
}
|
|
|
|
// Check all other app actions
|
|
for (const [action, handler] of this.actionHandlers) {
|
|
if (action !== "interrupt" && action !== "exit" && this.keybindings.matches(data, action)) {
|
|
// When kitty protocol is not active, \x1b\r is ambiguous:
|
|
// it could be alt+enter (followUp) or shift+enter mapped via /terminal-setup.
|
|
// Prioritize newLine since that's what terminal-setup configures.
|
|
// Alt+enter followUp still works in kitty-protocol terminals.
|
|
if (action === "followUp" && !isKittyProtocolActive() && data === "\x1b\r") {
|
|
break; // Fall through to parent editor's newLine handling
|
|
}
|
|
handler();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Pass to parent for editor handling
|
|
super.handleInput(data);
|
|
}
|
|
}
|