Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions packages/cli/src/ui/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ import { type InitializationResult } from '../core/initializer.js';
import { useFocus } from './hooks/useFocus.js';
import { useKeypress, type Key } from './hooks/useKeypress.js';
import { KeypressPriority } from './contexts/KeypressContext.js';
import { keyMatchers, Command } from './keyMatchers.js';
import { Command } from './keyMatchers.js';
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
import { useShellInactivityStatus } from './hooks/useShellInactivityStatus.js';
import { useFolderTrust } from './hooks/useFolderTrust.js';
Expand Down Expand Up @@ -164,7 +164,7 @@ import { NewAgentsChoice } from './components/NewAgentsNotification.js';
import { isSlashCommand } from './utils/commandUtils.js';
import { useTerminalTheme } from './hooks/useTerminalTheme.js';
import { useTimedMessage } from './hooks/useTimedMessage.js';
import { shouldDismissShortcutsHelpOnHotkey } from './utils/shortcutsHelp.js';
import { useIsHelpDismissKey } from './utils/shortcutsHelp.js';
import { useSuspend } from './hooks/useSuspend.js';
import { useRunEventNotifications } from './hooks/useRunEventNotifications.js';
import { isNotificationsEnabled } from '../utils/terminalNotifications.js';
Expand Down Expand Up @@ -205,6 +205,7 @@ import {
useVisibilityToggle,
APPROVAL_MODE_REVEAL_DURATION_MS,
} from './hooks/useVisibilityToggle.js';
import { useKeyMatchers } from './hooks/useKeyMatchers.js';

/**
* The fraction of the terminal width to allocate to the shell.
Expand All @@ -219,6 +220,8 @@ const SHELL_WIDTH_FRACTION = 0.89;
const SHELL_HEIGHT_PADDING = 10;

export const AppContainer = (props: AppContainerProps) => {
const isHelpDismissKey = useIsHelpDismissKey();
const keyMatchers = useKeyMatchers();
const { config, initializationResult, resumedSessionData } = props;
const settings = useSettings();
const { reset } = useOverflowActions()!;
Expand Down Expand Up @@ -1654,7 +1657,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
debugLogger.log('[DEBUG] Keystroke:', JSON.stringify(key));
}

if (shortcutsHelpVisible && shouldDismissShortcutsHelpOnHotkey(key)) {
if (shortcutsHelpVisible && isHelpDismissKey(key)) {
setShortcutsHelpVisible(false);
}

Expand Down Expand Up @@ -1848,6 +1851,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
settings.merged.general.devtools,
showErrorDetails,
triggerExpandHint,
keyMatchers,
isHelpDismissKey,
],
);

Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/ui/auth/ApiAuthDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { useTextBuffer } from '../components/shared/text-buffer.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { clearApiKey, debugLogger } from '@google/gemini-cli-core';
import { useKeypress } from '../hooks/useKeypress.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';

interface ApiAuthDialogProps {
onSubmit: (apiKey: string) => void;
Expand All @@ -28,6 +29,7 @@ export function ApiAuthDialog({
error,
defaultValue = '',
}: ApiAuthDialogProps): React.JSX.Element {
const keyMatchers = useKeyMatchers();
const { terminalWidth } = useUIState();
const viewportWidth = terminalWidth - 8;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { useUIActions } from '../contexts/UIActionsContext.js';
import { Command, keyMatchers } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';

export const AdminSettingsChangedDialog = () => {
const keyMatchers = useKeyMatchers();
const { handleRestart } = useUIActions();

useKeypress(
Expand Down
14 changes: 10 additions & 4 deletions packages/cli/src/ui/components/AskUserDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { BaseSelectionList } from './shared/BaseSelectionList.js';
import type { SelectionListItem } from '../hooks/useSelectionList.js';
import { TabHeader, type Tab } from './shared/TabHeader.js';
import { useKeypress, type Key } from '../hooks/useKeypress.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import { checkExhaustive } from '@google/gemini-cli-core';
import { TextInput } from './shared/TextInput.js';
import { formatCommand } from '../utils/keybindingUtils.js';
Expand All @@ -36,6 +36,7 @@ import { RenderInline } from '../utils/InlineMarkdownRenderer.js';
import { MaxSizedBox } from './shared/MaxSizedBox.js';
import { UIStateContext } from '../contexts/UIStateContext.js';
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';

/** Padding for dialog content to prevent text from touching edges. */
const DIALOG_PADDING = 4;
Expand Down Expand Up @@ -208,6 +209,7 @@ const ReviewView: React.FC<ReviewViewProps> = ({
progressHeader,
extraParts,
}) => {
const keyMatchers = useKeyMatchers();
const unansweredCount = questions.length - Object.keys(answers).length;
const hasUnanswered = unansweredCount > 0;

Expand Down Expand Up @@ -288,6 +290,7 @@ const TextQuestionView: React.FC<TextQuestionViewProps> = ({
progressHeader,
keyboardHints,
}) => {
const keyMatchers = useKeyMatchers();
const isAlternateBuffer = useAlternateBuffer();
const prefix = '> ';
const horizontalPadding = 1; // 1 for cursor
Expand Down Expand Up @@ -325,7 +328,7 @@ const TextQuestionView: React.FC<TextQuestionViewProps> = ({
}
return false;
},
[buffer, textValue],
[buffer, textValue, keyMatchers],
);

useKeypress(handleExtraKeys, { isActive: true, priority: true });
Expand Down Expand Up @@ -487,6 +490,7 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
progressHeader,
keyboardHints,
}) => {
const keyMatchers = useKeyMatchers();
const isAlternateBuffer = useAlternateBuffer();
const numOptions =
(question.options?.length ?? 0) + (question.type !== 'yesno' ? 1 : 0);
Expand Down Expand Up @@ -680,6 +684,7 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
customBuffer,
onEditingCustomOption,
customOptionText,
keyMatchers,
],
);

Expand Down Expand Up @@ -950,6 +955,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
availableHeight: availableHeightProp,
extraParts,
}) => {
const keyMatchers = useKeyMatchers();
const uiState = useContext(UIStateContext);
const availableHeight =
availableHeightProp ??
Expand Down Expand Up @@ -999,7 +1005,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
}
return false;
},
[onCancel, submitted, isEditingCustomOption],
[onCancel, submitted, isEditingCustomOption, keyMatchers],
);

useKeypress(handleCancel, {
Expand Down Expand Up @@ -1032,7 +1038,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
}
return false;
},
[questions.length, submitted, goToNextTab, goToPrevTab],
[questions.length, submitted, goToNextTab, goToPrevTab, keyMatchers],
);

useKeypress(handleNavigation, {
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/ui/components/BackgroundShellDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from '@google/gemini-cli-core';
import { cpLen, cpSlice, getCachedStringWidth } from '../utils/textUtils.js';
import { type BackgroundShell } from '../hooks/shellCommandProcessor.js';
import { Command, keyMatchers } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { formatCommand } from '../utils/keybindingUtils.js';
import {
Expand All @@ -30,6 +30,7 @@ import {
RadioButtonSelect,
type RadioSelectItem,
} from './shared/RadioButtonSelect.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';

interface BackgroundShellDisplayProps {
shells: Map<number, BackgroundShell>;
Expand Down Expand Up @@ -60,6 +61,7 @@ export const BackgroundShellDisplay = ({
isFocused,
isListOpenProp,
}: BackgroundShellDisplayProps) => {
const keyMatchers = useKeyMatchers();
const {
dismissBackgroundShell,
setActiveBackgroundShellPid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { ExitPlanModeDialog } from './ExitPlanModeDialog.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import {
ApprovalMode,
validatePlanContent,
processSingleFileContent,
type FileSystemService,
} from '@google/gemini-cli-core';
import * as fs from 'node:fs';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';

vi.mock('../utils/editorUtils.js', () => ({
openFileInEditor: vi.fn(),
Expand Down Expand Up @@ -402,6 +403,7 @@ Implement a comprehensive authentication system with multiple providers.
}: {
children: React.ReactNode;
}) => {
const keyMatchers = useKeyMatchers();
useKeypress(
(key) => {
if (keyMatchers[Command.QUIT](key)) {
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/ui/components/ExitPlanModeDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import { useConfig } from '../contexts/ConfigContext.js';
import { AskUserDialog } from './AskUserDialog.js';
import { openFileInEditor } from '../utils/editorUtils.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import { formatCommand } from '../utils/keybindingUtils.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';

export interface ExitPlanModeDialogProps {
planPath: string;
Expand Down Expand Up @@ -147,6 +148,7 @@ export const ExitPlanModeDialog: React.FC<ExitPlanModeDialogProps> = ({
width,
availableHeight,
}) => {
const keyMatchers = useKeyMatchers();
const config = useConfig();
const { stdin, setRawMode } = useStdin();
const planState = usePlanContent(planPath, config);
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/ui/components/FooterConfigDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import { theme } from '../semantic-colors.js';
import { useSettingsStore } from '../contexts/SettingsContext.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { useKeypress, type Key } from '../hooks/useKeypress.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import { FooterRow, type FooterRowItem } from './Footer.js';
import { ALL_ITEMS, resolveFooterState } from '../../config/footerItems.js';
import { SettingScope } from '../../config/settings.js';
import { BaseSelectionList } from './shared/BaseSelectionList.js';
import type { SelectionListItem } from '../hooks/useSelectionList.js';
import { DialogFooter } from './shared/DialogFooter.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';

interface FooterConfigDialogProps {
onClose?: () => void;
Expand Down Expand Up @@ -82,6 +83,7 @@ function footerConfigReducer(
export const FooterConfigDialog: React.FC<FooterConfigDialogProps> = ({
onClose,
}) => {
const keyMatchers = useKeyMatchers();
const { settings, setSetting } = useSettingsStore();
const { constrainHeight, terminalHeight, staticExtraHeight } = useUIState();
const [state, dispatch] = useReducer(footerConfigReducer, undefined, () =>
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/ui/components/HooksDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { useState, useMemo } from 'react';
import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';

/**
* Hook entry type matching HookRegistryEntry from core
Expand Down Expand Up @@ -49,6 +50,7 @@ export const HooksDialog: React.FC<HooksDialogProps> = ({
onClose,
maxVisibleHooks = DEFAULT_MAX_VISIBLE_HOOKS,
}) => {
const keyMatchers = useKeyMatchers();
const [scrollOffset, setScrollOffset] = useState(0);

// Flatten hooks with their event names for easier scrolling
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/ui/components/InputPrompt.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import { terminalCapabilityManager } from '../utils/terminalCapabilityManager.js
import type { UIState } from '../contexts/UIStateContext.js';
import { isLowColorDepth } from '../utils/terminalUtils.js';
import { cpLen } from '../utils/textUtils.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { defaultKeyMatchers, Command } from '../keyMatchers.js';
import type { Key } from '../hooks/useKeypress.js';
import {
appEvents,
Expand Down Expand Up @@ -197,7 +197,7 @@ describe('InputPrompt', () => {
visualCursor: [0, 0],
visualScrollRow: 0,
handleInput: vi.fn((key: Key) => {
if (keyMatchers[Command.CLEAR_INPUT](key)) {
if (defaultKeyMatchers[Command.CLEAR_INPUT](key)) {
if (mockBuffer.text.length > 0) {
mockBuffer.setText('');
return true;
Expand Down
11 changes: 8 additions & 3 deletions packages/cli/src/ui/components/InputPrompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
} from '../hooks/useCommandCompletion.js';
import type { Key } from '../hooks/useKeypress.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import { formatCommand } from '../utils/keybindingUtils.js';
import type { CommandContext, SlashCommand } from '../commands/types.js';
import type { Config } from '@google/gemini-cli-core';
Expand Down Expand Up @@ -72,8 +72,9 @@ import { useMouseClick } from '../hooks/useMouseClick.js';
import { useMouse, type MouseEvent } from '../contexts/MouseContext.js';
import { useUIActions } from '../contexts/UIActionsContext.js';
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
import { shouldDismissShortcutsHelpOnHotkey } from '../utils/shortcutsHelp.js';
import { useIsHelpDismissKey } from '../utils/shortcutsHelp.js';
import { useRepeatedKeyPress } from '../hooks/useRepeatedKeyPress.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';

/**
* Returns if the terminal can be trusted to handle paste events atomically
Expand Down Expand Up @@ -207,6 +208,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
suggestionsPosition = 'below',
setBannerVisible,
}) => {
const isHelpDismissKey = useIsHelpDismissKey();
const keyMatchers = useKeyMatchers();
const { stdout } = useStdout();
const { merged: settings } = useSettings();
const kittyProtocol = useKittyKeyboardProtocol();
Expand Down Expand Up @@ -737,7 +740,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
return true;
}

if (shortcutsHelpVisible && shouldDismissShortcutsHelpOnHotkey(key)) {
if (shortcutsHelpVisible && isHelpDismissKey(key)) {
setShortcutsHelpVisible(false);
}

Expand Down Expand Up @@ -1265,6 +1268,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
shouldShowSuggestions,
isShellSuggestionsVisible,
forceShowShellSuggestions,
keyMatchers,
isHelpDismissKey,
],
);

Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/ui/components/PolicyUpdateDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import { theme } from '../semantic-colors.js';
import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';

export enum PolicyUpdateChoice {
ACCEPT = 'accept',
Expand All @@ -34,6 +35,7 @@ export const PolicyUpdateDialog: React.FC<PolicyUpdateDialogProps> = ({
request,
onClose,
}) => {
const keyMatchers = useKeyMatchers();
const isProcessing = useRef(false);

const handleSelect = useCallback(
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/ui/components/RewindConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
import type { FileChangeStats } from '../utils/rewindFileOps.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { formatTimeAgo } from '../utils/formatters.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';

export enum RewindOutcome {
RewindAndRevert = 'rewind_and_revert',
Expand Down Expand Up @@ -58,6 +59,7 @@ export const RewindConfirmation: React.FC<RewindConfirmationProps> = ({
terminalWidth,
timestamp,
}) => {
const keyMatchers = useKeyMatchers();
const isScreenReaderEnabled = useIsScreenReaderEnabled();
useKeypress(
(key) => {
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/ui/components/RewindViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import { useKeypress } from '../hooks/useKeypress.js';
import { useRewind } from '../hooks/useRewind.js';
import { RewindConfirmation, RewindOutcome } from './RewindConfirmation.js';
import { stripReferenceContent } from '../utils/formatters.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import { CliSpinner } from './CliSpinner.js';
import { ExpandableText } from './shared/ExpandableText.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';

interface RewindViewerProps {
conversation: ConversationRecord;
Expand All @@ -48,6 +49,7 @@ export const RewindViewer: React.FC<RewindViewerProps> = ({
onExit,
onRewind,
}) => {
const keyMatchers = useKeyMatchers();
const [isRewinding, setIsRewinding] = useState(false);
const { terminalWidth, terminalHeight } = useUIState();
const isScreenReaderEnabled = useIsScreenReaderEnabled();
Expand Down
Loading
Loading