Switch from useInput to useKeypress. (#6056)
This commit is contained in:
parent
74fd0841d0
commit
d219f90132
|
@ -5,11 +5,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DetectedIde, getIdeInfo } from '@google/gemini-cli-core';
|
import { DetectedIde, getIdeInfo } from '@google/gemini-cli-core';
|
||||||
import { Box, Text, useInput } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import {
|
import {
|
||||||
RadioButtonSelect,
|
RadioButtonSelect,
|
||||||
RadioSelectItem,
|
RadioSelectItem,
|
||||||
} from './components/shared/RadioButtonSelect.js';
|
} from './components/shared/RadioButtonSelect.js';
|
||||||
|
import { useKeypress } from './hooks/useKeypress.js';
|
||||||
|
|
||||||
export type IdeIntegrationNudgeResult = {
|
export type IdeIntegrationNudgeResult = {
|
||||||
userSelection: 'yes' | 'no' | 'dismiss';
|
userSelection: 'yes' | 'no' | 'dismiss';
|
||||||
|
@ -25,14 +26,17 @@ export function IdeIntegrationNudge({
|
||||||
ide,
|
ide,
|
||||||
onComplete,
|
onComplete,
|
||||||
}: IdeIntegrationNudgeProps) {
|
}: IdeIntegrationNudgeProps) {
|
||||||
useInput((_input, key) => {
|
useKeypress(
|
||||||
if (key.escape) {
|
(key) => {
|
||||||
|
if (key.name === 'escape') {
|
||||||
onComplete({
|
onComplete({
|
||||||
userSelection: 'no',
|
userSelection: 'no',
|
||||||
isExtensionPreInstalled: false,
|
isExtensionPreInstalled: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
const { displayName: ideName } = getIdeInfo(ide);
|
const { displayName: ideName } = getIdeInfo(ide);
|
||||||
// Assume extension is already installed if the env variables are set.
|
// Assume extension is already installed if the env variables are set.
|
||||||
|
|
|
@ -5,12 +5,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Box, Text, useInput } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { Colors } from '../colors.js';
|
import { Colors } from '../colors.js';
|
||||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||||
import { LoadedSettings, SettingScope } from '../../config/settings.js';
|
import { LoadedSettings, SettingScope } from '../../config/settings.js';
|
||||||
import { AuthType } from '@google/gemini-cli-core';
|
import { AuthType } from '@google/gemini-cli-core';
|
||||||
import { validateAuthMethod } from '../../config/auth.js';
|
import { validateAuthMethod } from '../../config/auth.js';
|
||||||
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
|
||||||
interface AuthDialogProps {
|
interface AuthDialogProps {
|
||||||
onSelect: (authMethod: AuthType | undefined, scope: SettingScope) => void;
|
onSelect: (authMethod: AuthType | undefined, scope: SettingScope) => void;
|
||||||
|
@ -108,8 +109,9 @@ export function AuthDialog({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useInput((_input, key) => {
|
useKeypress(
|
||||||
if (key.escape) {
|
(key) => {
|
||||||
|
if (key.name === 'escape') {
|
||||||
// Prevent exit if there is an error message.
|
// Prevent exit if there is an error message.
|
||||||
// This means they user is not authenticated yet.
|
// This means they user is not authenticated yet.
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
|
@ -124,7 +126,9 @@ export function AuthDialog({
|
||||||
}
|
}
|
||||||
onSelect(undefined, SettingScope.User);
|
onSelect(undefined, SettingScope.User);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Box, Text, useInput } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import Spinner from 'ink-spinner';
|
import Spinner from 'ink-spinner';
|
||||||
import { Colors } from '../colors.js';
|
import { Colors } from '../colors.js';
|
||||||
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
|
||||||
interface AuthInProgressProps {
|
interface AuthInProgressProps {
|
||||||
onTimeout: () => void;
|
onTimeout: () => void;
|
||||||
|
@ -18,11 +19,14 @@ export function AuthInProgress({
|
||||||
}: AuthInProgressProps): React.JSX.Element {
|
}: AuthInProgressProps): React.JSX.Element {
|
||||||
const [timedOut, setTimedOut] = useState(false);
|
const [timedOut, setTimedOut] = useState(false);
|
||||||
|
|
||||||
useInput((input, key) => {
|
useKeypress(
|
||||||
if (key.escape || (key.ctrl && (input === 'c' || input === 'C'))) {
|
(key) => {
|
||||||
|
if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
|
||||||
onTimeout();
|
onTimeout();
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Text, useInput } from 'ink';
|
import { Text } from 'ink';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { Colors } from '../colors.js';
|
import { Colors } from '../colors.js';
|
||||||
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
|
||||||
export const DebugProfiler = () => {
|
export const DebugProfiler = () => {
|
||||||
const numRenders = useRef(0);
|
const numRenders = useRef(0);
|
||||||
|
@ -16,11 +17,14 @@ export const DebugProfiler = () => {
|
||||||
numRenders.current++;
|
numRenders.current++;
|
||||||
});
|
});
|
||||||
|
|
||||||
useInput((input, key) => {
|
useKeypress(
|
||||||
if (key.ctrl && input === 'b') {
|
(key) => {
|
||||||
|
if (key.ctrl && key.name === 'b') {
|
||||||
setShowNumRenders((prev) => !prev);
|
setShowNumRenders((prev) => !prev);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
if (!showNumRenders) {
|
if (!showNumRenders) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Box, Text, useInput } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { Colors } from '../colors.js';
|
import { Colors } from '../colors.js';
|
||||||
import {
|
import {
|
||||||
EDITOR_DISPLAY_NAMES,
|
EDITOR_DISPLAY_NAMES,
|
||||||
|
@ -15,6 +15,7 @@ import {
|
||||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||||
import { LoadedSettings, SettingScope } from '../../config/settings.js';
|
import { LoadedSettings, SettingScope } from '../../config/settings.js';
|
||||||
import { EditorType, isEditorAvailable } from '@google/gemini-cli-core';
|
import { EditorType, isEditorAvailable } from '@google/gemini-cli-core';
|
||||||
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
|
||||||
interface EditorDialogProps {
|
interface EditorDialogProps {
|
||||||
onSelect: (editorType: EditorType | undefined, scope: SettingScope) => void;
|
onSelect: (editorType: EditorType | undefined, scope: SettingScope) => void;
|
||||||
|
@ -33,14 +34,17 @@ export function EditorSettingsDialog({
|
||||||
const [focusedSection, setFocusedSection] = useState<'editor' | 'scope'>(
|
const [focusedSection, setFocusedSection] = useState<'editor' | 'scope'>(
|
||||||
'editor',
|
'editor',
|
||||||
);
|
);
|
||||||
useInput((_, key) => {
|
useKeypress(
|
||||||
if (key.tab) {
|
(key) => {
|
||||||
|
if (key.name === 'tab') {
|
||||||
setFocusedSection((prev) => (prev === 'editor' ? 'scope' : 'editor'));
|
setFocusedSection((prev) => (prev === 'editor' ? 'scope' : 'editor'));
|
||||||
}
|
}
|
||||||
if (key.escape) {
|
if (key.name === 'escape') {
|
||||||
onExit();
|
onExit();
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
const editorItems: EditorDisplay[] =
|
const editorItems: EditorDisplay[] =
|
||||||
editorSettingsManager.getAvailableEditorDisplays();
|
editorSettingsManager.getAvailableEditorDisplays();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { render } from 'ink-testing-library';
|
import { render } from 'ink-testing-library';
|
||||||
|
import { waitFor } from '@testing-library/react';
|
||||||
import { vi } from 'vitest';
|
import { vi } from 'vitest';
|
||||||
import { FolderTrustDialog, FolderTrustChoice } from './FolderTrustDialog.js';
|
import { FolderTrustDialog, FolderTrustChoice } from './FolderTrustDialog.js';
|
||||||
|
|
||||||
|
@ -18,12 +19,14 @@ describe('FolderTrustDialog', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call onSelect with DO_NOT_TRUST when escape is pressed', () => {
|
it('should call onSelect with DO_NOT_TRUST when escape is pressed', async () => {
|
||||||
const onSelect = vi.fn();
|
const onSelect = vi.fn();
|
||||||
const { stdin } = render(<FolderTrustDialog onSelect={onSelect} />);
|
const { stdin } = render(<FolderTrustDialog onSelect={onSelect} />);
|
||||||
|
|
||||||
stdin.write('\u001B'); // Simulate escape key
|
stdin.write('\x1b');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
expect(onSelect).toHaveBeenCalledWith(FolderTrustChoice.DO_NOT_TRUST);
|
expect(onSelect).toHaveBeenCalledWith(FolderTrustChoice.DO_NOT_TRUST);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -4,13 +4,14 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Box, Text, useInput } 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 {
|
import {
|
||||||
RadioButtonSelect,
|
RadioButtonSelect,
|
||||||
RadioSelectItem,
|
RadioSelectItem,
|
||||||
} from './shared/RadioButtonSelect.js';
|
} from './shared/RadioButtonSelect.js';
|
||||||
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
|
||||||
export enum FolderTrustChoice {
|
export enum FolderTrustChoice {
|
||||||
TRUST_FOLDER = 'trust_folder',
|
TRUST_FOLDER = 'trust_folder',
|
||||||
|
@ -25,11 +26,14 @@ interface FolderTrustDialogProps {
|
||||||
export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({
|
export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({
|
||||||
onSelect,
|
onSelect,
|
||||||
}) => {
|
}) => {
|
||||||
useInput((_, key) => {
|
useKeypress(
|
||||||
if (key.escape) {
|
(key) => {
|
||||||
|
if (key.name === 'escape') {
|
||||||
onSelect(FolderTrustChoice.DO_NOT_TRUST);
|
onSelect(FolderTrustChoice.DO_NOT_TRUST);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
const options: Array<RadioSelectItem<FolderTrustChoice>> = [
|
const options: Array<RadioSelectItem<FolderTrustChoice>> = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Box, Text, useInput } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { Colors } from '../colors.js';
|
import { Colors } from '../colors.js';
|
||||||
import {
|
import {
|
||||||
LoadedSettings,
|
LoadedSettings,
|
||||||
|
@ -31,6 +31,7 @@ import {
|
||||||
getDefaultValue,
|
getDefaultValue,
|
||||||
} from '../../utils/settingsUtils.js';
|
} from '../../utils/settingsUtils.js';
|
||||||
import { useVimMode } from '../contexts/VimModeContext.js';
|
import { useVimMode } from '../contexts/VimModeContext.js';
|
||||||
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
|
||||||
interface SettingsDialogProps {
|
interface SettingsDialogProps {
|
||||||
settings: LoadedSettings;
|
settings: LoadedSettings;
|
||||||
|
@ -256,12 +257,14 @@ export function SettingsDialog({
|
||||||
const showScrollUp = true;
|
const showScrollUp = true;
|
||||||
const showScrollDown = true;
|
const showScrollDown = true;
|
||||||
|
|
||||||
useInput((input, key) => {
|
useKeypress(
|
||||||
if (key.tab) {
|
(key) => {
|
||||||
|
const { name, ctrl } = key;
|
||||||
|
if (name === 'tab') {
|
||||||
setFocusSection((prev) => (prev === 'settings' ? 'scope' : 'settings'));
|
setFocusSection((prev) => (prev === 'settings' ? 'scope' : 'settings'));
|
||||||
}
|
}
|
||||||
if (focusSection === 'settings') {
|
if (focusSection === 'settings') {
|
||||||
if (key.upArrow || input === 'k') {
|
if (name === 'up' || name === 'k') {
|
||||||
const newIndex =
|
const newIndex =
|
||||||
activeSettingIndex > 0 ? activeSettingIndex - 1 : items.length - 1;
|
activeSettingIndex > 0 ? activeSettingIndex - 1 : items.length - 1;
|
||||||
setActiveSettingIndex(newIndex);
|
setActiveSettingIndex(newIndex);
|
||||||
|
@ -271,7 +274,7 @@ export function SettingsDialog({
|
||||||
} else if (newIndex < scrollOffset) {
|
} else if (newIndex < scrollOffset) {
|
||||||
setScrollOffset(newIndex);
|
setScrollOffset(newIndex);
|
||||||
}
|
}
|
||||||
} else if (key.downArrow || input === 'j') {
|
} else if (name === 'down' || name === 'j') {
|
||||||
const newIndex =
|
const newIndex =
|
||||||
activeSettingIndex < items.length - 1 ? activeSettingIndex + 1 : 0;
|
activeSettingIndex < items.length - 1 ? activeSettingIndex + 1 : 0;
|
||||||
setActiveSettingIndex(newIndex);
|
setActiveSettingIndex(newIndex);
|
||||||
|
@ -281,9 +284,9 @@ export function SettingsDialog({
|
||||||
} else if (newIndex >= scrollOffset + maxItemsToShow) {
|
} else if (newIndex >= scrollOffset + maxItemsToShow) {
|
||||||
setScrollOffset(newIndex - maxItemsToShow + 1);
|
setScrollOffset(newIndex - maxItemsToShow + 1);
|
||||||
}
|
}
|
||||||
} else if (key.return || input === ' ') {
|
} else if (name === 'return' || name === 'space') {
|
||||||
items[activeSettingIndex]?.toggle();
|
items[activeSettingIndex]?.toggle();
|
||||||
} else if ((key.ctrl && input === 'c') || (key.ctrl && input === 'l')) {
|
} else if (ctrl && (name === 'c' || name === 'l')) {
|
||||||
// Ctrl+C or Ctrl+L: Clear current setting and reset to default
|
// Ctrl+C or Ctrl+L: Clear current setting and reset to default
|
||||||
const currentSetting = items[activeSettingIndex];
|
const currentSetting = items[activeSettingIndex];
|
||||||
if (currentSetting) {
|
if (currentSetting) {
|
||||||
|
@ -334,7 +337,7 @@ export function SettingsDialog({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (showRestartPrompt && input === 'r') {
|
if (showRestartPrompt && name === 'r') {
|
||||||
// Only save settings that require restart (non-restart settings were already saved immediately)
|
// Only save settings that require restart (non-restart settings were already saved immediately)
|
||||||
const restartRequiredSettings =
|
const restartRequiredSettings =
|
||||||
getRestartRequiredFromModified(modifiedSettings);
|
getRestartRequiredFromModified(modifiedSettings);
|
||||||
|
@ -353,10 +356,12 @@ export function SettingsDialog({
|
||||||
setRestartRequiredSettings(new Set()); // Clear restart-required settings
|
setRestartRequiredSettings(new Set()); // Clear restart-required settings
|
||||||
if (onRestartRequest) onRestartRequest();
|
if (onRestartRequest) onRestartRequest();
|
||||||
}
|
}
|
||||||
if (key.escape) {
|
if (name === 'escape') {
|
||||||
onSelect(undefined, selectedScope);
|
onSelect(undefined, selectedScope);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
|
|
@ -5,13 +5,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ToolConfirmationOutcome } from '@google/gemini-cli-core';
|
import { ToolConfirmationOutcome } from '@google/gemini-cli-core';
|
||||||
import { Box, Text, useInput } 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 {
|
import {
|
||||||
RadioButtonSelect,
|
RadioButtonSelect,
|
||||||
RadioSelectItem,
|
RadioSelectItem,
|
||||||
} from './shared/RadioButtonSelect.js';
|
} from './shared/RadioButtonSelect.js';
|
||||||
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
|
||||||
export interface ShellConfirmationRequest {
|
export interface ShellConfirmationRequest {
|
||||||
commands: string[];
|
commands: string[];
|
||||||
|
@ -30,11 +31,14 @@ export const ShellConfirmationDialog: React.FC<
|
||||||
> = ({ request }) => {
|
> = ({ request }) => {
|
||||||
const { commands, onConfirm } = request;
|
const { commands, onConfirm } = request;
|
||||||
|
|
||||||
useInput((_, key) => {
|
useKeypress(
|
||||||
if (key.escape) {
|
(key) => {
|
||||||
|
if (key.name === 'escape') {
|
||||||
onConfirm(ToolConfirmationOutcome.Cancel);
|
onConfirm(ToolConfirmationOutcome.Cancel);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
const handleSelect = (item: ToolConfirmationOutcome) => {
|
const handleSelect = (item: ToolConfirmationOutcome) => {
|
||||||
if (item === ToolConfirmationOutcome.Cancel) {
|
if (item === ToolConfirmationOutcome.Cancel) {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { Box, Text, useInput } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { Colors } from '../colors.js';
|
import { Colors } from '../colors.js';
|
||||||
import { themeManager, DEFAULT_THEME } from '../themes/theme-manager.js';
|
import { themeManager, DEFAULT_THEME } from '../themes/theme-manager.js';
|
||||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||||
|
@ -16,6 +16,7 @@ import {
|
||||||
getScopeItems,
|
getScopeItems,
|
||||||
getScopeMessageForSetting,
|
getScopeMessageForSetting,
|
||||||
} from '../../utils/dialogScopeUtils.js';
|
} from '../../utils/dialogScopeUtils.js';
|
||||||
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
|
||||||
interface ThemeDialogProps {
|
interface ThemeDialogProps {
|
||||||
/** Callback function when a theme is selected */
|
/** Callback function when a theme is selected */
|
||||||
|
@ -111,14 +112,17 @@ export function ThemeDialog({
|
||||||
'theme',
|
'theme',
|
||||||
);
|
);
|
||||||
|
|
||||||
useInput((input, key) => {
|
useKeypress(
|
||||||
if (key.tab) {
|
(key) => {
|
||||||
|
if (key.name === 'tab') {
|
||||||
setFocusedSection((prev) => (prev === 'theme' ? 'scope' : 'theme'));
|
setFocusedSection((prev) => (prev === 'theme' ? 'scope' : 'theme'));
|
||||||
}
|
}
|
||||||
if (key.escape) {
|
if (key.name === 'escape') {
|
||||||
onSelect(undefined, selectedScope);
|
onSelect(undefined, selectedScope);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
// Generate scope message for theme setting
|
// Generate scope message for theme setting
|
||||||
const otherScopeModifiedMessage = getScopeMessageForSetting(
|
const otherScopeModifiedMessage = getScopeMessageForSetting(
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Text, useInput } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { DiffRenderer } from './DiffRenderer.js';
|
import { DiffRenderer } from './DiffRenderer.js';
|
||||||
import { Colors } from '../../colors.js';
|
import { Colors } from '../../colors.js';
|
||||||
import {
|
import {
|
||||||
|
@ -20,6 +20,7 @@ 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;
|
||||||
|
@ -56,12 +57,15 @@ export const ToolConfirmationMessage: React.FC<
|
||||||
onConfirm(outcome);
|
onConfirm(outcome);
|
||||||
};
|
};
|
||||||
|
|
||||||
useInput((input, key) => {
|
useKeypress(
|
||||||
|
(key) => {
|
||||||
if (!isFocused) return;
|
if (!isFocused) return;
|
||||||
if (key.escape || (key.ctrl && (input === 'c' || input === 'C'))) {
|
if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
|
||||||
handleConfirm(ToolConfirmationOutcome.Cancel);
|
handleConfirm(ToolConfirmationOutcome.Cancel);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: isFocused },
|
||||||
|
);
|
||||||
|
|
||||||
const handleSelect = (item: ToolConfirmationOutcome) => handleConfirm(item);
|
const handleSelect = (item: ToolConfirmationOutcome) => handleConfirm(item);
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import { Text, Box, useInput } from 'ink';
|
import { Text, Box } from 'ink';
|
||||||
import { Colors } from '../../colors.js';
|
import { Colors } from '../../colors.js';
|
||||||
|
import { useKeypress } from '../../hooks/useKeypress.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a single option for the RadioButtonSelect.
|
* Represents a single option for the RadioButtonSelect.
|
||||||
|
@ -85,9 +86,10 @@ export function RadioButtonSelect<T>({
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
useInput(
|
useKeypress(
|
||||||
(input, key) => {
|
(key) => {
|
||||||
const isNumeric = showNumbers && /^[0-9]$/.test(input);
|
const { sequence, name } = key;
|
||||||
|
const isNumeric = showNumbers && /^[0-9]$/.test(sequence);
|
||||||
|
|
||||||
// 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) {
|
||||||
|
@ -95,21 +97,21 @@ export function RadioButtonSelect<T>({
|
||||||
setNumberInput('');
|
setNumberInput('');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input === 'k' || key.upArrow) {
|
if (name === 'k' || name === 'up') {
|
||||||
const newIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1;
|
const newIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1;
|
||||||
setActiveIndex(newIndex);
|
setActiveIndex(newIndex);
|
||||||
onHighlight?.(items[newIndex]!.value);
|
onHighlight?.(items[newIndex]!.value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input === 'j' || key.downArrow) {
|
if (name === 'j' || name === 'down') {
|
||||||
const newIndex = activeIndex < items.length - 1 ? activeIndex + 1 : 0;
|
const newIndex = activeIndex < items.length - 1 ? activeIndex + 1 : 0;
|
||||||
setActiveIndex(newIndex);
|
setActiveIndex(newIndex);
|
||||||
onHighlight?.(items[newIndex]!.value);
|
onHighlight?.(items[newIndex]!.value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.return) {
|
if (name === 'return') {
|
||||||
onSelect(items[activeIndex]!.value);
|
onSelect(items[activeIndex]!.value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -120,7 +122,7 @@ export function RadioButtonSelect<T>({
|
||||||
clearTimeout(numberInputTimer.current);
|
clearTimeout(numberInputTimer.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newNumberInput = numberInput + input;
|
const newNumberInput = numberInput + sequence;
|
||||||
setNumberInput(newNumberInput);
|
setNumberInput(newNumberInput);
|
||||||
|
|
||||||
const targetIndex = Number.parseInt(newNumberInput, 10) - 1;
|
const targetIndex = Number.parseInt(newNumberInput, 10) - 1;
|
||||||
|
@ -154,7 +156,7 @@ export function RadioButtonSelect<T>({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ isActive: isFocused && items.length > 0 },
|
{ isActive: !!(isFocused && items.length > 0) },
|
||||||
);
|
);
|
||||||
|
|
||||||
const visibleItems = items.slice(scrollOffset, scrollOffset + maxItemsToShow);
|
const visibleItems = items.slice(scrollOffset, scrollOffset + maxItemsToShow);
|
||||||
|
|
|
@ -21,9 +21,9 @@ import {
|
||||||
Config as ActualConfigType,
|
Config as ActualConfigType,
|
||||||
ApprovalMode,
|
ApprovalMode,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import { useInput, type Key as InkKey } from 'ink';
|
import { useKeypress, Key } from './useKeypress.js';
|
||||||
|
|
||||||
vi.mock('ink');
|
vi.mock('./useKeypress.js');
|
||||||
|
|
||||||
vi.mock('@google/gemini-cli-core', async () => {
|
vi.mock('@google/gemini-cli-core', async () => {
|
||||||
const actualServerModule = (await vi.importActual(
|
const actualServerModule = (await vi.importActual(
|
||||||
|
@ -53,13 +53,12 @@ interface MockConfigInstanceShape {
|
||||||
getToolRegistry: Mock<() => { discoverTools: Mock<() => void> }>;
|
getToolRegistry: Mock<() => { discoverTools: Mock<() => void> }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type UseInputKey = InkKey;
|
type UseKeypressHandler = (key: Key) => void;
|
||||||
type UseInputHandler = (input: string, key: UseInputKey) => void;
|
|
||||||
|
|
||||||
describe('useAutoAcceptIndicator', () => {
|
describe('useAutoAcceptIndicator', () => {
|
||||||
let mockConfigInstance: MockConfigInstanceShape;
|
let mockConfigInstance: MockConfigInstanceShape;
|
||||||
let capturedUseInputHandler: UseInputHandler;
|
let capturedUseKeypressHandler: UseKeypressHandler;
|
||||||
let mockedInkUseInput: MockedFunction<typeof useInput>;
|
let mockedUseKeypress: MockedFunction<typeof useKeypress>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
|
@ -111,10 +110,12 @@ describe('useAutoAcceptIndicator', () => {
|
||||||
return instance;
|
return instance;
|
||||||
});
|
});
|
||||||
|
|
||||||
mockedInkUseInput = useInput as MockedFunction<typeof useInput>;
|
mockedUseKeypress = useKeypress as MockedFunction<typeof useKeypress>;
|
||||||
mockedInkUseInput.mockImplementation((handler: UseInputHandler) => {
|
mockedUseKeypress.mockImplementation(
|
||||||
capturedUseInputHandler = handler;
|
(handler: UseKeypressHandler, _options) => {
|
||||||
});
|
capturedUseKeypressHandler = handler;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
mockConfigInstance = new (Config as any)() as MockConfigInstanceShape;
|
mockConfigInstance = new (Config as any)() as MockConfigInstanceShape;
|
||||||
|
@ -163,7 +164,10 @@ describe('useAutoAcceptIndicator', () => {
|
||||||
expect(result.current).toBe(ApprovalMode.DEFAULT);
|
expect(result.current).toBe(ApprovalMode.DEFAULT);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('', { tab: true, shift: true } as InkKey);
|
capturedUseKeypressHandler({
|
||||||
|
name: 'tab',
|
||||||
|
shift: true,
|
||||||
|
} as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
|
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
|
||||||
ApprovalMode.AUTO_EDIT,
|
ApprovalMode.AUTO_EDIT,
|
||||||
|
@ -171,7 +175,7 @@ describe('useAutoAcceptIndicator', () => {
|
||||||
expect(result.current).toBe(ApprovalMode.AUTO_EDIT);
|
expect(result.current).toBe(ApprovalMode.AUTO_EDIT);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('y', { ctrl: true } as InkKey);
|
capturedUseKeypressHandler({ name: 'y', ctrl: true } as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
|
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
|
||||||
ApprovalMode.YOLO,
|
ApprovalMode.YOLO,
|
||||||
|
@ -179,7 +183,7 @@ describe('useAutoAcceptIndicator', () => {
|
||||||
expect(result.current).toBe(ApprovalMode.YOLO);
|
expect(result.current).toBe(ApprovalMode.YOLO);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('y', { ctrl: true } as InkKey);
|
capturedUseKeypressHandler({ name: 'y', ctrl: true } as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
|
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
|
||||||
ApprovalMode.DEFAULT,
|
ApprovalMode.DEFAULT,
|
||||||
|
@ -187,7 +191,7 @@ describe('useAutoAcceptIndicator', () => {
|
||||||
expect(result.current).toBe(ApprovalMode.DEFAULT);
|
expect(result.current).toBe(ApprovalMode.DEFAULT);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('y', { ctrl: true } as InkKey);
|
capturedUseKeypressHandler({ name: 'y', ctrl: true } as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
|
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
|
||||||
ApprovalMode.YOLO,
|
ApprovalMode.YOLO,
|
||||||
|
@ -195,7 +199,10 @@ describe('useAutoAcceptIndicator', () => {
|
||||||
expect(result.current).toBe(ApprovalMode.YOLO);
|
expect(result.current).toBe(ApprovalMode.YOLO);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('', { tab: true, shift: true } as InkKey);
|
capturedUseKeypressHandler({
|
||||||
|
name: 'tab',
|
||||||
|
shift: true,
|
||||||
|
} as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
|
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
|
||||||
ApprovalMode.AUTO_EDIT,
|
ApprovalMode.AUTO_EDIT,
|
||||||
|
@ -203,7 +210,10 @@ describe('useAutoAcceptIndicator', () => {
|
||||||
expect(result.current).toBe(ApprovalMode.AUTO_EDIT);
|
expect(result.current).toBe(ApprovalMode.AUTO_EDIT);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('', { tab: true, shift: true } as InkKey);
|
capturedUseKeypressHandler({
|
||||||
|
name: 'tab',
|
||||||
|
shift: true,
|
||||||
|
} as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
|
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
|
||||||
ApprovalMode.DEFAULT,
|
ApprovalMode.DEFAULT,
|
||||||
|
@ -220,37 +230,51 @@ describe('useAutoAcceptIndicator', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('', { tab: true, shift: false } as InkKey);
|
capturedUseKeypressHandler({
|
||||||
|
name: 'tab',
|
||||||
|
shift: false,
|
||||||
|
} as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('', { tab: false, shift: true } as InkKey);
|
capturedUseKeypressHandler({
|
||||||
|
name: 'unknown',
|
||||||
|
shift: true,
|
||||||
|
} as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('a', { tab: false, shift: false } as InkKey);
|
capturedUseKeypressHandler({
|
||||||
|
name: 'a',
|
||||||
|
shift: false,
|
||||||
|
ctrl: false,
|
||||||
|
} as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('y', { tab: true } as InkKey);
|
capturedUseKeypressHandler({ name: 'y', ctrl: false } as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('a', { ctrl: true } as InkKey);
|
capturedUseKeypressHandler({ name: 'a', ctrl: true } as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('y', { shift: true } as InkKey);
|
capturedUseKeypressHandler({ name: 'y', shift: true } as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
capturedUseInputHandler('a', { ctrl: true, shift: true } as InkKey);
|
capturedUseKeypressHandler({
|
||||||
|
name: 'a',
|
||||||
|
ctrl: true,
|
||||||
|
shift: true,
|
||||||
|
} as Key);
|
||||||
});
|
});
|
||||||
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useInput } from 'ink';
|
|
||||||
import { ApprovalMode, type Config } from '@google/gemini-cli-core';
|
import { ApprovalMode, type Config } from '@google/gemini-cli-core';
|
||||||
|
import { useKeypress } from './useKeypress.js';
|
||||||
|
|
||||||
export interface UseAutoAcceptIndicatorArgs {
|
export interface UseAutoAcceptIndicatorArgs {
|
||||||
config: Config;
|
config: Config;
|
||||||
|
@ -23,15 +23,16 @@ export function useAutoAcceptIndicator({
|
||||||
setShowAutoAcceptIndicator(currentConfigValue);
|
setShowAutoAcceptIndicator(currentConfigValue);
|
||||||
}, [currentConfigValue]);
|
}, [currentConfigValue]);
|
||||||
|
|
||||||
useInput((input, key) => {
|
useKeypress(
|
||||||
|
(key) => {
|
||||||
let nextApprovalMode: ApprovalMode | undefined;
|
let nextApprovalMode: ApprovalMode | undefined;
|
||||||
|
|
||||||
if (key.ctrl && input === 'y') {
|
if (key.ctrl && key.name === 'y') {
|
||||||
nextApprovalMode =
|
nextApprovalMode =
|
||||||
config.getApprovalMode() === ApprovalMode.YOLO
|
config.getApprovalMode() === ApprovalMode.YOLO
|
||||||
? ApprovalMode.DEFAULT
|
? ApprovalMode.DEFAULT
|
||||||
: ApprovalMode.YOLO;
|
: ApprovalMode.YOLO;
|
||||||
} else if (key.tab && key.shift) {
|
} else if (key.shift && key.name === 'tab') {
|
||||||
nextApprovalMode =
|
nextApprovalMode =
|
||||||
config.getApprovalMode() === ApprovalMode.AUTO_EDIT
|
config.getApprovalMode() === ApprovalMode.AUTO_EDIT
|
||||||
? ApprovalMode.DEFAULT
|
? ApprovalMode.DEFAULT
|
||||||
|
@ -43,7 +44,9 @@ export function useAutoAcceptIndicator({
|
||||||
// Update local state immediately for responsiveness
|
// Update local state immediately for responsiveness
|
||||||
setShowAutoAcceptIndicator(nextApprovalMode);
|
setShowAutoAcceptIndicator(nextApprovalMode);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
return showAutoAcceptIndicator;
|
return showAutoAcceptIndicator;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
||||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||||
import { useGeminiStream, mergePartListUnions } from './useGeminiStream.js';
|
import { useGeminiStream, mergePartListUnions } from './useGeminiStream.js';
|
||||||
import { useInput } from 'ink';
|
import { useKeypress } from './useKeypress.js';
|
||||||
import {
|
import {
|
||||||
useReactToolScheduler,
|
useReactToolScheduler,
|
||||||
TrackedToolCall,
|
TrackedToolCall,
|
||||||
|
@ -71,10 +71,9 @@ vi.mock('./useReactToolScheduler.js', async (importOriginal) => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('ink', async (importOriginal) => {
|
vi.mock('./useKeypress.js', () => ({
|
||||||
const actualInkModule = (await importOriginal()) as any;
|
useKeypress: vi.fn(),
|
||||||
return { ...(actualInkModule || {}), useInput: vi.fn() };
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
vi.mock('./shellCommandProcessor.js', () => ({
|
vi.mock('./shellCommandProcessor.js', () => ({
|
||||||
useShellCommandProcessor: vi.fn().mockReturnValue({
|
useShellCommandProcessor: vi.fn().mockReturnValue({
|
||||||
|
@ -899,19 +898,23 @@ describe('useGeminiStream', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('User Cancellation', () => {
|
describe('User Cancellation', () => {
|
||||||
let useInputCallback: (input: string, key: any) => void;
|
let keypressCallback: (key: any) => void;
|
||||||
const mockUseInput = useInput as Mock;
|
const mockUseKeypress = useKeypress as Mock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Capture the callback passed to useInput
|
// Capture the callback passed to useKeypress
|
||||||
mockUseInput.mockImplementation((callback) => {
|
mockUseKeypress.mockImplementation((callback, options) => {
|
||||||
useInputCallback = callback;
|
if (options.isActive) {
|
||||||
|
keypressCallback = callback;
|
||||||
|
} else {
|
||||||
|
keypressCallback = () => {};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const simulateEscapeKeyPress = () => {
|
const simulateEscapeKeyPress = () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
useInputCallback('', { escape: true });
|
keypressCallback({ name: 'escape' });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useInput } from 'ink';
|
|
||||||
import {
|
import {
|
||||||
Config,
|
Config,
|
||||||
GeminiClient,
|
GeminiClient,
|
||||||
|
@ -55,6 +54,7 @@ import {
|
||||||
TrackedCancelledToolCall,
|
TrackedCancelledToolCall,
|
||||||
} from './useReactToolScheduler.js';
|
} from './useReactToolScheduler.js';
|
||||||
import { useSessionStats } from '../contexts/SessionContext.js';
|
import { useSessionStats } from '../contexts/SessionContext.js';
|
||||||
|
import { useKeypress } from './useKeypress.js';
|
||||||
|
|
||||||
export function mergePartListUnions(list: PartListUnion[]): PartListUnion {
|
export function mergePartListUnions(list: PartListUnion[]): PartListUnion {
|
||||||
const resultParts: PartListUnion = [];
|
const resultParts: PartListUnion = [];
|
||||||
|
@ -213,11 +213,14 @@ export const useGeminiStream = (
|
||||||
pendingHistoryItemRef,
|
pendingHistoryItemRef,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useInput((_input, key) => {
|
useKeypress(
|
||||||
if (key.escape) {
|
(key) => {
|
||||||
|
if (key.name === 'escape') {
|
||||||
cancelOngoingRequest();
|
cancelOngoingRequest();
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: streamingState === StreamingState.Responding },
|
||||||
|
);
|
||||||
|
|
||||||
const prepareQueryForGemini = useCallback(
|
const prepareQueryForGemini = useCallback(
|
||||||
async (
|
async (
|
||||||
|
|
|
@ -4,12 +4,13 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Box, Newline, Text, useInput } from 'ink';
|
import { Box, Newline, Text } from 'ink';
|
||||||
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
|
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
|
||||||
import { usePrivacySettings } from '../hooks/usePrivacySettings.js';
|
import { usePrivacySettings } from '../hooks/usePrivacySettings.js';
|
||||||
import { CloudPaidPrivacyNotice } from './CloudPaidPrivacyNotice.js';
|
import { CloudPaidPrivacyNotice } from './CloudPaidPrivacyNotice.js';
|
||||||
import { Config } from '@google/gemini-cli-core';
|
import { Config } from '@google/gemini-cli-core';
|
||||||
import { Colors } from '../colors.js';
|
import { Colors } from '../colors.js';
|
||||||
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
|
||||||
interface CloudFreePrivacyNoticeProps {
|
interface CloudFreePrivacyNoticeProps {
|
||||||
config: Config;
|
config: Config;
|
||||||
|
@ -23,11 +24,14 @@ export const CloudFreePrivacyNotice = ({
|
||||||
const { privacyState, updateDataCollectionOptIn } =
|
const { privacyState, updateDataCollectionOptIn } =
|
||||||
usePrivacySettings(config);
|
usePrivacySettings(config);
|
||||||
|
|
||||||
useInput((input, key) => {
|
useKeypress(
|
||||||
if (privacyState.error && key.escape) {
|
(key) => {
|
||||||
|
if (privacyState.error && key.name === 'escape') {
|
||||||
onExit();
|
onExit();
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
if (privacyState.isLoading) {
|
if (privacyState.isLoading) {
|
||||||
return <Text color={Colors.Gray}>Loading...</Text>;
|
return <Text color={Colors.Gray}>Loading...</Text>;
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Box, Newline, Text, useInput } from 'ink';
|
import { Box, Newline, Text } from 'ink';
|
||||||
import { Colors } from '../colors.js';
|
import { Colors } from '../colors.js';
|
||||||
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
|
||||||
interface CloudPaidPrivacyNoticeProps {
|
interface CloudPaidPrivacyNoticeProps {
|
||||||
onExit: () => void;
|
onExit: () => void;
|
||||||
|
@ -14,11 +15,14 @@ interface CloudPaidPrivacyNoticeProps {
|
||||||
export const CloudPaidPrivacyNotice = ({
|
export const CloudPaidPrivacyNotice = ({
|
||||||
onExit,
|
onExit,
|
||||||
}: CloudPaidPrivacyNoticeProps) => {
|
}: CloudPaidPrivacyNoticeProps) => {
|
||||||
useInput((input, key) => {
|
useKeypress(
|
||||||
if (key.escape) {
|
(key) => {
|
||||||
|
if (key.name === 'escape') {
|
||||||
onExit();
|
onExit();
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" marginBottom={1}>
|
<Box flexDirection="column" marginBottom={1}>
|
||||||
|
|
|
@ -4,19 +4,23 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Box, Newline, Text, useInput } from 'ink';
|
import { Box, Newline, Text } from 'ink';
|
||||||
import { Colors } from '../colors.js';
|
import { Colors } from '../colors.js';
|
||||||
|
import { useKeypress } from '../hooks/useKeypress.js';
|
||||||
|
|
||||||
interface GeminiPrivacyNoticeProps {
|
interface GeminiPrivacyNoticeProps {
|
||||||
onExit: () => void;
|
onExit: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GeminiPrivacyNotice = ({ onExit }: GeminiPrivacyNoticeProps) => {
|
export const GeminiPrivacyNotice = ({ onExit }: GeminiPrivacyNoticeProps) => {
|
||||||
useInput((input, key) => {
|
useKeypress(
|
||||||
if (key.escape) {
|
(key) => {
|
||||||
|
if (key.name === 'escape') {
|
||||||
onExit();
|
onExit();
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{ isActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" marginBottom={1}>
|
<Box flexDirection="column" marginBottom={1}>
|
||||||
|
|
Loading…
Reference in New Issue