mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-02-01 14:44:29 +00:00
feat(ui): use Tab to switch focus between shell and input (#14332)
This commit is contained in:
@@ -91,17 +91,18 @@ available combinations.
|
||||
|
||||
#### App Controls
|
||||
|
||||
| Action | Keys |
|
||||
| ----------------------------------------------------------------- | ------------- |
|
||||
| Toggle detailed error information. | `F12` |
|
||||
| Toggle the full TODO list. | `Ctrl + T` |
|
||||
| Toggle IDE context details. | `Ctrl + G` |
|
||||
| Toggle Markdown rendering. | `Cmd + M` |
|
||||
| Toggle copy mode when the terminal is using the alternate buffer. | `Ctrl + S` |
|
||||
| Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl + Y` |
|
||||
| Toggle Auto Edit (auto-accept edits) mode. | `Shift + Tab` |
|
||||
| Expand a height-constrained response to show additional lines. | `Ctrl + S` |
|
||||
| Toggle focus between the shell and Gemini input. | `Ctrl + F` |
|
||||
| Action | Keys |
|
||||
| ----------------------------------------------------------------- | ----------------------------------- |
|
||||
| Toggle detailed error information. | `F12` |
|
||||
| Toggle the full TODO list. | `Ctrl + T` |
|
||||
| Toggle IDE context details. | `Ctrl + G` |
|
||||
| Toggle Markdown rendering. | `Cmd + M` |
|
||||
| Toggle copy mode when the terminal is using the alternate buffer. | `Ctrl + S` |
|
||||
| Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl + Y` |
|
||||
| Toggle Auto Edit (auto-accept edits) mode. | `Shift + Tab` |
|
||||
| Expand a height-constrained response to show additional lines. | `Ctrl + S` |
|
||||
| Toggle focus between the shell and Gemini input. | `Tab (no Shift)` |
|
||||
| Toggle focus out of the interactive shell and into Gemini input. | `Tab (no Shift)`<br />`Shift + Tab` |
|
||||
|
||||
#### Session Control
|
||||
|
||||
@@ -122,8 +123,7 @@ available combinations.
|
||||
- `Ctrl+Delete` / `Meta+Delete`: Delete the word to the right of the cursor.
|
||||
- `Ctrl+B` or `Left Arrow`: Move the cursor one character to the left while
|
||||
editing text.
|
||||
- `Ctrl+F` or `Right Arrow`: Move the cursor one character to the right; with an
|
||||
embedded shell attached, `Ctrl+F` still toggles focus.
|
||||
- `Ctrl+F` or `Right Arrow`: Move the cursor one character to the right.
|
||||
- `Ctrl+D` or `Delete`: Remove the character immediately to the right of the
|
||||
cursor.
|
||||
- `Ctrl+H` or `Backspace`: Remove the character immediately to the left of the
|
||||
|
||||
@@ -135,7 +135,7 @@ user input, such as text editors (`vim`, `nano`), terminal-based UIs (`htop`),
|
||||
and interactive version control operations (`git rebase -i`).
|
||||
|
||||
When an interactive command is running, you can send input to it from the Gemini
|
||||
CLI. To focus on the interactive shell, press `ctrl+f`. The terminal output,
|
||||
CLI. To focus on the interactive shell, press `Tab`. The terminal output,
|
||||
including complex TUIs, will be rendered correctly.
|
||||
|
||||
## Important notes
|
||||
|
||||
@@ -72,7 +72,8 @@ export enum Command {
|
||||
REVERSE_SEARCH = 'reverseSearch',
|
||||
SUBMIT_REVERSE_SEARCH = 'submitReverseSearch',
|
||||
ACCEPT_SUGGESTION_REVERSE_SEARCH = 'acceptSuggestionReverseSearch',
|
||||
TOGGLE_SHELL_INPUT_FOCUS = 'toggleShellInputFocus',
|
||||
TOGGLE_SHELL_INPUT_FOCUS_IN = 'toggleShellInputFocus',
|
||||
TOGGLE_SHELL_INPUT_FOCUS_OUT = 'toggleShellInputFocusOut',
|
||||
|
||||
// Suggestion expansion
|
||||
EXPAND_SUGGESTION = 'expandSuggestion',
|
||||
@@ -216,8 +217,11 @@ export const defaultKeyBindings: KeyBindingConfig = {
|
||||
// Note: original logic ONLY checked ctrl=false, ignored meta/shift/paste
|
||||
[Command.SUBMIT_REVERSE_SEARCH]: [{ key: 'return', ctrl: false }],
|
||||
[Command.ACCEPT_SUGGESTION_REVERSE_SEARCH]: [{ key: 'tab' }],
|
||||
[Command.TOGGLE_SHELL_INPUT_FOCUS]: [{ key: 'f', ctrl: true }],
|
||||
|
||||
[Command.TOGGLE_SHELL_INPUT_FOCUS_IN]: [{ key: 'tab', shift: false }],
|
||||
[Command.TOGGLE_SHELL_INPUT_FOCUS_OUT]: [
|
||||
{ key: 'tab', shift: false },
|
||||
{ key: 'tab', shift: true },
|
||||
],
|
||||
// Suggestion expansion
|
||||
[Command.EXPAND_SUGGESTION]: [{ key: 'right' }],
|
||||
[Command.COLLAPSE_SUGGESTION]: [{ key: 'left' }],
|
||||
@@ -312,7 +316,8 @@ export const commandCategories: readonly CommandCategory[] = [
|
||||
Command.TOGGLE_YOLO,
|
||||
Command.TOGGLE_AUTO_EDIT,
|
||||
Command.SHOW_MORE_LINES,
|
||||
Command.TOGGLE_SHELL_INPUT_FOCUS,
|
||||
Command.TOGGLE_SHELL_INPUT_FOCUS_IN,
|
||||
Command.TOGGLE_SHELL_INPUT_FOCUS_OUT,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -370,8 +375,10 @@ export const commandDescriptions: Readonly<Record<Command, string>> = {
|
||||
[Command.SUBMIT_REVERSE_SEARCH]: 'Insert the selected reverse-search match.',
|
||||
[Command.ACCEPT_SUGGESTION_REVERSE_SEARCH]:
|
||||
'Accept a suggestion while reverse searching.',
|
||||
[Command.TOGGLE_SHELL_INPUT_FOCUS]:
|
||||
[Command.TOGGLE_SHELL_INPUT_FOCUS_IN]:
|
||||
'Toggle focus between the shell and Gemini input.',
|
||||
[Command.TOGGLE_SHELL_INPUT_FOCUS_OUT]:
|
||||
'Toggle focus out of the interactive shell and into Gemini input.',
|
||||
[Command.EXPAND_SUGGESTION]: 'Expand an inline suggestion.',
|
||||
[Command.COLLAPSE_SUGGESTION]: 'Collapse an inline suggestion.',
|
||||
};
|
||||
|
||||
@@ -823,11 +823,17 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
embeddedShellFocused,
|
||||
);
|
||||
|
||||
const lastOutputTimeRef = useRef(0);
|
||||
useEffect(() => {
|
||||
lastOutputTimeRef.current = lastOutputTime;
|
||||
}, [lastOutputTime]);
|
||||
|
||||
// Auto-accept indicator
|
||||
const showAutoAcceptIndicator = useAutoAcceptIndicator({
|
||||
config,
|
||||
addItem: historyManager.addItem,
|
||||
onApprovalModeChange: handleApprovalModeChange,
|
||||
isActive: !embeddedShellFocused,
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -1053,19 +1059,20 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
|
||||
useIncludeDirsTrust(config, isTrustedFolder, historyManager, setCustomDialog);
|
||||
|
||||
const warningTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const tabFocusTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const handleWarning = useCallback((message: string) => {
|
||||
setWarningMessage(message);
|
||||
if (warningTimeoutRef.current) {
|
||||
clearTimeout(warningTimeoutRef.current);
|
||||
}
|
||||
warningTimeoutRef.current = setTimeout(() => {
|
||||
setWarningMessage(null);
|
||||
}, WARNING_PROMPT_DURATION_MS);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
|
||||
const handleWarning = (message: string) => {
|
||||
setWarningMessage(message);
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = setTimeout(() => {
|
||||
setWarningMessage(null);
|
||||
}, WARNING_PROMPT_DURATION_MS);
|
||||
};
|
||||
|
||||
const handleSelectionWarning = () => {
|
||||
handleWarning('Press Ctrl-S to enter selection mode to copy text.');
|
||||
};
|
||||
@@ -1077,11 +1084,14 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
return () => {
|
||||
appEvents.off(AppEvent.SelectionWarning, handleSelectionWarning);
|
||||
appEvents.off(AppEvent.PasteTimeout, handlePasteTimeout);
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
if (warningTimeoutRef.current) {
|
||||
clearTimeout(warningTimeoutRef.current);
|
||||
}
|
||||
if (tabFocusTimeoutRef.current) {
|
||||
clearTimeout(tabFocusTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, [handleWarning]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ideNeedsRestart) {
|
||||
@@ -1269,10 +1279,37 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
!enteringConstrainHeightMode
|
||||
) {
|
||||
setConstrainHeight(false);
|
||||
} else if (keyMatchers[Command.TOGGLE_SHELL_INPUT_FOCUS](key)) {
|
||||
if (activePtyId || embeddedShellFocused) {
|
||||
setEmbeddedShellFocused((prev) => !prev);
|
||||
} else if (
|
||||
keyMatchers[Command.TOGGLE_SHELL_INPUT_FOCUS_OUT](key) &&
|
||||
activePtyId &&
|
||||
embeddedShellFocused
|
||||
) {
|
||||
if (key.name === 'tab' && key.shift) {
|
||||
// Always change focus
|
||||
setEmbeddedShellFocused(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
// If the shell hasn't produced output in the last 100ms, it's considered idle.
|
||||
const isIdle = now - lastOutputTimeRef.current >= 100;
|
||||
if (isIdle) {
|
||||
if (tabFocusTimeoutRef.current) {
|
||||
clearTimeout(tabFocusTimeoutRef.current);
|
||||
}
|
||||
tabFocusTimeoutRef.current = setTimeout(() => {
|
||||
tabFocusTimeoutRef.current = null;
|
||||
// If the shell produced output since the tab press, we assume it handled the tab
|
||||
// (e.g. autocomplete) so we should not toggle focus.
|
||||
if (lastOutputTimeRef.current > now) {
|
||||
handleWarning('Press Shift+Tab to focus out.');
|
||||
return;
|
||||
}
|
||||
setEmbeddedShellFocused(false);
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
handleWarning('Press Shift+Tab to focus out.');
|
||||
}
|
||||
},
|
||||
[
|
||||
@@ -1293,6 +1330,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
setCopyModeEnabled,
|
||||
copyModeEnabled,
|
||||
isAlternateBuffer,
|
||||
handleWarning,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -2418,6 +2418,84 @@ describe('InputPrompt', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tab focus toggle', () => {
|
||||
it.each([
|
||||
{
|
||||
name: 'should toggle focus in on Tab when no suggestions or ghost text',
|
||||
showSuggestions: false,
|
||||
ghostText: '',
|
||||
suggestions: [],
|
||||
expectedFocusToggle: true,
|
||||
},
|
||||
{
|
||||
name: 'should accept ghost text and NOT toggle focus on Tab',
|
||||
showSuggestions: false,
|
||||
ghostText: 'ghost text',
|
||||
suggestions: [],
|
||||
expectedFocusToggle: false,
|
||||
expectedAcceptCall: true,
|
||||
},
|
||||
{
|
||||
name: 'should NOT toggle focus on Tab when suggestions are present',
|
||||
showSuggestions: true,
|
||||
ghostText: '',
|
||||
suggestions: [{ label: 'test', value: 'test' }],
|
||||
expectedFocusToggle: false,
|
||||
},
|
||||
])(
|
||||
'$name',
|
||||
async ({
|
||||
showSuggestions,
|
||||
ghostText,
|
||||
suggestions,
|
||||
expectedFocusToggle,
|
||||
expectedAcceptCall,
|
||||
}) => {
|
||||
const mockAccept = vi.fn();
|
||||
mockedUseCommandCompletion.mockReturnValue({
|
||||
...mockCommandCompletion,
|
||||
showSuggestions,
|
||||
suggestions,
|
||||
promptCompletion: {
|
||||
text: ghostText,
|
||||
accept: mockAccept,
|
||||
clear: vi.fn(),
|
||||
isLoading: false,
|
||||
isActive: ghostText !== '',
|
||||
markSelected: vi.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
const { stdin, unmount } = renderWithProviders(
|
||||
<InputPrompt {...props} />,
|
||||
{
|
||||
uiActions,
|
||||
uiState: { activePtyId: 1 },
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\t');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
if (expectedFocusToggle) {
|
||||
expect(uiActions.setEmbeddedShellFocused).toHaveBeenCalledWith(
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
expect(uiActions.setEmbeddedShellFocused).not.toHaveBeenCalled();
|
||||
}
|
||||
|
||||
if (expectedAcceptCall) {
|
||||
expect(mockAccept).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
unmount();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('mouse interaction', () => {
|
||||
it.each([
|
||||
{
|
||||
|
||||
@@ -135,7 +135,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
const kittyProtocol = useKittyKeyboardProtocol();
|
||||
const isShellFocused = useShellFocusState();
|
||||
const { setEmbeddedShellFocused } = useUIActions();
|
||||
const { mainAreaWidth } = useUIState();
|
||||
const { mainAreaWidth, activePtyId } = useUIState();
|
||||
const [justNavigatedHistory, setJustNavigatedHistory] = useState(false);
|
||||
const escPressCount = useRef(0);
|
||||
const [showEscapePrompt, setShowEscapePrompt] = useState(false);
|
||||
@@ -829,6 +829,14 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyMatchers[Command.TOGGLE_SHELL_INPUT_FOCUS_IN](key)) {
|
||||
// If we got here, Autocomplete didn't handle the key (e.g. no suggestions).
|
||||
if (activePtyId) {
|
||||
setEmbeddedShellFocused(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fall back to the text buffer's default input handling for all other keys
|
||||
buffer.handleInput(key);
|
||||
|
||||
@@ -870,6 +878,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
kittyProtocol.enabled,
|
||||
tryLoadQueuedMessages,
|
||||
setBannerVisible,
|
||||
activePtyId,
|
||||
setEmbeddedShellFocused,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
|
||||
{shouldShowFocusHint && (
|
||||
<Box marginLeft={1} flexShrink={0}>
|
||||
<Text color={theme.text.accent}>
|
||||
{isThisShellFocused ? '(Focused)' : '(ctrl+f to focus)'}
|
||||
{isThisShellFocused ? '(Focused)' : '(tab to focus)'}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -112,7 +112,7 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
|
||||
{shouldShowFocusHint && (
|
||||
<Box marginLeft={1} flexShrink={0}>
|
||||
<Text color={theme.text.accent}>
|
||||
{isThisShellFocused ? '(Focused)' : '(ctrl+f to focus)'}
|
||||
{isThisShellFocused ? '(Focused)' : '(tab to focus)'}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -15,12 +15,14 @@ export interface UseAutoAcceptIndicatorArgs {
|
||||
config: Config;
|
||||
addItem?: (item: HistoryItemWithoutId, timestamp: number) => void;
|
||||
onApprovalModeChange?: (mode: ApprovalMode) => void;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export function useAutoAcceptIndicator({
|
||||
config,
|
||||
addItem,
|
||||
onApprovalModeChange,
|
||||
isActive = true,
|
||||
}: UseAutoAcceptIndicatorArgs): ApprovalMode {
|
||||
const currentConfigValue = config.getApprovalMode();
|
||||
const [showAutoAcceptIndicator, setShowAutoAcceptIndicator] =
|
||||
@@ -82,7 +84,7 @@ export function useAutoAcceptIndicator({
|
||||
}
|
||||
}
|
||||
},
|
||||
{ isActive: true },
|
||||
{ isActive },
|
||||
);
|
||||
|
||||
return showAutoAcceptIndicator;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useInactivityTimer } from './useInactivityTimer.js';
|
||||
|
||||
export const PHRASE_CHANGE_INTERVAL_MS = 15000;
|
||||
export const INTERACTIVE_SHELL_WAITING_PHRASE =
|
||||
'Interactive shell awaiting input... press Ctrl+f to focus shell';
|
||||
'Interactive shell awaiting input... press tab to focus shell';
|
||||
|
||||
/**
|
||||
* Custom hook to manage cycling through loading phrases.
|
||||
|
||||
@@ -22,67 +22,6 @@ describe('keyMatchers', () => {
|
||||
...mods,
|
||||
});
|
||||
|
||||
// Original hard-coded logic (for comparison)
|
||||
const originalMatchers: Record<Command, (key: Key) => boolean> = {
|
||||
[Command.RETURN]: (key: Key) => key.name === 'return',
|
||||
[Command.HOME]: (key: Key) => key.ctrl && key.name === 'a',
|
||||
[Command.END]: (key: Key) => key.ctrl && key.name === 'e',
|
||||
[Command.KILL_LINE_RIGHT]: (key: Key) => key.ctrl && key.name === 'k',
|
||||
[Command.KILL_LINE_LEFT]: (key: Key) => key.ctrl && key.name === 'u',
|
||||
[Command.CLEAR_INPUT]: (key: Key) => key.ctrl && key.name === 'c',
|
||||
[Command.DELETE_WORD_BACKWARD]: (key: Key) =>
|
||||
(key.ctrl || key.meta) && key.name === 'backspace',
|
||||
[Command.CLEAR_SCREEN]: (key: Key) => key.ctrl && key.name === 'l',
|
||||
[Command.SCROLL_UP]: (key: Key) => key.name === 'up' && !!key.shift,
|
||||
[Command.SCROLL_DOWN]: (key: Key) => key.name === 'down' && !!key.shift,
|
||||
[Command.SCROLL_HOME]: (key: Key) => key.name === 'home',
|
||||
[Command.SCROLL_END]: (key: Key) => key.name === 'end',
|
||||
[Command.PAGE_UP]: (key: Key) => key.name === 'pageup',
|
||||
[Command.PAGE_DOWN]: (key: Key) => key.name === 'pagedown',
|
||||
[Command.HISTORY_UP]: (key: Key) => key.ctrl && key.name === 'p',
|
||||
[Command.HISTORY_DOWN]: (key: Key) => key.ctrl && key.name === 'n',
|
||||
[Command.NAVIGATION_UP]: (key: Key) => key.name === 'up',
|
||||
[Command.NAVIGATION_DOWN]: (key: Key) => key.name === 'down',
|
||||
[Command.DIALOG_NAVIGATION_UP]: (key: Key) =>
|
||||
!key.shift && (key.name === 'up' || key.name === 'k'),
|
||||
[Command.DIALOG_NAVIGATION_DOWN]: (key: Key) =>
|
||||
!key.shift && (key.name === 'down' || key.name === 'j'),
|
||||
[Command.ACCEPT_SUGGESTION]: (key: Key) =>
|
||||
key.name === 'tab' || (key.name === 'return' && !key.ctrl),
|
||||
[Command.COMPLETION_UP]: (key: Key) =>
|
||||
key.name === 'up' || (key.ctrl && key.name === 'p'),
|
||||
[Command.COMPLETION_DOWN]: (key: Key) =>
|
||||
key.name === 'down' || (key.ctrl && key.name === 'n'),
|
||||
[Command.ESCAPE]: (key: Key) => key.name === 'escape',
|
||||
[Command.SUBMIT]: (key: Key) =>
|
||||
key.name === 'return' && !key.ctrl && !key.meta && !key.paste,
|
||||
[Command.NEWLINE]: (key: Key) =>
|
||||
key.name === 'return' && (key.ctrl || key.meta || key.paste),
|
||||
[Command.OPEN_EXTERNAL_EDITOR]: (key: Key) =>
|
||||
key.ctrl && (key.name === 'x' || key.sequence === '\x18'),
|
||||
[Command.PASTE_CLIPBOARD]: (key: Key) => key.ctrl && key.name === 'v',
|
||||
[Command.SHOW_ERROR_DETAILS]: (key: Key) => key.name === 'f12',
|
||||
[Command.SHOW_FULL_TODOS]: (key: Key) => key.ctrl && key.name === 't',
|
||||
[Command.TOGGLE_IDE_CONTEXT_DETAIL]: (key: Key) =>
|
||||
key.ctrl && key.name === 'g',
|
||||
[Command.TOGGLE_MARKDOWN]: (key: Key) => key.meta && key.name === 'm',
|
||||
[Command.TOGGLE_COPY_MODE]: (key: Key) => key.ctrl && key.name === 's',
|
||||
[Command.QUIT]: (key: Key) => key.ctrl && key.name === 'c',
|
||||
[Command.EXIT]: (key: Key) => key.ctrl && key.name === 'd',
|
||||
[Command.SHOW_MORE_LINES]: (key: Key) => key.ctrl && key.name === 's',
|
||||
[Command.REVERSE_SEARCH]: (key: Key) => key.ctrl && key.name === 'r',
|
||||
[Command.SUBMIT_REVERSE_SEARCH]: (key: Key) =>
|
||||
key.name === 'return' && !key.ctrl,
|
||||
[Command.ACCEPT_SUGGESTION_REVERSE_SEARCH]: (key: Key) =>
|
||||
key.name === 'tab',
|
||||
[Command.TOGGLE_SHELL_INPUT_FOCUS]: (key: Key) =>
|
||||
key.ctrl && key.name === 'f',
|
||||
[Command.TOGGLE_YOLO]: (key: Key) => key.ctrl && key.name === 'y',
|
||||
[Command.TOGGLE_AUTO_EDIT]: (key: Key) => key.shift && key.name === 'tab',
|
||||
[Command.EXPAND_SUGGESTION]: (key: Key) => key.name === 'right',
|
||||
[Command.COLLAPSE_SUGGESTION]: (key: Key) => key.name === 'left',
|
||||
};
|
||||
|
||||
// Test data for each command with positive and negative test cases
|
||||
const testCases = [
|
||||
// Basic bindings
|
||||
@@ -334,9 +273,9 @@ describe('keyMatchers', () => {
|
||||
negative: [createKey('return'), createKey('space')],
|
||||
},
|
||||
{
|
||||
command: Command.TOGGLE_SHELL_INPUT_FOCUS,
|
||||
positive: [createKey('f', { ctrl: true })],
|
||||
negative: [createKey('f')],
|
||||
command: Command.TOGGLE_SHELL_INPUT_FOCUS_IN,
|
||||
positive: [createKey('tab')],
|
||||
negative: [createKey('f', { ctrl: true }), createKey('f')],
|
||||
},
|
||||
{
|
||||
command: Command.TOGGLE_YOLO,
|
||||
@@ -358,10 +297,6 @@ describe('keyMatchers', () => {
|
||||
keyMatchers[command](key),
|
||||
`Expected ${command} to match ${JSON.stringify(key)}`,
|
||||
).toBe(true);
|
||||
expect(
|
||||
originalMatchers[command](key),
|
||||
`Original matcher should also match ${JSON.stringify(key)}`,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
negative.forEach((key) => {
|
||||
@@ -369,10 +304,6 @@ describe('keyMatchers', () => {
|
||||
keyMatchers[command](key),
|
||||
`Expected ${command} to NOT match ${JSON.stringify(key)}`,
|
||||
).toBe(false);
|
||||
expect(
|
||||
originalMatchers[command](key),
|
||||
`Original matcher should also NOT match ${JSON.stringify(key)}`,
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user