mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-20 08:04:44 +00:00
fix(coding-agent): use key helpers for arrow keys and Enter
Fixed arrow key and Enter detection in selector components to work with Kitty protocol when Caps Lock or Num Lock is enabled. Updated: oauth-selector, user-message-selector, hook-selector, hook-input, model-selector, session-selector
This commit is contained in:
parent
65c292b7dd
commit
8d1229b5ec
6 changed files with 45 additions and 21 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
* Simple text input component for hooks.
|
* Simple text input component for hooks.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Container, Input, isEscape, Spacer, Text } from "@mariozechner/pi-tui";
|
import { Container, Input, isEnter, 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";
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ export class HookInputComponent extends Container {
|
||||||
|
|
||||||
handleInput(keyData: string): void {
|
handleInput(keyData: string): void {
|
||||||
// Enter
|
// Enter
|
||||||
if (keyData === "\r" || keyData === "\n") {
|
if (isEnter(keyData) || keyData === "\n") {
|
||||||
this.onSubmitCallback(this.input.getValue());
|
this.onSubmitCallback(this.input.getValue());
|
||||||
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, isEscape, Spacer, Text } from "@mariozechner/pi-tui";
|
import { Container, isArrowDown, isArrowUp, isEnter, 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";
|
||||||
|
|
||||||
|
|
@ -67,17 +67,17 @@ export class HookSelectorComponent extends Container {
|
||||||
|
|
||||||
handleInput(keyData: string): void {
|
handleInput(keyData: string): void {
|
||||||
// Up arrow or k
|
// Up arrow or k
|
||||||
if (keyData === "\x1b[A" || keyData === "k") {
|
if (isArrowUp(keyData) || keyData === "k") {
|
||||||
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
||||||
this.updateList();
|
this.updateList();
|
||||||
}
|
}
|
||||||
// Down arrow or j
|
// Down arrow or j
|
||||||
else if (keyData === "\x1b[B" || keyData === "j") {
|
else if (isArrowDown(keyData) || keyData === "j") {
|
||||||
this.selectedIndex = Math.min(this.options.length - 1, this.selectedIndex + 1);
|
this.selectedIndex = Math.min(this.options.length - 1, this.selectedIndex + 1);
|
||||||
this.updateList();
|
this.updateList();
|
||||||
}
|
}
|
||||||
// Enter
|
// Enter
|
||||||
else if (keyData === "\r" || keyData === "\n") {
|
else if (isEnter(keyData) || keyData === "\n") {
|
||||||
const selected = this.options[this.selectedIndex];
|
const selected = this.options[this.selectedIndex];
|
||||||
if (selected) {
|
if (selected) {
|
||||||
this.onSelectCallback(selected);
|
this.onSelectCallback(selected);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,15 @@
|
||||||
import type { Model } from "@mariozechner/pi-ai";
|
import type { Model } from "@mariozechner/pi-ai";
|
||||||
import { Container, Input, isEscape, Spacer, Text, type TUI } from "@mariozechner/pi-tui";
|
import {
|
||||||
|
Container,
|
||||||
|
Input,
|
||||||
|
isArrowDown,
|
||||||
|
isArrowUp,
|
||||||
|
isEnter,
|
||||||
|
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";
|
||||||
|
|
@ -175,17 +185,17 @@ export class ModelSelectorComponent extends Container {
|
||||||
|
|
||||||
handleInput(keyData: string): void {
|
handleInput(keyData: string): void {
|
||||||
// Up arrow - wrap to bottom when at top
|
// Up arrow - wrap to bottom when at top
|
||||||
if (keyData === "\x1b[A") {
|
if (isArrowUp(keyData)) {
|
||||||
this.selectedIndex = this.selectedIndex === 0 ? this.filteredModels.length - 1 : this.selectedIndex - 1;
|
this.selectedIndex = this.selectedIndex === 0 ? this.filteredModels.length - 1 : this.selectedIndex - 1;
|
||||||
this.updateList();
|
this.updateList();
|
||||||
}
|
}
|
||||||
// Down arrow - wrap to top when at bottom
|
// Down arrow - wrap to top when at bottom
|
||||||
else if (keyData === "\x1b[B") {
|
else if (isArrowDown(keyData)) {
|
||||||
this.selectedIndex = this.selectedIndex === this.filteredModels.length - 1 ? 0 : this.selectedIndex + 1;
|
this.selectedIndex = this.selectedIndex === this.filteredModels.length - 1 ? 0 : this.selectedIndex + 1;
|
||||||
this.updateList();
|
this.updateList();
|
||||||
}
|
}
|
||||||
// Enter
|
// Enter
|
||||||
else if (keyData === "\r") {
|
else if (isEnter(keyData)) {
|
||||||
const selectedModel = this.filteredModels[this.selectedIndex];
|
const selectedModel = this.filteredModels[this.selectedIndex];
|
||||||
if (selectedModel) {
|
if (selectedModel) {
|
||||||
this.handleSelect(selectedModel.model);
|
this.handleSelect(selectedModel.model);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Container, isEscape, Spacer, TruncatedText } from "@mariozechner/pi-tui";
|
import { Container, isArrowDown, isArrowUp, isEnter, 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";
|
||||||
|
|
@ -90,17 +90,17 @@ export class OAuthSelectorComponent extends Container {
|
||||||
|
|
||||||
handleInput(keyData: string): void {
|
handleInput(keyData: string): void {
|
||||||
// Up arrow
|
// Up arrow
|
||||||
if (keyData === "\x1b[A") {
|
if (isArrowUp(keyData)) {
|
||||||
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
||||||
this.updateList();
|
this.updateList();
|
||||||
}
|
}
|
||||||
// Down arrow
|
// Down arrow
|
||||||
else if (keyData === "\x1b[B") {
|
else if (isArrowDown(keyData)) {
|
||||||
this.selectedIndex = Math.min(this.allProviders.length - 1, this.selectedIndex + 1);
|
this.selectedIndex = Math.min(this.allProviders.length - 1, this.selectedIndex + 1);
|
||||||
this.updateList();
|
this.updateList();
|
||||||
}
|
}
|
||||||
// Enter
|
// Enter
|
||||||
else if (keyData === "\r") {
|
else if (isEnter(keyData)) {
|
||||||
const selectedProvider = this.allProviders[this.selectedIndex];
|
const selectedProvider = this.allProviders[this.selectedIndex];
|
||||||
if (selectedProvider?.available) {
|
if (selectedProvider?.available) {
|
||||||
this.onSelectCallback(selectedProvider.id);
|
this.onSelectCallback(selectedProvider.id);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@ import {
|
||||||
type Component,
|
type Component,
|
||||||
Container,
|
Container,
|
||||||
Input,
|
Input,
|
||||||
|
isArrowDown,
|
||||||
|
isArrowUp,
|
||||||
isCtrlC,
|
isCtrlC,
|
||||||
|
isEnter,
|
||||||
isEscape,
|
isEscape,
|
||||||
Spacer,
|
Spacer,
|
||||||
Text,
|
Text,
|
||||||
|
|
@ -134,15 +137,15 @@ class SessionList implements Component {
|
||||||
|
|
||||||
handleInput(keyData: string): void {
|
handleInput(keyData: string): void {
|
||||||
// Up arrow
|
// Up arrow
|
||||||
if (keyData === "\x1b[A") {
|
if (isArrowUp(keyData)) {
|
||||||
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
||||||
}
|
}
|
||||||
// Down arrow
|
// Down arrow
|
||||||
else if (keyData === "\x1b[B") {
|
else if (isArrowDown(keyData)) {
|
||||||
this.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);
|
this.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);
|
||||||
}
|
}
|
||||||
// Enter
|
// Enter
|
||||||
else if (keyData === "\r") {
|
else if (isEnter(keyData)) {
|
||||||
const selected = this.filteredSessions[this.selectedIndex];
|
const selected = this.filteredSessions[this.selectedIndex];
|
||||||
if (selected && this.onSelect) {
|
if (selected && this.onSelect) {
|
||||||
this.onSelect(selected.path);
|
this.onSelect(selected.path);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,15 @@
|
||||||
import { type Component, Container, isCtrlC, isEscape, Spacer, Text, truncateToWidth } from "@mariozechner/pi-tui";
|
import {
|
||||||
|
type Component,
|
||||||
|
Container,
|
||||||
|
isArrowDown,
|
||||||
|
isArrowUp,
|
||||||
|
isCtrlC,
|
||||||
|
isEnter,
|
||||||
|
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";
|
||||||
|
|
||||||
|
|
@ -79,15 +90,15 @@ class UserMessageList implements Component {
|
||||||
|
|
||||||
handleInput(keyData: string): void {
|
handleInput(keyData: string): void {
|
||||||
// Up arrow - go to previous (older) message, wrap to bottom when at top
|
// Up arrow - go to previous (older) message, wrap to bottom when at top
|
||||||
if (keyData === "\x1b[A") {
|
if (isArrowUp(keyData)) {
|
||||||
this.selectedIndex = this.selectedIndex === 0 ? this.messages.length - 1 : this.selectedIndex - 1;
|
this.selectedIndex = this.selectedIndex === 0 ? this.messages.length - 1 : this.selectedIndex - 1;
|
||||||
}
|
}
|
||||||
// Down arrow - go to next (newer) message, wrap to top when at bottom
|
// Down arrow - go to next (newer) message, wrap to top when at bottom
|
||||||
else if (keyData === "\x1b[B") {
|
else if (isArrowDown(keyData)) {
|
||||||
this.selectedIndex = this.selectedIndex === this.messages.length - 1 ? 0 : this.selectedIndex + 1;
|
this.selectedIndex = this.selectedIndex === this.messages.length - 1 ? 0 : this.selectedIndex + 1;
|
||||||
}
|
}
|
||||||
// Enter - select message and branch
|
// Enter - select message and branch
|
||||||
else if (keyData === "\r") {
|
else if (isEnter(keyData)) {
|
||||||
const selected = this.messages[this.selectedIndex];
|
const selected = this.messages[this.selectedIndex];
|
||||||
if (selected && this.onSelect) {
|
if (selected && this.onSelect) {
|
||||||
this.onSelect(selected.index);
|
this.onSelect(selected.index);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue