feat: add click-to-focus support for interactive shell (#13341)

This commit is contained in:
Gal Zahavi
2025-11-19 15:49:39 -08:00
committed by GitHub
parent d0a845b6e6
commit 2231497b1f
17 changed files with 1072 additions and 416 deletions

View File

@@ -8,6 +8,7 @@ import { render as inkRender } from 'ink-testing-library';
import { Box } from 'ink';
import type React from 'react';
import { act } from 'react';
import { vi } from 'vitest';
import { LoadedSettings, type Settings } from '../config/settings.js';
import { KeypressProvider } from '../ui/contexts/KeypressContext.js';
import { SettingsContext } from '../ui/contexts/SettingsContext.js';
@@ -20,6 +21,10 @@ import { VimModeProvider } from '../ui/contexts/VimModeContext.js';
import { MouseProvider } from '../ui/contexts/MouseContext.js';
import { ScrollProvider } from '../ui/contexts/ScrollProvider.js';
import { StreamingContext } from '../ui/contexts/StreamingContext.js';
import {
type UIActions,
UIActionsContext,
} from '../ui/contexts/UIActionsContext.js';
import { type Config } from '@google/gemini-cli-core';
@@ -65,6 +70,19 @@ export const render = (
};
};
export const simulateClick = async (
stdin: ReturnType<typeof inkRender>['stdin'],
col: number,
row: number,
button: 0 | 1 | 2 = 0, // 0 for left, 1 for middle, 2 for right
) => {
// Terminal mouse events are 1-based, so convert if necessary.
const mouseEventString = `\x1b[<${button};${col};${row}M`;
await act(async () => {
stdin.write(mouseEventString);
});
};
const mockConfig = {
getModel: () => 'gemini-pro',
getTargetDir: () =>
@@ -117,6 +135,38 @@ const baseMockUiState = {
currentModel: 'gemini-pro',
};
const mockUIActions: UIActions = {
handleThemeSelect: vi.fn(),
closeThemeDialog: vi.fn(),
handleThemeHighlight: vi.fn(),
handleAuthSelect: vi.fn(),
setAuthState: vi.fn(),
onAuthError: vi.fn(),
handleEditorSelect: vi.fn(),
exitEditorDialog: vi.fn(),
exitPrivacyNotice: vi.fn(),
closeSettingsDialog: vi.fn(),
closeModelDialog: vi.fn(),
openPermissionsDialog: vi.fn(),
closePermissionsDialog: vi.fn(),
setShellModeActive: vi.fn(),
vimHandleInput: vi.fn(),
handleIdePromptComplete: vi.fn(),
handleFolderTrustSelect: vi.fn(),
setConstrainHeight: vi.fn(),
onEscapePromptChange: vi.fn(),
refreshStatic: vi.fn(),
handleFinalSubmit: vi.fn(),
handleClearScreen: vi.fn(),
handleProQuotaChoice: vi.fn(),
setQueueErrorMessage: vi.fn(),
popAllMessages: vi.fn(),
handleApiKeySubmit: vi.fn(),
handleApiKeyCancel: vi.fn(),
setBannerVisible: vi.fn(),
setEmbeddedShellFocused: vi.fn(),
};
export const renderWithProviders = (
component: React.ReactElement,
{
@@ -127,6 +177,7 @@ export const renderWithProviders = (
mouseEventsEnabled = false,
config = configProxy as unknown as Config,
useAlternateBuffer,
uiActions,
}: {
shellFocus?: boolean;
settings?: LoadedSettings;
@@ -135,8 +186,9 @@ export const renderWithProviders = (
mouseEventsEnabled?: boolean;
config?: Config;
useAlternateBuffer?: boolean;
uiActions?: Partial<UIActions>;
} = {},
): ReturnType<typeof render> => {
): ReturnType<typeof render> & { simulateClick: typeof simulateClick } => {
const baseState: UIState = new Proxy(
{ ...baseMockUiState, ...providedUiState },
{
@@ -175,27 +227,31 @@ export const renderWithProviders = (
mainAreaWidth,
};
return render(
const finalUIActions = { ...mockUIActions, ...uiActions };
const renderResult = render(
<ConfigContext.Provider value={config}>
<SettingsContext.Provider value={finalSettings}>
<UIStateContext.Provider value={finalUiState}>
<VimModeProvider settings={finalSettings}>
<ShellFocusContext.Provider value={shellFocus}>
<StreamingContext.Provider value={finalUiState.streamingState}>
<KeypressProvider>
<MouseProvider mouseEventsEnabled={mouseEventsEnabled}>
<ScrollProvider>
<Box
width={terminalWidth}
flexShrink={0}
flexGrow={0}
flexDirection="column"
>
{component}
</Box>
</ScrollProvider>
</MouseProvider>
</KeypressProvider>
<UIActionsContext.Provider value={finalUIActions}>
<KeypressProvider>
<MouseProvider mouseEventsEnabled={mouseEventsEnabled}>
<ScrollProvider>
<Box
width={terminalWidth}
flexShrink={0}
flexGrow={0}
flexDirection="column"
>
{component}
</Box>
</ScrollProvider>
</MouseProvider>
</KeypressProvider>
</UIActionsContext.Provider>
</StreamingContext.Provider>
</ShellFocusContext.Provider>
</VimModeProvider>
@@ -204,6 +260,8 @@ export const renderWithProviders = (
</ConfigContext.Provider>,
terminalWidth,
);
return { ...renderResult, simulateClick };
};
export function renderHook<Result, Props>(