mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-28 05:55:17 -07:00
Merge branch 'main' into adibakm/disable-esc-esc-rewind
This commit is contained in:
@@ -71,8 +71,12 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{(!uiState.slashCommands || !uiState.isConfigInitialized) && (
|
||||
<ConfigInitDisplay />
|
||||
{(!uiState.slashCommands ||
|
||||
!uiState.isConfigInitialized ||
|
||||
uiState.isResuming) && (
|
||||
<ConfigInitDisplay
|
||||
message={uiState.isResuming ? 'Resuming session...' : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
<QueuedMessageDisplay messageQueue={uiState.messageQueue} />
|
||||
|
||||
@@ -15,13 +15,17 @@ import {
|
||||
import { GeminiSpinner } from './GeminiRespondingSpinner.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
export const ConfigInitDisplay = () => {
|
||||
const [message, setMessage] = useState('Initializing...');
|
||||
export const ConfigInitDisplay = ({
|
||||
message: initialMessage = 'Initializing...',
|
||||
}: {
|
||||
message?: string;
|
||||
}) => {
|
||||
const [message, setMessage] = useState(initialMessage);
|
||||
|
||||
useEffect(() => {
|
||||
const onChange = (clients?: Map<string, McpClient>) => {
|
||||
if (!clients || clients.size === 0) {
|
||||
setMessage(`Initializing...`);
|
||||
setMessage(initialMessage);
|
||||
return;
|
||||
}
|
||||
let connected = 0;
|
||||
@@ -39,12 +43,18 @@ export const ConfigInitDisplay = () => {
|
||||
const displayedServers = connecting.slice(0, maxDisplay).join(', ');
|
||||
const remaining = connecting.length - maxDisplay;
|
||||
const suffix = remaining > 0 ? `, +${remaining} more` : '';
|
||||
const mcpMessage = `Connecting to MCP servers... (${connected}/${clients.size}) - Waiting for: ${displayedServers}${suffix}`;
|
||||
setMessage(
|
||||
`Connecting to MCP servers... (${connected}/${clients.size}) - Waiting for: ${displayedServers}${suffix}`,
|
||||
initialMessage && initialMessage !== 'Initializing...'
|
||||
? `${initialMessage} (${mcpMessage})`
|
||||
: mcpMessage,
|
||||
);
|
||||
} else {
|
||||
const mcpMessage = `Connecting to MCP servers... (${connected}/${clients.size})`;
|
||||
setMessage(
|
||||
`Connecting to MCP servers... (${connected}/${clients.size})`,
|
||||
initialMessage && initialMessage !== 'Initializing...'
|
||||
? `${initialMessage} (${mcpMessage})`
|
||||
: mcpMessage,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -53,7 +63,7 @@ export const ConfigInitDisplay = () => {
|
||||
return () => {
|
||||
coreEvents.off(CoreEvent.McpClientUpdate, onChange);
|
||||
};
|
||||
}, []);
|
||||
}, [initialMessage]);
|
||||
|
||||
return (
|
||||
<Box marginTop={1}>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from 'vitest';
|
||||
import { ValidationDialog } from './ValidationDialog.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import type { Key } from '../hooks/useKeypress.js';
|
||||
|
||||
// Mock the child components and utilities
|
||||
vi.mock('./shared/RadioButtonSelect.js', () => ({
|
||||
@@ -41,8 +42,15 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
// Capture keypress handler to test it
|
||||
let mockKeypressHandler: (key: Key) => void;
|
||||
let mockKeypressOptions: { isActive: boolean };
|
||||
|
||||
vi.mock('../hooks/useKeypress.js', () => ({
|
||||
useKeypress: vi.fn(),
|
||||
useKeypress: vi.fn((handler, options) => {
|
||||
mockKeypressHandler = handler;
|
||||
mockKeypressOptions = options;
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('ValidationDialog', () => {
|
||||
@@ -99,6 +107,29 @@ describe('ValidationDialog', () => {
|
||||
expect(lastFrame()).toContain('https://example.com/help');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should call onChoice with cancel when ESCAPE is pressed', () => {
|
||||
const { unmount } = render(<ValidationDialog onChoice={mockOnChoice} />);
|
||||
|
||||
// Verify the keypress hook is active
|
||||
expect(mockKeypressOptions.isActive).toBe(true);
|
||||
|
||||
// Simulate ESCAPE key press
|
||||
act(() => {
|
||||
mockKeypressHandler({
|
||||
name: 'escape',
|
||||
ctrl: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
cmd: false,
|
||||
insertable: false,
|
||||
sequence: '\x1b',
|
||||
});
|
||||
});
|
||||
|
||||
expect(mockOnChoice).toHaveBeenCalledWith('cancel');
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChoice handling', () => {
|
||||
|
||||
@@ -48,17 +48,17 @@ export function ValidationDialog({
|
||||
},
|
||||
];
|
||||
|
||||
// Handle keypresses during 'waiting' state (ESC to cancel, Enter to confirm completion)
|
||||
// Handle keypresses globally for cancellation, and specific logic for waiting state
|
||||
useKeypress(
|
||||
(key) => {
|
||||
if (keyMatchers[Command.ESCAPE](key) || keyMatchers[Command.QUIT](key)) {
|
||||
onChoice('cancel');
|
||||
} else if (keyMatchers[Command.RETURN](key)) {
|
||||
} else if (state === 'waiting' && keyMatchers[Command.RETURN](key)) {
|
||||
// User confirmed verification is complete - transition to 'complete' state
|
||||
setState('complete');
|
||||
}
|
||||
},
|
||||
{ isActive: state === 'waiting' },
|
||||
{ isActive: state !== 'complete' },
|
||||
);
|
||||
|
||||
// When state becomes 'complete', show success message briefly then proceed
|
||||
|
||||
Reference in New Issue
Block a user