diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index c5624e4d..ad986930 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,9 +2,9 @@ ## [Unreleased] -### Fixed +### Added -- **Shift+Enter for newlines**: Fixed Shift+Enter not working for newlines in Ghostty, Kitty, WezTerm, and other terminals supporting the Kitty keyboard protocol. Also fixed Alt+Enter, Shift+Tab, and all Ctrl+key combinations (Ctrl+A/C/E/K/O/P/T/U/W). (by [@kim0](https://github.com/kim0)) +- **Kitty keyboard protocol support**: Added support for the Kitty keyboard protocol, enabling Shift+Enter, Alt+Enter, Shift+Tab, Ctrl+D to exit, and all Ctrl+key combinations to work in Ghostty, Kitty, WezTerm, and other modern terminals. (by [@kim0](https://github.com/kim0)) ## [0.23.4] - 2025-12-18 diff --git a/packages/coding-agent/src/modes/interactive/components/custom-editor.ts b/packages/coding-agent/src/modes/interactive/components/custom-editor.ts index 74989d42..92b8ab0c 100644 --- a/packages/coding-agent/src/modes/interactive/components/custom-editor.ts +++ b/packages/coding-agent/src/modes/interactive/components/custom-editor.ts @@ -1,4 +1,4 @@ -import { Editor, isCtrlC, isCtrlO, isCtrlP, isCtrlT, isShiftTab } from "@mariozechner/pi-tui"; +import { Editor, isCtrlC, isCtrlD, isCtrlO, isCtrlP, isCtrlT, isShiftTab } from "@mariozechner/pi-tui"; /** * Custom editor that handles Escape and Ctrl+C keys for coding-agent @@ -6,6 +6,7 @@ import { Editor, isCtrlC, isCtrlO, isCtrlP, isCtrlT, isShiftTab } from "@marioze export class CustomEditor extends Editor { public onEscape?: () => void; public onCtrlC?: () => void; + public onCtrlD?: () => void; public onShiftTab?: () => void; public onCtrlP?: () => void; public onCtrlO?: () => void; @@ -49,6 +50,15 @@ export class CustomEditor extends Editor { return; } + // Intercept Ctrl+D (only when editor is empty) + if (isCtrlD(data)) { + if (this.getText().length === 0 && this.onCtrlD) { + this.onCtrlD(); + } + // Always consume Ctrl+D (don't pass to parent) + return; + } + // Pass to parent for normal handling super.handleInput(data); } diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 35023b58..ed0e4ef4 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -562,6 +562,7 @@ export class InteractiveMode { }; this.editor.onCtrlC = () => this.handleCtrlC(); + this.editor.onCtrlD = () => this.handleCtrlD(); this.editor.onShiftTab = () => this.cycleThinkingLevel(); this.editor.onCtrlP = () => this.cycleModel(); this.editor.onCtrlO = () => this.toggleToolOutputExpansion(); @@ -1128,6 +1129,12 @@ export class InteractiveMode { } } + private handleCtrlD(): void { + // Only called when editor is empty (enforced by CustomEditor) + this.stop(); + process.exit(0); + } + private updateEditorBorderColor(): void { if (this.isBashMode) { this.editor.borderColor = theme.getBashModeBorderColor(); diff --git a/packages/tui/src/index.ts b/packages/tui/src/index.ts index 185f23b4..439c2925 100644 --- a/packages/tui/src/index.ts +++ b/packages/tui/src/index.ts @@ -23,6 +23,7 @@ export { isAltBackspace, isCtrlA, isCtrlC, + isCtrlD, isCtrlE, isCtrlK, isCtrlO, diff --git a/packages/tui/src/keys.ts b/packages/tui/src/keys.ts index 0c4b7c38..bac4557d 100644 --- a/packages/tui/src/keys.ts +++ b/packages/tui/src/keys.ts @@ -18,6 +18,7 @@ const CODEPOINTS = { // Letters (lowercase ASCII) a: 97, c: 99, + d: 100, e: 101, k: 107, o: 111, @@ -52,6 +53,7 @@ export const Keys = { // Ctrl+ combinations CTRL_A: kittySequence(CODEPOINTS.a, MODIFIERS.ctrl), CTRL_C: kittySequence(CODEPOINTS.c, MODIFIERS.ctrl), + CTRL_D: kittySequence(CODEPOINTS.d, MODIFIERS.ctrl), CTRL_E: kittySequence(CODEPOINTS.e, MODIFIERS.ctrl), CTRL_K: kittySequence(CODEPOINTS.k, MODIFIERS.ctrl), CTRL_O: kittySequence(CODEPOINTS.o, MODIFIERS.ctrl), @@ -97,6 +99,7 @@ export function isKittyKey(data: string, codepoint: number, modifier: number): b const RAW = { CTRL_A: "\x01", CTRL_C: "\x03", + CTRL_D: "\x04", CTRL_E: "\x05", CTRL_K: "\x0b", CTRL_O: "\x0f", @@ -122,6 +125,13 @@ export function isCtrlC(data: string): boolean { return data === RAW.CTRL_C || data === Keys.CTRL_C; } +/** + * Check if input matches Ctrl+D (raw byte or Kitty protocol). + */ +export function isCtrlD(data: string): boolean { + return data === RAW.CTRL_D || data === Keys.CTRL_D; +} + /** * Check if input matches Ctrl+E (raw byte or Kitty protocol). */