refactor: migrate to useKeyMatchers hook (#21753)

This commit is contained in:
Tommaso Sciortino
2026-03-09 20:48:09 +00:00
committed by GitHub
parent e406dcc249
commit ab64b15d51
34 changed files with 162 additions and 54 deletions
@@ -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(
@@ -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';
@@ -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;
@@ -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;
@@ -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
@@ -325,7 +328,7 @@ const TextQuestionView: React.FC<TextQuestionViewProps> = ({
}
return false;
},
[buffer, textValue],
[buffer, textValue, keyMatchers],
);
useKeypress(handleExtraKeys, { isActive: true, priority: true });
@@ -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);
@@ -680,6 +684,7 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
customBuffer,
onEditingCustomOption,
customOptionText,
keyMatchers,
],
);
@@ -950,6 +955,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
availableHeight: availableHeightProp,
extraParts,
}) => {
const keyMatchers = useKeyMatchers();
const uiState = useContext(UIStateContext);
const availableHeight =
availableHeightProp ??
@@ -999,7 +1005,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
}
return false;
},
[onCancel, submitted, isEditingCustomOption],
[onCancel, submitted, isEditingCustomOption, keyMatchers],
);
useKeypress(handleCancel, {
@@ -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, {
@@ -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 {
@@ -30,6 +30,7 @@ import {
RadioButtonSelect,
type RadioSelectItem,
} from './shared/RadioButtonSelect.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
interface BackgroundShellDisplayProps {
shells: Map<number, BackgroundShell>;
@@ -60,6 +61,7 @@ export const BackgroundShellDisplay = ({
isFocused,
isListOpenProp,
}: BackgroundShellDisplayProps) => {
const keyMatchers = useKeyMatchers();
const {
dismissBackgroundShell,
setActiveBackgroundShellPid,
@@ -10,7 +10,7 @@ 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,
@@ -18,6 +18,7 @@ import {
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(),
@@ -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)) {
@@ -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;
@@ -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);
@@ -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;
@@ -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, () =>
@@ -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
@@ -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
@@ -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,
@@ -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;
@@ -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';
@@ -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
@@ -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();
@@ -737,7 +740,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
return true;
}
if (shortcutsHelpVisible && shouldDismissShortcutsHelpOnHotkey(key)) {
if (shortcutsHelpVisible && isHelpDismissKey(key)) {
setShortcutsHelpVisible(false);
}
@@ -1265,6 +1268,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
shouldShowSuggestions,
isShellSuggestionsVisible,
forceShowShellSuggestions,
keyMatchers,
isHelpDismissKey,
],
);
@@ -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',
@@ -34,6 +35,7 @@ export const PolicyUpdateDialog: React.FC<PolicyUpdateDialogProps> = ({
request,
onClose,
}) => {
const keyMatchers = useKeyMatchers();
const isProcessing = useRef(false);
const handleSelect = useCallback(
@@ -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',
@@ -58,6 +59,7 @@ export const RewindConfirmation: React.FC<RewindConfirmationProps> = ({
terminalWidth,
timestamp,
}) => {
const keyMatchers = useKeyMatchers();
const isScreenReaderEnabled = useIsScreenReaderEnabled();
useKeypress(
(key) => {
@@ -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;
@@ -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();
@@ -10,7 +10,8 @@ import { useKeypress } from '../hooks/useKeypress.js';
import { ShellExecutionService } from '@google/gemini-cli-core';
import { keyToAnsi, type Key } from '../hooks/keyToAnsi.js';
import { ACTIVE_SHELL_MAX_LINES } from '../constants.js';
import { Command, keyMatchers } from '../keyMatchers.js';
import { Command } from '../keyMatchers.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
export interface ShellInputPromptProps {
activeShellPtyId: number | null;
@@ -23,6 +24,7 @@ export const ShellInputPrompt: React.FC<ShellInputPromptProps> = ({
focus = true,
scrollPageSize = ACTIVE_SHELL_MAX_LINES,
}) => {
const keyMatchers = useKeyMatchers();
const handleShellInputSubmit = useCallback(
(input: string) => {
if (activeShellPtyId) {
@@ -73,7 +75,13 @@ export const ShellInputPrompt: React.FC<ShellInputPromptProps> = ({
return false;
},
[focus, handleShellInputSubmit, activeShellPtyId, scrollPageSize],
[
focus,
handleShellInputSubmit,
activeShellPtyId,
scrollPageSize,
keyMatchers,
],
);
useKeypress(handleInput, { isActive: focus });
@@ -16,7 +16,8 @@ import {
type ValidationIntent,
} 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 ValidationDialogProps {
validationLink?: string;
@@ -32,6 +33,7 @@ export function ValidationDialog({
learnMoreUrl,
onChoice,
}: ValidationDialogProps): React.JSX.Element {
const keyMatchers = useKeyMatchers();
const [state, setState] = useState<DialogState>('choosing');
const [errorMessage, setErrorMessage] = useState<string>('');
@@ -29,7 +29,7 @@ import {
import { useKeypress } from '../../hooks/useKeypress.js';
import { theme } from '../../semantic-colors.js';
import { useSettings } from '../../contexts/SettingsContext.js';
import { keyMatchers, Command } from '../../keyMatchers.js';
import { Command } from '../../keyMatchers.js';
import { formatCommand } from '../../utils/keybindingUtils.js';
import { AskUserDialog } from '../AskUserDialog.js';
import { ExitPlanModeDialog } from '../ExitPlanModeDialog.js';
@@ -40,6 +40,7 @@ import {
toUnicodeUrl,
type DeceptiveUrlDetails,
} from '../../utils/urlSecurityUtils.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
export interface ToolConfirmationMessageProps {
callId: string;
@@ -67,6 +68,7 @@ export const ToolConfirmationMessage: React.FC<
availableTerminalHeight,
terminalWidth,
}) => {
const keyMatchers = useKeyMatchers();
const { confirm, isDiffingEnabled } = useToolActions();
const [mcpDetailsExpansionState, setMcpDetailsExpansionState] = useState<{
callId: string;
@@ -19,10 +19,11 @@ import { TextInput } from './TextInput.js';
import type { TextBuffer } from './text-buffer.js';
import { cpSlice, cpLen, cpIndexToOffset } from '../../utils/textUtils.js';
import { useKeypress, type Key } from '../../hooks/useKeypress.js';
import { keyMatchers, Command } from '../../keyMatchers.js';
import { Command } from '../../keyMatchers.js';
import { useSettingsNavigation } from '../../hooks/useSettingsNavigation.js';
import { useInlineEditBuffer } from '../../hooks/useInlineEditBuffer.js';
import { formatCommand } from '../../utils/keybindingUtils.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
/**
* Represents a single item in the settings dialog.
@@ -136,6 +137,7 @@ export function BaseSettingsDialog({
availableHeight,
footer,
}: BaseSettingsDialogProps): React.JSX.Element {
const keyMatchers = useKeyMatchers();
// Calculate effective max items and scope visibility based on terminal height
const { effectiveMaxItemsToShow, finalShowScopeSelector } = useMemo(() => {
const initialShowScope = showScopeSelector;
@@ -19,8 +19,9 @@ import { useKeypress, type Key } from '../../hooks/useKeypress.js';
import { useScrollable } from '../../contexts/ScrollProvider.js';
import { useAnimatedScrollbar } from '../../hooks/useAnimatedScrollbar.js';
import { useBatchedScroll } from '../../hooks/useBatchedScroll.js';
import { keyMatchers, Command } from '../../keyMatchers.js';
import { Command } from '../../keyMatchers.js';
import { useOverflowActions } from '../../contexts/OverflowContext.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
interface ScrollableProps {
children?: React.ReactNode;
@@ -45,6 +46,7 @@ export const Scrollable: React.FC<ScrollableProps> = ({
flexGrow,
reportOverflow = false,
}) => {
const keyMatchers = useKeyMatchers();
const [scrollTop, setScrollTop] = useState(0);
const viewportRef = useRef<DOMElement | null>(null);
const contentRef = useRef<DOMElement | null>(null);
@@ -22,7 +22,8 @@ import { useScrollable } from '../../contexts/ScrollProvider.js';
import { Box, type DOMElement } from 'ink';
import { useAnimatedScrollbar } from '../../hooks/useAnimatedScrollbar.js';
import { useKeypress, type Key } from '../../hooks/useKeypress.js';
import { keyMatchers, Command } from '../../keyMatchers.js';
import { Command } from '../../keyMatchers.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
const ANIMATION_FRAME_DURATION_MS = 33;
@@ -46,6 +47,7 @@ function ScrollableList<T>(
props: ScrollableListProps<T>,
ref: React.Ref<ScrollableListRef<T>>,
) {
const keyMatchers = useKeyMatchers();
const { hasFocus, width } = props;
const virtualizedListRef = useRef<VirtualizedListRef<T>>(null);
const containerRef = useRef<DOMElement>(null);
@@ -11,7 +11,8 @@ import { useSelectionList } from '../../hooks/useSelectionList.js';
import { TextInput } from './TextInput.js';
import type { TextBuffer } from './text-buffer.js';
import { useKeypress } from '../../hooks/useKeypress.js';
import { keyMatchers, Command } from '../../keyMatchers.js';
import { Command } from '../../keyMatchers.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
/**
* Generic interface for items in a searchable list.
@@ -85,6 +86,7 @@ export function SearchableList<T extends GenericListItem>({
onSearch,
resetSelectionOnItemsChange = false,
}: SearchableListProps<T>): React.JSX.Element {
const keyMatchers = useKeyMatchers();
const { filteredItems, searchBuffer, maxLabelWidth } = useSearch({
items,
onSearch,
@@ -14,7 +14,8 @@ import { theme } from '../../semantic-colors.js';
import type { TextBuffer } from './text-buffer.js';
import { expandPastePlaceholders } from './text-buffer.js';
import { cpSlice, cpIndexToOffset } from '../../utils/textUtils.js';
import { keyMatchers, Command } from '../../keyMatchers.js';
import { Command } from '../../keyMatchers.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
export interface TextInputProps {
buffer: TextBuffer;
@@ -31,6 +32,7 @@ export function TextInput({
onCancel,
focus = true,
}: TextInputProps): React.JSX.Element {
const keyMatchers = useKeyMatchers();
const {
text,
handleInput,
@@ -55,7 +57,7 @@ export function TextInput({
const handled = handleInput(key);
return handled;
},
[handleInput, onCancel, onSubmit, text, buffer.pastedContent],
[handleInput, onCancel, onSubmit, text, buffer.pastedContent, keyMatchers],
);
useKeypress(handleKeyPress, { isActive: focus, priority: true });
@@ -25,11 +25,12 @@ import {
} from '../../utils/textUtils.js';
import { parsePastedPaths } from '../../utils/clipboardUtils.js';
import type { Key } from '../../contexts/KeypressContext.js';
import { keyMatchers, Command } from '../../keyMatchers.js';
import { Command } from '../../keyMatchers.js';
import type { VimAction } from './vim-buffer-actions.js';
import { handleVimAction } from './vim-buffer-actions.js';
import { LRU_BUFFER_PERF_CACHE_LIMIT } from '../../constants.js';
import { openFileInEditor } from '../../utils/editorUtils.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
export const LARGE_PASTE_LINE_THRESHOLD = 5;
export const LARGE_PASTE_CHAR_THRESHOLD = 500;
@@ -2708,6 +2709,7 @@ export function useTextBuffer({
singleLine = false,
getPreferredEditor,
}: UseTextBufferProps): TextBuffer {
const keyMatchers = useKeyMatchers();
const initialState = useMemo((): TextBufferState => {
const lines = initialText.split('\n');
const [initialCursorRow, initialCursorCol] = calculateInitialCursorPosition(
@@ -3270,6 +3272,7 @@ export function useTextBuffer({
text,
visualCursor,
visualLines,
keyMatchers,
],
);
@@ -10,7 +10,8 @@ import Spinner from 'ink-spinner';
import type { Config } from '@google/gemini-cli-core';
import { debugLogger, spawnAsync, LlmRole } 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 Issue {
number: number;
@@ -106,6 +107,7 @@ export const TriageDuplicates = ({
onExit: () => void;
initialLimit?: number;
}) => {
const keyMatchers = useKeyMatchers();
const [state, setState] = useState<TriageState>({
status: 'loading',
issues: [],
@@ -10,9 +10,10 @@ import Spinner from 'ink-spinner';
import type { Config } from '@google/gemini-cli-core';
import { debugLogger, spawnAsync, LlmRole } from '@google/gemini-cli-core';
import { useKeypress } from '../../hooks/useKeypress.js';
import { keyMatchers, Command } from '../../keyMatchers.js';
import { Command } from '../../keyMatchers.js';
import { TextInput } from '../shared/TextInput.js';
import { useTextBuffer } from '../shared/text-buffer.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
interface Issue {
number: number;
@@ -69,6 +70,7 @@ export const TriageIssues = ({
initialLimit?: number;
until?: string;
}) => {
const keyMatchers = useKeyMatchers();
const [state, setState] = useState<TriageState>({
status: 'loading',
issues: [],