mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
fix(cli): dismiss '?' shortcuts help on hotkeys and active states (#18583)
Co-authored-by: jacob314 <jacob314@gmail.com>
This commit is contained in:
@@ -130,9 +130,10 @@ available combinations.
|
|||||||
terminal isn't configured to send Meta with Option.
|
terminal isn't configured to send Meta with Option.
|
||||||
- `!` on an empty prompt: Enter or exit shell mode.
|
- `!` on an empty prompt: Enter or exit shell mode.
|
||||||
- `?` on an empty prompt: Toggle the shortcuts panel above the input. Press
|
- `?` on an empty prompt: Toggle the shortcuts panel above the input. Press
|
||||||
`Esc`, `Backspace`, or any printable key to close it. Press `?` again to close
|
`Esc`, `Backspace`, any printable key, or a registered app hotkey to close it.
|
||||||
the panel and insert a `?` into the prompt. You can hide only the hint text
|
The panel also auto-hides while the agent is running/streaming or when
|
||||||
via `ui.showShortcutsHint`, without changing this keyboard behavior.
|
action-required dialogs are shown. Press `?` again to close the panel and
|
||||||
|
insert a `?` into the prompt.
|
||||||
- `\` (at end of a line) + `Enter`: Insert a newline without leaving single-line
|
- `\` (at end of a line) + `Enter`: Insert a newline without leaving single-line
|
||||||
mode.
|
mode.
|
||||||
- `Esc` pressed twice quickly: Clear the input prompt if it is not empty,
|
- `Esc` pressed twice quickly: Clear the input prompt if it is not empty,
|
||||||
|
|||||||
@@ -197,7 +197,8 @@ import { useTextBuffer } from './components/shared/text-buffer.js';
|
|||||||
import { useLogger } from './hooks/useLogger.js';
|
import { useLogger } from './hooks/useLogger.js';
|
||||||
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
|
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
|
||||||
import { useInputHistoryStore } from './hooks/useInputHistoryStore.js';
|
import { useInputHistoryStore } from './hooks/useInputHistoryStore.js';
|
||||||
import { useKeypress } from './hooks/useKeypress.js';
|
import { useKeypress, type Key } from './hooks/useKeypress.js';
|
||||||
|
import * as useKeypressModule from './hooks/useKeypress.js';
|
||||||
import { measureElement } from 'ink';
|
import { measureElement } from 'ink';
|
||||||
import { useTerminalSize } from './hooks/useTerminalSize.js';
|
import { useTerminalSize } from './hooks/useTerminalSize.js';
|
||||||
import {
|
import {
|
||||||
@@ -2091,6 +2092,128 @@ describe('AppContainer State Management', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Shortcuts Help Visibility', () => {
|
||||||
|
let handleGlobalKeypress: (key: Key) => boolean;
|
||||||
|
let mockedUseKeypress: Mock;
|
||||||
|
let rerender: () => void;
|
||||||
|
let unmount: () => void;
|
||||||
|
|
||||||
|
const setupShortcutsVisibilityTest = async () => {
|
||||||
|
const renderResult = renderAppContainer();
|
||||||
|
await act(async () => {
|
||||||
|
vi.advanceTimersByTime(0);
|
||||||
|
});
|
||||||
|
rerender = () => renderResult.rerender(getAppContainer());
|
||||||
|
unmount = renderResult.unmount;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pressKey = (key: Partial<Key>) => {
|
||||||
|
act(() => {
|
||||||
|
handleGlobalKeypress({
|
||||||
|
name: 'r',
|
||||||
|
shift: false,
|
||||||
|
alt: false,
|
||||||
|
ctrl: false,
|
||||||
|
cmd: false,
|
||||||
|
insertable: false,
|
||||||
|
sequence: '',
|
||||||
|
...key,
|
||||||
|
} as Key);
|
||||||
|
});
|
||||||
|
rerender();
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockedUseKeypress = vi.spyOn(useKeypressModule, 'useKeypress') as Mock;
|
||||||
|
mockedUseKeypress.mockImplementation(
|
||||||
|
(callback: (key: Key) => boolean, options: { isActive: boolean }) => {
|
||||||
|
// AppContainer registers multiple keypress handlers; capture only
|
||||||
|
// active handlers so inactive copy-mode handler doesn't override.
|
||||||
|
if (options?.isActive) {
|
||||||
|
handleGlobalKeypress = callback;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockedUseKeypress.mockRestore();
|
||||||
|
vi.useRealTimers();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dismisses shortcuts help when a registered hotkey is pressed', async () => {
|
||||||
|
await setupShortcutsVisibilityTest();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
capturedUIActions.setShortcutsHelpVisible(true);
|
||||||
|
});
|
||||||
|
rerender();
|
||||||
|
expect(capturedUIState.shortcutsHelpVisible).toBe(true);
|
||||||
|
|
||||||
|
pressKey({ name: 'r', ctrl: true, sequence: '\x12' }); // Ctrl+R
|
||||||
|
expect(capturedUIState.shortcutsHelpVisible).toBe(false);
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dismisses shortcuts help when streaming starts', async () => {
|
||||||
|
await setupShortcutsVisibilityTest();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
capturedUIActions.setShortcutsHelpVisible(true);
|
||||||
|
});
|
||||||
|
rerender();
|
||||||
|
expect(capturedUIState.shortcutsHelpVisible).toBe(true);
|
||||||
|
|
||||||
|
mockedUseGeminiStream.mockReturnValue({
|
||||||
|
...DEFAULT_GEMINI_STREAM_MOCK,
|
||||||
|
streamingState: 'responding',
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
rerender();
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(capturedUIState.shortcutsHelpVisible).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dismisses shortcuts help when action-required confirmation appears', async () => {
|
||||||
|
await setupShortcutsVisibilityTest();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
capturedUIActions.setShortcutsHelpVisible(true);
|
||||||
|
});
|
||||||
|
rerender();
|
||||||
|
expect(capturedUIState.shortcutsHelpVisible).toBe(true);
|
||||||
|
|
||||||
|
mockedUseSlashCommandProcessor.mockReturnValue({
|
||||||
|
handleSlashCommand: vi.fn(),
|
||||||
|
slashCommands: [],
|
||||||
|
pendingHistoryItems: [],
|
||||||
|
commandContext: {},
|
||||||
|
shellConfirmationRequest: null,
|
||||||
|
confirmationRequest: {
|
||||||
|
prompt: 'Confirm this action?',
|
||||||
|
onConfirm: vi.fn(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
rerender();
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(capturedUIState.shortcutsHelpVisible).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Copy Mode (CTRL+S)', () => {
|
describe('Copy Mode (CTRL+S)', () => {
|
||||||
let rerender: () => void;
|
let rerender: () => void;
|
||||||
let unmount: () => void;
|
let unmount: () => void;
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ import { isSlashCommand } from './utils/commandUtils.js';
|
|||||||
import { useTerminalTheme } from './hooks/useTerminalTheme.js';
|
import { useTerminalTheme } from './hooks/useTerminalTheme.js';
|
||||||
import { useTimedMessage } from './hooks/useTimedMessage.js';
|
import { useTimedMessage } from './hooks/useTimedMessage.js';
|
||||||
import { isITerm2 } from './utils/terminalUtils.js';
|
import { isITerm2 } from './utils/terminalUtils.js';
|
||||||
|
import { shouldDismissShortcutsHelpOnHotkey } from './utils/shortcutsHelp.js';
|
||||||
|
|
||||||
function isToolExecuting(pendingHistoryItems: HistoryItemWithoutId[]) {
|
function isToolExecuting(pendingHistoryItems: HistoryItemWithoutId[]) {
|
||||||
return pendingHistoryItems.some((item) => {
|
return pendingHistoryItems.some((item) => {
|
||||||
@@ -1489,6 +1490,10 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
debugLogger.log('[DEBUG] Keystroke:', JSON.stringify(key));
|
debugLogger.log('[DEBUG] Keystroke:', JSON.stringify(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shortcutsHelpVisible && shouldDismissShortcutsHelpOnHotkey(key)) {
|
||||||
|
setShortcutsHelpVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (isAlternateBuffer && keyMatchers[Command.TOGGLE_COPY_MODE](key)) {
|
if (isAlternateBuffer && keyMatchers[Command.TOGGLE_COPY_MODE](key)) {
|
||||||
setCopyModeEnabled(true);
|
setCopyModeEnabled(true);
|
||||||
disableMouseEvents();
|
disableMouseEvents();
|
||||||
@@ -1652,6 +1657,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
refreshStatic,
|
refreshStatic,
|
||||||
setCopyModeEnabled,
|
setCopyModeEnabled,
|
||||||
isAlternateBuffer,
|
isAlternateBuffer,
|
||||||
|
shortcutsHelpVisible,
|
||||||
backgroundCurrentShell,
|
backgroundCurrentShell,
|
||||||
toggleBackgroundShell,
|
toggleBackgroundShell,
|
||||||
backgroundShells,
|
backgroundShells,
|
||||||
@@ -1811,6 +1817,36 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
[pendingSlashCommandHistoryItems, pendingGeminiHistoryItems],
|
[pendingSlashCommandHistoryItems, pendingGeminiHistoryItems],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hasPendingToolConfirmation = useMemo(
|
||||||
|
() => isToolAwaitingConfirmation(pendingHistoryItems),
|
||||||
|
[pendingHistoryItems],
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasPendingActionRequired =
|
||||||
|
hasPendingToolConfirmation ||
|
||||||
|
!!commandConfirmationRequest ||
|
||||||
|
!!authConsentRequest ||
|
||||||
|
confirmUpdateExtensionRequests.length > 0 ||
|
||||||
|
!!loopDetectionConfirmationRequest ||
|
||||||
|
!!proQuotaRequest ||
|
||||||
|
!!validationRequest ||
|
||||||
|
!!customDialog;
|
||||||
|
|
||||||
|
const isPassiveShortcutsHelpState =
|
||||||
|
isInputActive &&
|
||||||
|
streamingState === StreamingState.Idle &&
|
||||||
|
!hasPendingActionRequired;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shortcutsHelpVisible && !isPassiveShortcutsHelpState) {
|
||||||
|
setShortcutsHelpVisible(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
shortcutsHelpVisible,
|
||||||
|
isPassiveShortcutsHelpState,
|
||||||
|
setShortcutsHelpVisible,
|
||||||
|
]);
|
||||||
|
|
||||||
const allToolCalls = useMemo(
|
const allToolCalls = useMemo(
|
||||||
() =>
|
() =>
|
||||||
pendingHistoryItems
|
pendingHistoryItems
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ const createMockUIActions = (): UIActions =>
|
|||||||
setShellModeActive: vi.fn(),
|
setShellModeActive: vi.fn(),
|
||||||
onEscapePromptChange: vi.fn(),
|
onEscapePromptChange: vi.fn(),
|
||||||
vimHandleInput: vi.fn(),
|
vimHandleInput: vi.fn(),
|
||||||
|
setShortcutsHelpVisible: vi.fn(),
|
||||||
}) as Partial<UIActions> as UIActions;
|
}) as Partial<UIActions> as UIActions;
|
||||||
|
|
||||||
const createMockConfig = (overrides = {}): Config =>
|
const createMockConfig = (overrides = {}): Config =>
|
||||||
@@ -337,7 +338,7 @@ describe('Composer', () => {
|
|||||||
expect(output).toContain('LoadingIndicator: Thinking ...');
|
expect(output).toContain('LoadingIndicator: Thinking ...');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps shortcuts hint visible while loading', () => {
|
it('hides shortcuts hint while loading', () => {
|
||||||
const uiState = createMockUIState({
|
const uiState = createMockUIState({
|
||||||
streamingState: StreamingState.Responding,
|
streamingState: StreamingState.Responding,
|
||||||
elapsedTime: 1,
|
elapsedTime: 1,
|
||||||
@@ -347,7 +348,7 @@ describe('Composer', () => {
|
|||||||
|
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
expect(output).toContain('LoadingIndicator');
|
expect(output).toContain('LoadingIndicator');
|
||||||
expect(output).toContain('ShortcutsHint');
|
expect(output).not.toContain('ShortcutsHint');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders LoadingIndicator without thought when accessibility disables loading phrases', () => {
|
it('renders LoadingIndicator without thought when accessibility disables loading phrases', () => {
|
||||||
@@ -686,4 +687,43 @@ describe('Composer', () => {
|
|||||||
expect(lastFrame()).toContain('ShortcutsHint');
|
expect(lastFrame()).toContain('ShortcutsHint');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Shortcuts Help', () => {
|
||||||
|
it('shows shortcuts help in passive state', () => {
|
||||||
|
const uiState = createMockUIState({
|
||||||
|
shortcutsHelpVisible: true,
|
||||||
|
streamingState: StreamingState.Idle,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { lastFrame } = renderComposer(uiState);
|
||||||
|
|
||||||
|
expect(lastFrame()).toContain('ShortcutsHelp');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides shortcuts help while streaming', () => {
|
||||||
|
const uiState = createMockUIState({
|
||||||
|
shortcutsHelpVisible: true,
|
||||||
|
streamingState: StreamingState.Responding,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { lastFrame } = renderComposer(uiState);
|
||||||
|
|
||||||
|
expect(lastFrame()).not.toContain('ShortcutsHelp');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides shortcuts help when action is required', () => {
|
||||||
|
const uiState = createMockUIState({
|
||||||
|
shortcutsHelpVisible: true,
|
||||||
|
customDialog: (
|
||||||
|
<Box>
|
||||||
|
<Text>Dialog content</Text>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { lastFrame } = renderComposer(uiState);
|
||||||
|
|
||||||
|
expect(lastFrame()).not.toContain('ShortcutsHelp');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { Box, useIsScreenReaderEnabled } from 'ink';
|
import { Box, useIsScreenReaderEnabled } from 'ink';
|
||||||
import { LoadingIndicator } from './LoadingIndicator.js';
|
import { LoadingIndicator } from './LoadingIndicator.js';
|
||||||
import { StatusDisplay } from './StatusDisplay.js';
|
import { StatusDisplay } from './StatusDisplay.js';
|
||||||
@@ -28,7 +28,11 @@ import { useVimMode } from '../contexts/VimModeContext.js';
|
|||||||
import { useConfig } from '../contexts/ConfigContext.js';
|
import { useConfig } from '../contexts/ConfigContext.js';
|
||||||
import { useSettings } from '../contexts/SettingsContext.js';
|
import { useSettings } from '../contexts/SettingsContext.js';
|
||||||
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
|
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
|
||||||
import { StreamingState, ToolCallStatus } from '../types.js';
|
import {
|
||||||
|
StreamingState,
|
||||||
|
type HistoryItemToolGroup,
|
||||||
|
ToolCallStatus,
|
||||||
|
} from '../types.js';
|
||||||
import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
|
import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
|
||||||
import { TodoTray } from './messages/Todo.js';
|
import { TodoTray } from './messages/Todo.js';
|
||||||
import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';
|
import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';
|
||||||
@@ -51,11 +55,19 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
|||||||
const suggestionsPosition = isAlternateBuffer ? 'above' : 'below';
|
const suggestionsPosition = isAlternateBuffer ? 'above' : 'below';
|
||||||
const hideContextSummary =
|
const hideContextSummary =
|
||||||
suggestionsVisible && suggestionsPosition === 'above';
|
suggestionsVisible && suggestionsPosition === 'above';
|
||||||
const hasPendingToolConfirmation = (uiState.pendingHistoryItems ?? []).some(
|
|
||||||
(item) =>
|
const hasPendingToolConfirmation = useMemo(
|
||||||
item.type === 'tool_group' &&
|
() =>
|
||||||
item.tools.some((tool) => tool.status === ToolCallStatus.Confirming),
|
(uiState.pendingHistoryItems ?? [])
|
||||||
|
.filter(
|
||||||
|
(item): item is HistoryItemToolGroup => item.type === 'tool_group',
|
||||||
|
)
|
||||||
|
.some((item) =>
|
||||||
|
item.tools.some((tool) => tool.status === ToolCallStatus.Confirming),
|
||||||
|
),
|
||||||
|
[uiState.pendingHistoryItems],
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasPendingActionRequired =
|
const hasPendingActionRequired =
|
||||||
hasPendingToolConfirmation ||
|
hasPendingToolConfirmation ||
|
||||||
Boolean(uiState.commandConfirmationRequest) ||
|
Boolean(uiState.commandConfirmationRequest) ||
|
||||||
@@ -65,6 +77,31 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
|||||||
Boolean(uiState.quota.proQuotaRequest) ||
|
Boolean(uiState.quota.proQuotaRequest) ||
|
||||||
Boolean(uiState.quota.validationRequest) ||
|
Boolean(uiState.quota.validationRequest) ||
|
||||||
Boolean(uiState.customDialog);
|
Boolean(uiState.customDialog);
|
||||||
|
const isPassiveShortcutsHelpState =
|
||||||
|
uiState.isInputActive &&
|
||||||
|
uiState.streamingState === StreamingState.Idle &&
|
||||||
|
!hasPendingActionRequired;
|
||||||
|
|
||||||
|
const { setShortcutsHelpVisible } = uiActions;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (uiState.shortcutsHelpVisible && !isPassiveShortcutsHelpState) {
|
||||||
|
setShortcutsHelpVisible(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
uiState.shortcutsHelpVisible,
|
||||||
|
isPassiveShortcutsHelpState,
|
||||||
|
setShortcutsHelpVisible,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const showShortcutsHelp =
|
||||||
|
uiState.shortcutsHelpVisible &&
|
||||||
|
uiState.streamingState === StreamingState.Idle &&
|
||||||
|
!hasPendingActionRequired;
|
||||||
|
const showShortcutsHint =
|
||||||
|
settings.merged.ui.showShortcutsHint &&
|
||||||
|
uiState.streamingState === StreamingState.Idle &&
|
||||||
|
!hasPendingActionRequired;
|
||||||
const hasToast = shouldShowToast(uiState);
|
const hasToast = shouldShowToast(uiState);
|
||||||
const showLoadingIndicator =
|
const showLoadingIndicator =
|
||||||
(!uiState.embeddedShellFocused || uiState.isBackgroundShellVisible) &&
|
(!uiState.embeddedShellFocused || uiState.isBackgroundShellVisible) &&
|
||||||
@@ -133,11 +170,10 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
|||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
alignItems={isNarrow ? 'flex-start' : 'flex-end'}
|
alignItems={isNarrow ? 'flex-start' : 'flex-end'}
|
||||||
>
|
>
|
||||||
{settings.merged.ui.showShortcutsHint &&
|
{showShortcutsHint && <ShortcutsHint />}
|
||||||
!hasPendingActionRequired && <ShortcutsHint />}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{uiState.shortcutsHelpVisible && <ShortcutsHelp />}
|
{showShortcutsHelp && <ShortcutsHelp />}
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
<Box
|
<Box
|
||||||
justifyContent={
|
justifyContent={
|
||||||
|
|||||||
@@ -4342,6 +4342,18 @@ describe('InputPrompt', () => {
|
|||||||
vi.mocked(clipboardy.read).mockResolvedValue('clipboard text');
|
vi.mocked(clipboardy.read).mockResolvedValue('clipboard text');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Ctrl+R hotkey is pressed',
|
||||||
|
input: '\x12',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Ctrl+X hotkey is pressed',
|
||||||
|
input: '\x18',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'F12 hotkey is pressed',
|
||||||
|
input: '\x1b[24~',
|
||||||
|
},
|
||||||
])(
|
])(
|
||||||
'should close shortcuts help when a $name',
|
'should close shortcuts help when a $name',
|
||||||
async ({ input, setupMocks, mouseEventsEnabled }) => {
|
async ({ input, setupMocks, mouseEventsEnabled }) => {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ import { useMouseClick } from '../hooks/useMouseClick.js';
|
|||||||
import { useMouse, type MouseEvent } from '../contexts/MouseContext.js';
|
import { useMouse, type MouseEvent } from '../contexts/MouseContext.js';
|
||||||
import { useUIActions } from '../contexts/UIActionsContext.js';
|
import { useUIActions } from '../contexts/UIActionsContext.js';
|
||||||
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
|
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
|
||||||
|
import { shouldDismissShortcutsHelpOnHotkey } from '../utils/shortcutsHelp.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if the terminal can be trusted to handle paste events atomically
|
* Returns if the terminal can be trusted to handle paste events atomically
|
||||||
@@ -661,6 +662,10 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shortcutsHelpVisible && shouldDismissShortcutsHelpOnHotkey(key)) {
|
||||||
|
setShortcutsHelpVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (shortcutsHelpVisible) {
|
if (shortcutsHelpVisible) {
|
||||||
if (key.sequence === '?' && key.insertable) {
|
if (key.sequence === '?' && key.insertable) {
|
||||||
setShortcutsHelpVisible(false);
|
setShortcutsHelpVisible(false);
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Command, keyMatchers } from '../keyMatchers.js';
|
||||||
|
import type { Key } from '../hooks/useKeypress.js';
|
||||||
|
|
||||||
|
export function shouldDismissShortcutsHelpOnHotkey(key: Key): boolean {
|
||||||
|
return Object.values(Command).some((command) => keyMatchers[command](key));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user