summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/hooks')
-rw-r--r--packages/cli/src/ui/hooks/useAutoAcceptIndicator.test.ts70
-rw-r--r--packages/cli/src/ui/hooks/useAutoAcceptIndicator.ts43
-rw-r--r--packages/cli/src/ui/hooks/useGeminiStream.test.tsx25
-rw-r--r--packages/cli/src/ui/hooks/useGeminiStream.ts15
4 files changed, 93 insertions, 60 deletions
diff --git a/packages/cli/src/ui/hooks/useAutoAcceptIndicator.test.ts b/packages/cli/src/ui/hooks/useAutoAcceptIndicator.test.ts
index bda6c259..657d792b 100644
--- a/packages/cli/src/ui/hooks/useAutoAcceptIndicator.test.ts
+++ b/packages/cli/src/ui/hooks/useAutoAcceptIndicator.test.ts
@@ -21,9 +21,9 @@ import {
Config as ActualConfigType,
ApprovalMode,
} 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 () => {
const actualServerModule = (await vi.importActual(
@@ -53,13 +53,12 @@ interface MockConfigInstanceShape {
getToolRegistry: Mock<() => { discoverTools: Mock<() => void> }>;
}
-type UseInputKey = InkKey;
-type UseInputHandler = (input: string, key: UseInputKey) => void;
+type UseKeypressHandler = (key: Key) => void;
describe('useAutoAcceptIndicator', () => {
let mockConfigInstance: MockConfigInstanceShape;
- let capturedUseInputHandler: UseInputHandler;
- let mockedInkUseInput: MockedFunction<typeof useInput>;
+ let capturedUseKeypressHandler: UseKeypressHandler;
+ let mockedUseKeypress: MockedFunction<typeof useKeypress>;
beforeEach(() => {
vi.resetAllMocks();
@@ -111,10 +110,12 @@ describe('useAutoAcceptIndicator', () => {
return instance;
});
- mockedInkUseInput = useInput as MockedFunction<typeof useInput>;
- mockedInkUseInput.mockImplementation((handler: UseInputHandler) => {
- capturedUseInputHandler = handler;
- });
+ mockedUseKeypress = useKeypress as MockedFunction<typeof useKeypress>;
+ mockedUseKeypress.mockImplementation(
+ (handler: UseKeypressHandler, _options) => {
+ capturedUseKeypressHandler = handler;
+ },
+ );
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mockConfigInstance = new (Config as any)() as MockConfigInstanceShape;
@@ -163,7 +164,10 @@ describe('useAutoAcceptIndicator', () => {
expect(result.current).toBe(ApprovalMode.DEFAULT);
act(() => {
- capturedUseInputHandler('', { tab: true, shift: true } as InkKey);
+ capturedUseKeypressHandler({
+ name: 'tab',
+ shift: true,
+ } as Key);
});
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
ApprovalMode.AUTO_EDIT,
@@ -171,7 +175,7 @@ describe('useAutoAcceptIndicator', () => {
expect(result.current).toBe(ApprovalMode.AUTO_EDIT);
act(() => {
- capturedUseInputHandler('y', { ctrl: true } as InkKey);
+ capturedUseKeypressHandler({ name: 'y', ctrl: true } as Key);
});
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
ApprovalMode.YOLO,
@@ -179,7 +183,7 @@ describe('useAutoAcceptIndicator', () => {
expect(result.current).toBe(ApprovalMode.YOLO);
act(() => {
- capturedUseInputHandler('y', { ctrl: true } as InkKey);
+ capturedUseKeypressHandler({ name: 'y', ctrl: true } as Key);
});
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
ApprovalMode.DEFAULT,
@@ -187,7 +191,7 @@ describe('useAutoAcceptIndicator', () => {
expect(result.current).toBe(ApprovalMode.DEFAULT);
act(() => {
- capturedUseInputHandler('y', { ctrl: true } as InkKey);
+ capturedUseKeypressHandler({ name: 'y', ctrl: true } as Key);
});
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
ApprovalMode.YOLO,
@@ -195,7 +199,10 @@ describe('useAutoAcceptIndicator', () => {
expect(result.current).toBe(ApprovalMode.YOLO);
act(() => {
- capturedUseInputHandler('', { tab: true, shift: true } as InkKey);
+ capturedUseKeypressHandler({
+ name: 'tab',
+ shift: true,
+ } as Key);
});
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
ApprovalMode.AUTO_EDIT,
@@ -203,7 +210,10 @@ describe('useAutoAcceptIndicator', () => {
expect(result.current).toBe(ApprovalMode.AUTO_EDIT);
act(() => {
- capturedUseInputHandler('', { tab: true, shift: true } as InkKey);
+ capturedUseKeypressHandler({
+ name: 'tab',
+ shift: true,
+ } as Key);
});
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalledWith(
ApprovalMode.DEFAULT,
@@ -220,37 +230,51 @@ describe('useAutoAcceptIndicator', () => {
);
act(() => {
- capturedUseInputHandler('', { tab: true, shift: false } as InkKey);
+ capturedUseKeypressHandler({
+ name: 'tab',
+ shift: false,
+ } as Key);
});
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
act(() => {
- capturedUseInputHandler('', { tab: false, shift: true } as InkKey);
+ capturedUseKeypressHandler({
+ name: 'unknown',
+ shift: true,
+ } as Key);
});
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
act(() => {
- capturedUseInputHandler('a', { tab: false, shift: false } as InkKey);
+ capturedUseKeypressHandler({
+ name: 'a',
+ shift: false,
+ ctrl: false,
+ } as Key);
});
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
act(() => {
- capturedUseInputHandler('y', { tab: true } as InkKey);
+ capturedUseKeypressHandler({ name: 'y', ctrl: false } as Key);
});
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
act(() => {
- capturedUseInputHandler('a', { ctrl: true } as InkKey);
+ capturedUseKeypressHandler({ name: 'a', ctrl: true } as Key);
});
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
act(() => {
- capturedUseInputHandler('y', { shift: true } as InkKey);
+ capturedUseKeypressHandler({ name: 'y', shift: true } as Key);
});
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
act(() => {
- capturedUseInputHandler('a', { ctrl: true, shift: true } as InkKey);
+ capturedUseKeypressHandler({
+ name: 'a',
+ ctrl: true,
+ shift: true,
+ } as Key);
});
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
});
diff --git a/packages/cli/src/ui/hooks/useAutoAcceptIndicator.ts b/packages/cli/src/ui/hooks/useAutoAcceptIndicator.ts
index 8af3cea1..2cc16077 100644
--- a/packages/cli/src/ui/hooks/useAutoAcceptIndicator.ts
+++ b/packages/cli/src/ui/hooks/useAutoAcceptIndicator.ts
@@ -5,8 +5,8 @@
*/
import { useState, useEffect } from 'react';
-import { useInput } from 'ink';
import { ApprovalMode, type Config } from '@google/gemini-cli-core';
+import { useKeypress } from './useKeypress.js';
export interface UseAutoAcceptIndicatorArgs {
config: Config;
@@ -23,27 +23,30 @@ export function useAutoAcceptIndicator({
setShowAutoAcceptIndicator(currentConfigValue);
}, [currentConfigValue]);
- useInput((input, key) => {
- let nextApprovalMode: ApprovalMode | undefined;
+ useKeypress(
+ (key) => {
+ let nextApprovalMode: ApprovalMode | undefined;
- if (key.ctrl && input === 'y') {
- nextApprovalMode =
- config.getApprovalMode() === ApprovalMode.YOLO
- ? ApprovalMode.DEFAULT
- : ApprovalMode.YOLO;
- } else if (key.tab && key.shift) {
- nextApprovalMode =
- config.getApprovalMode() === ApprovalMode.AUTO_EDIT
- ? ApprovalMode.DEFAULT
- : ApprovalMode.AUTO_EDIT;
- }
+ if (key.ctrl && key.name === 'y') {
+ nextApprovalMode =
+ config.getApprovalMode() === ApprovalMode.YOLO
+ ? ApprovalMode.DEFAULT
+ : ApprovalMode.YOLO;
+ } else if (key.shift && key.name === 'tab') {
+ nextApprovalMode =
+ config.getApprovalMode() === ApprovalMode.AUTO_EDIT
+ ? ApprovalMode.DEFAULT
+ : ApprovalMode.AUTO_EDIT;
+ }
- if (nextApprovalMode) {
- config.setApprovalMode(nextApprovalMode);
- // Update local state immediately for responsiveness
- setShowAutoAcceptIndicator(nextApprovalMode);
- }
- });
+ if (nextApprovalMode) {
+ config.setApprovalMode(nextApprovalMode);
+ // Update local state immediately for responsiveness
+ setShowAutoAcceptIndicator(nextApprovalMode);
+ }
+ },
+ { isActive: true },
+ );
return showAutoAcceptIndicator;
}
diff --git a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
index 751b869e..37d63e9a 100644
--- a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
+++ b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
@@ -8,7 +8,7 @@
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
import { renderHook, act, waitFor } from '@testing-library/react';
import { useGeminiStream, mergePartListUnions } from './useGeminiStream.js';
-import { useInput } from 'ink';
+import { useKeypress } from './useKeypress.js';
import {
useReactToolScheduler,
TrackedToolCall,
@@ -71,10 +71,9 @@ vi.mock('./useReactToolScheduler.js', async (importOriginal) => {
};
});
-vi.mock('ink', async (importOriginal) => {
- const actualInkModule = (await importOriginal()) as any;
- return { ...(actualInkModule || {}), useInput: vi.fn() };
-});
+vi.mock('./useKeypress.js', () => ({
+ useKeypress: vi.fn(),
+}));
vi.mock('./shellCommandProcessor.js', () => ({
useShellCommandProcessor: vi.fn().mockReturnValue({
@@ -899,19 +898,23 @@ describe('useGeminiStream', () => {
});
describe('User Cancellation', () => {
- let useInputCallback: (input: string, key: any) => void;
- const mockUseInput = useInput as Mock;
+ let keypressCallback: (key: any) => void;
+ const mockUseKeypress = useKeypress as Mock;
beforeEach(() => {
- // Capture the callback passed to useInput
- mockUseInput.mockImplementation((callback) => {
- useInputCallback = callback;
+ // Capture the callback passed to useKeypress
+ mockUseKeypress.mockImplementation((callback, options) => {
+ if (options.isActive) {
+ keypressCallback = callback;
+ } else {
+ keypressCallback = () => {};
+ }
});
});
const simulateEscapeKeyPress = () => {
act(() => {
- useInputCallback('', { escape: true });
+ keypressCallback({ name: 'escape' });
});
};
diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts
index 6385d267..6f3cb4fd 100644
--- a/packages/cli/src/ui/hooks/useGeminiStream.ts
+++ b/packages/cli/src/ui/hooks/useGeminiStream.ts
@@ -5,7 +5,6 @@
*/
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
-import { useInput } from 'ink';
import {
Config,
GeminiClient,
@@ -55,6 +54,7 @@ import {
TrackedCancelledToolCall,
} from './useReactToolScheduler.js';
import { useSessionStats } from '../contexts/SessionContext.js';
+import { useKeypress } from './useKeypress.js';
export function mergePartListUnions(list: PartListUnion[]): PartListUnion {
const resultParts: PartListUnion = [];
@@ -213,11 +213,14 @@ export const useGeminiStream = (
pendingHistoryItemRef,
]);
- useInput((_input, key) => {
- if (key.escape) {
- cancelOngoingRequest();
- }
- });
+ useKeypress(
+ (key) => {
+ if (key.name === 'escape') {
+ cancelOngoingRequest();
+ }
+ },
+ { isActive: streamingState === StreamingState.Responding },
+ );
const prepareQueryForGemini = useCallback(
async (