Fix CLI interactivity issue by eliminating multiple useKeypress hook conflicts

Co-authored-by: pomelo-nwu <10703060+pomelo-nwu@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-08-26 12:00:12 +00:00
parent b03daebbc1
commit f5ee3df219
3 changed files with 33 additions and 22 deletions

View File

@@ -8,7 +8,6 @@ import { ToolConfirmationOutcome } from '@qwen-code/qwen-code-core';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import React from 'react'; import React from 'react';
import { Colors } from '../colors.js'; import { Colors } from '../colors.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { import {
RadioButtonSelect, RadioButtonSelect,
RadioSelectItem, RadioSelectItem,
@@ -31,15 +30,6 @@ export const ShellConfirmationDialog: React.FC<
> = ({ request }) => { > = ({ request }) => {
const { commands, onConfirm } = request; const { commands, onConfirm } = request;
useKeypress(
(key) => {
if (key.name === 'escape') {
onConfirm(ToolConfirmationOutcome.Cancel);
}
},
{ isActive: true },
);
const handleSelect = (item: ToolConfirmationOutcome) => { const handleSelect = (item: ToolConfirmationOutcome) => {
if (item === ToolConfirmationOutcome.Cancel) { if (item === ToolConfirmationOutcome.Cancel) {
onConfirm(item); onConfirm(item);
@@ -50,6 +40,10 @@ export const ShellConfirmationDialog: React.FC<
} }
}; };
const handleEscape = () => {
onConfirm(ToolConfirmationOutcome.Cancel);
};
const options: Array<RadioSelectItem<ToolConfirmationOutcome>> = [ const options: Array<RadioSelectItem<ToolConfirmationOutcome>> = [
{ {
label: 'Yes, allow once', label: 'Yes, allow once',
@@ -96,7 +90,12 @@ export const ShellConfirmationDialog: React.FC<
<Text>Do you want to proceed?</Text> <Text>Do you want to proceed?</Text>
</Box> </Box>
<RadioButtonSelect items={options} onSelect={handleSelect} isFocused /> <RadioButtonSelect
items={options}
onSelect={handleSelect}
onEscape={handleEscape}
isFocused
/>
</Box> </Box>
); );
}; };

View File

@@ -20,7 +20,6 @@ import {
RadioSelectItem, RadioSelectItem,
} from '../shared/RadioButtonSelect.js'; } from '../shared/RadioButtonSelect.js';
import { MaxSizedBox } from '../shared/MaxSizedBox.js'; import { MaxSizedBox } from '../shared/MaxSizedBox.js';
import { useKeypress } from '../../hooks/useKeypress.js';
export interface ToolConfirmationMessageProps { export interface ToolConfirmationMessageProps {
confirmationDetails: ToolCallConfirmationDetails; confirmationDetails: ToolCallConfirmationDetails;
@@ -57,18 +56,11 @@ export const ToolConfirmationMessage: React.FC<
onConfirm(outcome); onConfirm(outcome);
}; };
useKeypress(
(key) => {
if (!isFocused) return;
if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
handleConfirm(ToolConfirmationOutcome.Cancel);
}
},
{ isActive: isFocused },
);
const handleSelect = (item: ToolConfirmationOutcome) => handleConfirm(item); const handleSelect = (item: ToolConfirmationOutcome) => handleConfirm(item);
const handleEscape = () => handleConfirm(ToolConfirmationOutcome.Cancel);
const handleCancel = () => handleConfirm(ToolConfirmationOutcome.Cancel);
let bodyContent: React.ReactNode | null = null; // Removed contextDisplay here let bodyContent: React.ReactNode | null = null; // Removed contextDisplay here
let question: string; let question: string;
@@ -283,6 +275,8 @@ export const ToolConfirmationMessage: React.FC<
<RadioButtonSelect <RadioButtonSelect
items={options} items={options}
onSelect={handleSelect} onSelect={handleSelect}
onEscape={handleEscape}
onCancel={handleCancel}
isFocused={isFocused} isFocused={isFocused}
/> />
</Box> </Box>

View File

@@ -34,6 +34,10 @@ export interface RadioButtonSelectProps<T> {
onSelect: (value: T) => void; onSelect: (value: T) => void;
/** Function called when an item is highlighted. Receives the `value` of the selected item. */ /** Function called when an item is highlighted. Receives the `value` of the selected item. */
onHighlight?: (value: T) => void; onHighlight?: (value: T) => void;
/** Function called when escape key is pressed. */
onEscape?: () => void;
/** Function called when Ctrl+C is pressed. */
onCancel?: () => void;
/** Whether this select input is currently focused and should respond to input. */ /** Whether this select input is currently focused and should respond to input. */
isFocused?: boolean; isFocused?: boolean;
/** Whether to show the scroll arrows. */ /** Whether to show the scroll arrows. */
@@ -55,6 +59,8 @@ export function RadioButtonSelect<T>({
initialIndex = 0, initialIndex = 0,
onSelect, onSelect,
onHighlight, onHighlight,
onEscape,
onCancel,
isFocused, isFocused,
showScrollArrows = false, showScrollArrows = false,
maxItemsToShow = 10, maxItemsToShow = 10,
@@ -91,6 +97,18 @@ export function RadioButtonSelect<T>({
const { sequence, name } = key; const { sequence, name } = key;
const isNumeric = showNumbers && /^[0-9]$/.test(sequence); const isNumeric = showNumbers && /^[0-9]$/.test(sequence);
// Handle escape key
if (name === 'escape') {
onEscape?.();
return;
}
// Handle Ctrl+C
if (key.ctrl && name === 'c') {
onCancel?.();
return;
}
// Any key press that is not a digit should clear the number input buffer. // Any key press that is not a digit should clear the number input buffer.
if (!isNumeric && numberInputTimer.current) { if (!isNumeric && numberInputTimer.current) {
clearTimeout(numberInputTimer.current); clearTimeout(numberInputTimer.current);