From 345fa975f106043ea6a947759e6b5f49a2187216 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 2 Jan 2026 22:43:39 +0100 Subject: [PATCH] Handle ctrlc like escape in selectors (#400) Cheers @nicokosi --- .../modes/interactive/components/hook-editor.ts | 6 +++--- .../modes/interactive/components/hook-input.ts | 6 +++--- .../modes/interactive/components/hook-selector.ts | 6 +++--- .../interactive/components/model-selector.ts | 5 +++-- .../interactive/components/oauth-selector.ts | 15 ++++++++++++--- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/coding-agent/src/modes/interactive/components/hook-editor.ts b/packages/coding-agent/src/modes/interactive/components/hook-editor.ts index 6efc67cd..3b1282b4 100644 --- a/packages/coding-agent/src/modes/interactive/components/hook-editor.ts +++ b/packages/coding-agent/src/modes/interactive/components/hook-editor.ts @@ -7,7 +7,7 @@ import { spawnSync } from "node:child_process"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; -import { Container, Editor, isCtrlG, isEscape, Spacer, Text, type TUI } from "@mariozechner/pi-tui"; +import { Container, Editor, isCtrlC, isCtrlG, isEscape, Spacer, Text, type TUI } from "@mariozechner/pi-tui"; import { getEditorTheme, theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; @@ -67,8 +67,8 @@ export class HookEditorComponent extends Container { return; } - // Escape to cancel - if (isEscape(keyData)) { + // Escape or Ctrl+C to cancel + if (isEscape(keyData) || isCtrlC(keyData)) { this.onCancelCallback(); return; } diff --git a/packages/coding-agent/src/modes/interactive/components/hook-input.ts b/packages/coding-agent/src/modes/interactive/components/hook-input.ts index a7b30ba0..e76c41e5 100644 --- a/packages/coding-agent/src/modes/interactive/components/hook-input.ts +++ b/packages/coding-agent/src/modes/interactive/components/hook-input.ts @@ -2,7 +2,7 @@ * Simple text input component for hooks. */ -import { Container, Input, isEnter, isEscape, Spacer, Text } from "@mariozechner/pi-tui"; +import { Container, Input, isCtrlC, isEnter, isEscape, Spacer, Text } from "@mariozechner/pi-tui"; import { theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; @@ -52,8 +52,8 @@ export class HookInputComponent extends Container { return; } - // Escape to cancel - if (isEscape(keyData)) { + // Escape or Ctrl+C to cancel + if (isEscape(keyData) || isCtrlC(keyData)) { this.onCancelCallback(); return; } diff --git a/packages/coding-agent/src/modes/interactive/components/hook-selector.ts b/packages/coding-agent/src/modes/interactive/components/hook-selector.ts index e39a9119..2238c79f 100644 --- a/packages/coding-agent/src/modes/interactive/components/hook-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/hook-selector.ts @@ -3,7 +3,7 @@ * Displays a list of string options with keyboard navigation. */ -import { Container, isArrowDown, isArrowUp, isEnter, isEscape, Spacer, Text } from "@mariozechner/pi-tui"; +import { Container, isArrowDown, isArrowUp, isCtrlC, isEnter, isEscape, Spacer, Text } from "@mariozechner/pi-tui"; import { theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; @@ -83,8 +83,8 @@ export class HookSelectorComponent extends Container { this.onSelectCallback(selected); } } - // Escape - else if (isEscape(keyData)) { + // Escape or Ctrl+C + else if (isEscape(keyData) || isCtrlC(keyData)) { this.onCancelCallback(); } } diff --git a/packages/coding-agent/src/modes/interactive/components/model-selector.ts b/packages/coding-agent/src/modes/interactive/components/model-selector.ts index 0d1ad239..99429220 100644 --- a/packages/coding-agent/src/modes/interactive/components/model-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/model-selector.ts @@ -4,6 +4,7 @@ import { Input, isArrowDown, isArrowUp, + isCtrlC, isEnter, isEscape, Spacer, @@ -234,8 +235,8 @@ export class ModelSelectorComponent extends Container { this.handleSelect(selectedModel.model); } } - // Escape - else if (isEscape(keyData)) { + // Escape or Ctrl+C + else if (isEscape(keyData) || isCtrlC(keyData)) { this.onCancelCallback(); } // Pass everything else to search input diff --git a/packages/coding-agent/src/modes/interactive/components/oauth-selector.ts b/packages/coding-agent/src/modes/interactive/components/oauth-selector.ts index 1a5a480b..ac97c211 100644 --- a/packages/coding-agent/src/modes/interactive/components/oauth-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/oauth-selector.ts @@ -1,5 +1,14 @@ import { getOAuthProviders, type OAuthProviderInfo } from "@mariozechner/pi-ai"; -import { Container, isArrowDown, isArrowUp, isEnter, isEscape, Spacer, TruncatedText } from "@mariozechner/pi-tui"; +import { + Container, + isArrowDown, + isArrowUp, + isCtrlC, + isEnter, + isEscape, + Spacer, + TruncatedText, +} from "@mariozechner/pi-tui"; import type { AuthStorage } from "../../../core/auth-storage.js"; import { theme } from "../theme/theme.js"; import { DynamicBorder } from "./dynamic-border.js"; @@ -112,8 +121,8 @@ export class OAuthSelectorComponent extends Container { this.onSelectCallback(selectedProvider.id); } } - // Escape - else if (isEscape(keyData)) { + // Escape or Ctrl+C + else if (isEscape(keyData) || isCtrlC(keyData)) { this.onCancelCallback(); } }