diff options
Diffstat (limited to 'packages/cli/src/ui/hooks')
| -rw-r--r-- | packages/cli/src/ui/hooks/useAutoAcceptIndicator.test.ts | 70 | ||||
| -rw-r--r-- | packages/cli/src/ui/hooks/useAutoAcceptIndicator.ts | 43 | ||||
| -rw-r--r-- | packages/cli/src/ui/hooks/useGeminiStream.test.tsx | 25 | ||||
| -rw-r--r-- | packages/cli/src/ui/hooks/useGeminiStream.ts | 15 |
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 ( |
