mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-20 13:02:15 +00:00
Fix Escape key handling for Kitty keyboard protocol
Add isEscape() helper that handles both raw (\x1b) and Kitty protocol (\x1b[27u) Escape sequences. Update all components that check for Escape key to use the new helper.
This commit is contained in:
parent
314ef34ebc
commit
f8b6164ecd
11 changed files with 38 additions and 18 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { Editor, isCtrlC, isCtrlD, isCtrlO, isCtrlP, isCtrlT, isShiftTab } from "@mariozechner/pi-tui";
|
import { Editor, isCtrlC, isCtrlD, isCtrlO, isCtrlP, isCtrlT, isEscape, isShiftTab } from "@mariozechner/pi-tui";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom editor that handles Escape and Ctrl+C keys for coding-agent
|
* Custom editor that handles Escape and Ctrl+C keys for coding-agent
|
||||||
|
|
@ -39,7 +39,7 @@ export class CustomEditor extends Editor {
|
||||||
|
|
||||||
// Intercept Escape key - but only if autocomplete is NOT active
|
// Intercept Escape key - but only if autocomplete is NOT active
|
||||||
// (let parent handle escape for autocomplete cancellation)
|
// (let parent handle escape for autocomplete cancellation)
|
||||||
if (data === "\x1b" && this.onEscape && !this.isShowingAutocomplete()) {
|
if (isEscape(data) && this.onEscape && !this.isShowingAutocomplete()) {
|
||||||
this.onEscape();
|
this.onEscape();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* Simple text input component for hooks.
|
* Simple text input component for hooks.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Container, Input, Spacer, Text } from "@mariozechner/pi-tui";
|
import { Container, Input, isEscape, Spacer, Text } from "@mariozechner/pi-tui";
|
||||||
import { theme } from "../theme/theme.js";
|
import { theme } from "../theme/theme.js";
|
||||||
import { DynamicBorder } from "./dynamic-border.js";
|
import { DynamicBorder } from "./dynamic-border.js";
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ export class HookInputComponent extends Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape to cancel
|
// Escape to cancel
|
||||||
if (keyData === "\x1b") {
|
if (isEscape(keyData)) {
|
||||||
this.onCancelCallback();
|
this.onCancelCallback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Displays a list of string options with keyboard navigation.
|
* Displays a list of string options with keyboard navigation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Container, Spacer, Text } from "@mariozechner/pi-tui";
|
import { Container, isEscape, Spacer, Text } from "@mariozechner/pi-tui";
|
||||||
import { theme } from "../theme/theme.js";
|
import { theme } from "../theme/theme.js";
|
||||||
import { DynamicBorder } from "./dynamic-border.js";
|
import { DynamicBorder } from "./dynamic-border.js";
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ export class HookSelectorComponent extends Container {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Escape
|
// Escape
|
||||||
else if (keyData === "\x1b") {
|
else if (isEscape(keyData)) {
|
||||||
this.onCancelCallback();
|
this.onCancelCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Model } from "@mariozechner/pi-ai";
|
import type { Model } from "@mariozechner/pi-ai";
|
||||||
import { Container, Input, Spacer, Text, type TUI } from "@mariozechner/pi-tui";
|
import { Container, Input, isEscape, Spacer, Text, type TUI } from "@mariozechner/pi-tui";
|
||||||
import { getAvailableModels } from "../../../core/model-config.js";
|
import { getAvailableModels } from "../../../core/model-config.js";
|
||||||
import type { SettingsManager } from "../../../core/settings-manager.js";
|
import type { SettingsManager } from "../../../core/settings-manager.js";
|
||||||
import { fuzzyFilter } from "../../../utils/fuzzy.js";
|
import { fuzzyFilter } from "../../../utils/fuzzy.js";
|
||||||
|
|
@ -192,7 +192,7 @@ export class ModelSelectorComponent extends Container {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Escape
|
// Escape
|
||||||
else if (keyData === "\x1b") {
|
else if (isEscape(keyData)) {
|
||||||
this.onCancelCallback();
|
this.onCancelCallback();
|
||||||
}
|
}
|
||||||
// Pass everything else to search input
|
// Pass everything else to search input
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Container, Spacer, TruncatedText } from "@mariozechner/pi-tui";
|
import { Container, isEscape, Spacer, TruncatedText } from "@mariozechner/pi-tui";
|
||||||
import { getOAuthProviders, type OAuthProviderInfo } from "../../../core/oauth/index.js";
|
import { getOAuthProviders, type OAuthProviderInfo } from "../../../core/oauth/index.js";
|
||||||
import { loadOAuthCredentials } from "../../../core/oauth/storage.js";
|
import { loadOAuthCredentials } from "../../../core/oauth/storage.js";
|
||||||
import { theme } from "../theme/theme.js";
|
import { theme } from "../theme/theme.js";
|
||||||
|
|
@ -107,7 +107,7 @@ export class OAuthSelectorComponent extends Container {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Escape
|
// Escape
|
||||||
else if (keyData === "\x1b") {
|
else if (isEscape(keyData)) {
|
||||||
this.onCancelCallback();
|
this.onCancelCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
import { type Component, Container, Input, isCtrlC, Spacer, Text, truncateToWidth } from "@mariozechner/pi-tui";
|
import {
|
||||||
|
type Component,
|
||||||
|
Container,
|
||||||
|
Input,
|
||||||
|
isCtrlC,
|
||||||
|
isEscape,
|
||||||
|
Spacer,
|
||||||
|
Text,
|
||||||
|
truncateToWidth,
|
||||||
|
} from "@mariozechner/pi-tui";
|
||||||
import type { SessionManager } from "../../../core/session-manager.js";
|
import type { SessionManager } from "../../../core/session-manager.js";
|
||||||
import { fuzzyFilter } from "../../../utils/fuzzy.js";
|
import { fuzzyFilter } from "../../../utils/fuzzy.js";
|
||||||
import { theme } from "../theme/theme.js";
|
import { theme } from "../theme/theme.js";
|
||||||
|
|
@ -139,7 +148,7 @@ class SessionList implements Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Escape - cancel
|
// Escape - cancel
|
||||||
else if (keyData === "\x1b") {
|
else if (isEscape(keyData)) {
|
||||||
if (this.onCancel) {
|
if (this.onCancel) {
|
||||||
this.onCancel();
|
this.onCancel();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { type Component, Container, isCtrlC, Spacer, Text, truncateToWidth } from "@mariozechner/pi-tui";
|
import { type Component, Container, isCtrlC, isEscape, Spacer, Text, truncateToWidth } from "@mariozechner/pi-tui";
|
||||||
import { theme } from "../theme/theme.js";
|
import { theme } from "../theme/theme.js";
|
||||||
import { DynamicBorder } from "./dynamic-border.js";
|
import { DynamicBorder } from "./dynamic-border.js";
|
||||||
|
|
||||||
|
|
@ -94,7 +94,7 @@ class UserMessageList implements Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Escape - cancel
|
// Escape - cancel
|
||||||
else if (keyData === "\x1b") {
|
else if (isEscape(keyData)) {
|
||||||
if (this.onCancel) {
|
if (this.onCancel) {
|
||||||
this.onCancel();
|
this.onCancel();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { AutocompleteProvider, CombinedAutocompleteProvider } from "../autocomplete.js";
|
import type { AutocompleteProvider, CombinedAutocompleteProvider } from "../autocomplete.js";
|
||||||
import { isAltBackspace, isCtrlA, isCtrlC, isCtrlE, isCtrlK, isCtrlU, isCtrlW, Keys } from "../keys.js";
|
import { isAltBackspace, isCtrlA, isCtrlC, isCtrlE, isCtrlK, isCtrlU, isCtrlW, isEscape, Keys } from "../keys.js";
|
||||||
import type { Component } from "../tui.js";
|
import type { Component } from "../tui.js";
|
||||||
import { visibleWidth } from "../utils.js";
|
import { visibleWidth } from "../utils.js";
|
||||||
import { SelectList, type SelectListTheme } from "./select-list.js";
|
import { SelectList, type SelectListTheme } from "./select-list.js";
|
||||||
|
|
@ -267,7 +267,7 @@ export class Editor implements Component {
|
||||||
// Handle autocomplete special keys first (but don't block other input)
|
// Handle autocomplete special keys first (but don't block other input)
|
||||||
if (this.isAutocompleting && this.autocompleteList) {
|
if (this.isAutocompleting && this.autocompleteList) {
|
||||||
// Escape - cancel autocomplete
|
// Escape - cancel autocomplete
|
||||||
if (data === "\x1b") {
|
if (isEscape(data)) {
|
||||||
this.cancelAutocomplete();
|
this.cancelAutocomplete();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { isCtrlC } from "../keys.js";
|
import { isCtrlC, isEscape } from "../keys.js";
|
||||||
import type { Component } from "../tui.js";
|
import type { Component } from "../tui.js";
|
||||||
import { truncateToWidth } from "../utils.js";
|
import { truncateToWidth } from "../utils.js";
|
||||||
|
|
||||||
|
|
@ -163,7 +163,7 @@ export class SelectList implements Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Escape or Ctrl+C
|
// Escape or Ctrl+C
|
||||||
else if (keyData === "\x1b" || isCtrlC(keyData)) {
|
else if (isEscape(keyData) || isCtrlC(keyData)) {
|
||||||
if (this.onCancel) {
|
if (this.onCancel) {
|
||||||
this.onCancel();
|
this.onCancel();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ export {
|
||||||
isCtrlT,
|
isCtrlT,
|
||||||
isCtrlU,
|
isCtrlU,
|
||||||
isCtrlW,
|
isCtrlW,
|
||||||
|
isEscape,
|
||||||
isShiftTab,
|
isShiftTab,
|
||||||
Keys,
|
Keys,
|
||||||
} from "./keys.js";
|
} from "./keys.js";
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ const CODEPOINTS = {
|
||||||
w: 119,
|
w: 119,
|
||||||
|
|
||||||
// Special keys
|
// Special keys
|
||||||
|
escape: 27,
|
||||||
tab: 9,
|
tab: 9,
|
||||||
enter: 13,
|
enter: 13,
|
||||||
backspace: 127,
|
backspace: 127,
|
||||||
|
|
@ -194,3 +195,12 @@ export function isAltBackspace(data: string): boolean {
|
||||||
export function isShiftTab(data: string): boolean {
|
export function isShiftTab(data: string): boolean {
|
||||||
return data === RAW.SHIFT_TAB || data === Keys.SHIFT_TAB;
|
return data === RAW.SHIFT_TAB || data === Keys.SHIFT_TAB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if input matches the Escape key (raw byte or Kitty protocol).
|
||||||
|
* Raw: \x1b (single byte)
|
||||||
|
* Kitty: \x1b[27u (codepoint 27 = escape)
|
||||||
|
*/
|
||||||
|
export function isEscape(data: string): boolean {
|
||||||
|
return data === "\x1b" || data === `\x1b[${CODEPOINTS.escape}u`;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue