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
168 changes: 84 additions & 84 deletions docs/reference/keyboard-shortcuts.md

Large diffs are not rendered by default.

31 changes: 13 additions & 18 deletions packages/cli/src/ui/components/ApprovalModeIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,14 @@ import type React from 'react';
import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { ApprovalMode } from '@google/gemini-cli-core';
import { formatCommand } from '../utils/keybindingUtils.js';
import { Command } from '../../config/keyBindings.js';

interface ApprovalModeIndicatorProps {
approvalMode: ApprovalMode;
allowPlanMode?: boolean;
}

export const APPROVAL_MODE_TEXT = {
AUTO_EDIT: 'auto-accept edits',
PLAN: 'plan',
YOLO: 'YOLO',
HINT_SWITCH_TO_PLAN_MODE: 'shift+tab to plan',
HINT_SWITCH_TO_MANUAL_MODE: 'shift+tab to manual',
HINT_SWITCH_TO_AUTO_EDIT_MODE: 'shift+tab to accept edits',
HINT_SWITCH_TO_YOLO_MODE: 'ctrl+y',
};

export const ApprovalModeIndicator: React.FC<ApprovalModeIndicatorProps> = ({
approvalMode,
allowPlanMode,
Expand All @@ -32,29 +24,32 @@ export const ApprovalModeIndicator: React.FC<ApprovalModeIndicatorProps> = ({
let textContent = '';
let subText = '';

const cycleHint = formatCommand(Command.CYCLE_APPROVAL_MODE);
const yoloHint = formatCommand(Command.TOGGLE_YOLO);

switch (approvalMode) {
case ApprovalMode.AUTO_EDIT:
textColor = theme.status.warning;
textContent = APPROVAL_MODE_TEXT.AUTO_EDIT;
textContent = 'auto-accept edits';
subText = allowPlanMode
? APPROVAL_MODE_TEXT.HINT_SWITCH_TO_PLAN_MODE
: APPROVAL_MODE_TEXT.HINT_SWITCH_TO_MANUAL_MODE;
? `${cycleHint} to plan`
: `${cycleHint} to manual`;
break;
case ApprovalMode.PLAN:
textColor = theme.status.success;
textContent = APPROVAL_MODE_TEXT.PLAN;
subText = APPROVAL_MODE_TEXT.HINT_SWITCH_TO_MANUAL_MODE;
textContent = 'plan';
subText = `${cycleHint} to manual`;
break;
case ApprovalMode.YOLO:
textColor = theme.status.error;
textContent = APPROVAL_MODE_TEXT.YOLO;
subText = APPROVAL_MODE_TEXT.HINT_SWITCH_TO_YOLO_MODE;
textContent = 'YOLO';
subText = yoloHint;
break;
case ApprovalMode.DEFAULT:
default:
textColor = theme.text.accent;
textContent = '';
subText = APPROVAL_MODE_TEXT.HINT_SWITCH_TO_AUTO_EDIT_MODE;
subText = `${cycleHint} to accept edits`;
break;
}

Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/ui/components/AskUserDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useKeypress, type Key } from '../hooks/useKeypress.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import { checkExhaustive } from '@google/gemini-cli-core';
import { TextInput } from './shared/TextInput.js';
import { formatCommand } from '../utils/keybindingUtils.js';
import { useTextBuffer } from './shared/text-buffer.js';
import { getCachedStringWidth } from '../utils/textUtils.js';
import { useTabbedNavigation } from '../hooks/useTabbedNavigation.js';
Expand Down Expand Up @@ -252,7 +253,7 @@ const ReviewView: React.FC<ReviewViewProps> = ({
</Box>
<DialogFooter
primaryAction="Enter to submit"
navigationActions="Tab/Shift+Tab to edit answers"
navigationActions={`${formatCommand(Command.DIALOG_NEXT)}/${formatCommand(Command.DIALOG_PREV)} to edit answers`}
extraParts={extraParts}
/>
</Box>
Expand Down Expand Up @@ -1146,7 +1147,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
navigationActions={
questions.length > 1
? currentQuestion.type === 'text' || isEditingCustomOption
? 'Tab/Shift+Tab to switch questions'
? `${formatCommand(Command.DIALOG_NEXT)}/${formatCommand(Command.DIALOG_PREV)} to switch questions`
: '←/→ to switch questions'
: currentQuestion.type === 'text' || isEditingCustomOption
? undefined
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/ui/components/Help.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('Help Component', () => {
expect(output).toContain('Keyboard Shortcuts:');
expect(output).toContain('Ctrl+C');
expect(output).toContain('Ctrl+S');
expect(output).toContain('Page Up/Down');
expect(output).toContain('Page Up/Page Down');
unmount();
});
});
32 changes: 17 additions & 15 deletions packages/cli/src/ui/components/Help.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { theme } from '../semantic-colors.js';
import { type SlashCommand, CommandKind } from '../commands/types.js';
import { KEYBOARD_SHORTCUTS_URL } from '../constants.js';
import { sanitizeForDisplay } from '../utils/textUtils.js';
import { formatCommand } from '../utils/keybindingUtils.js';
import { Command } from '../../config/keyBindings.js';

interface Help {
commands: readonly SlashCommand[];
Expand Down Expand Up @@ -116,75 +118,75 @@ export const Help: React.FC<Help> = ({ commands }) => (
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
Alt+Left/Right
{formatCommand(Command.MOVE_WORD_LEFT)}/
{formatCommand(Command.MOVE_WORD_RIGHT)}
</Text>{' '}
- Jump through words in the input
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
Ctrl+C
{formatCommand(Command.QUIT)}
</Text>{' '}
- Quit application
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
{process.platform === 'win32' ? 'Ctrl+Enter' : 'Ctrl+J'}
{formatCommand(Command.NEWLINE)}
</Text>{' '}
{process.platform === 'linux'
? '- New line (Alt+Enter works for certain linux distros)'
: '- New line'}
- New line
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
Ctrl+L
{formatCommand(Command.CLEAR_SCREEN)}
</Text>{' '}
- Clear the screen
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
Ctrl+S
{formatCommand(Command.TOGGLE_COPY_MODE)}
</Text>{' '}
- Enter selection mode to copy text
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
Ctrl+X
{formatCommand(Command.OPEN_EXTERNAL_EDITOR)}
</Text>{' '}
- Open input in external editor
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
Ctrl+Y
{formatCommand(Command.TOGGLE_YOLO)}
</Text>{' '}
- Toggle YOLO mode
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
Enter
{formatCommand(Command.SUBMIT)}
</Text>{' '}
- Send message
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
Esc
{formatCommand(Command.ESCAPE)}
</Text>{' '}
- Cancel operation / Clear input (double press)
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
Page Up/Down
{formatCommand(Command.PAGE_UP)}/{formatCommand(Command.PAGE_DOWN)}
</Text>{' '}
- Scroll page up/down
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
Shift+Tab
{formatCommand(Command.CYCLE_APPROVAL_MODE)}
</Text>{' '}
- Toggle auto-accepting edits
</Text>
<Text color={theme.text.primary}>
<Text bold color={theme.text.accent}>
Up/Down
{formatCommand(Command.HISTORY_UP)}/
{formatCommand(Command.HISTORY_DOWN)}
</Text>{' '}
- Cycle through your prompt history
</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@

import { render } from '../../test-utils/render.js';
import { RawMarkdownIndicator } from './RawMarkdownIndicator.js';
import { describe, it, expect, afterEach } from 'vitest';
import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';

describe('RawMarkdownIndicator', () => {
const originalPlatform = process.platform;

beforeEach(() => vi.stubEnv('FORCE_GENERIC_KEYBINDING_HINTS', ''));

afterEach(() => {
Object.defineProperty(process, 'platform', {
value: originalPlatform,
});
vi.unstubAllEnvs();
});

it('renders correct key binding for darwin', async () => {
Expand All @@ -26,7 +29,7 @@ describe('RawMarkdownIndicator', () => {
);
await waitUntilReady();
expect(lastFrame()).toContain('raw markdown mode');
expect(lastFrame()).toContain('option+m to toggle');
expect(lastFrame()).toContain('Option+M to toggle');
unmount();
});

Expand All @@ -39,7 +42,7 @@ describe('RawMarkdownIndicator', () => {
);
await waitUntilReady();
expect(lastFrame()).toContain('raw markdown mode');
expect(lastFrame()).toContain('alt+m to toggle');
expect(lastFrame()).toContain('Alt+M to toggle');
unmount();
});
});
4 changes: 3 additions & 1 deletion packages/cli/src/ui/components/RawMarkdownIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import type React from 'react';
import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { formatCommand } from '../utils/keybindingUtils.js';
import { Command } from '../../config/keyBindings.js';

export const RawMarkdownIndicator: React.FC = () => {
const modKey = process.platform === 'darwin' ? 'option+m' : 'alt+m';
const modKey = formatCommand(Command.TOGGLE_MARKDOWN);
return (
<Box>
<Text>
Expand Down
9 changes: 6 additions & 3 deletions packages/cli/src/ui/components/ShortcutsHelp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, it, expect, afterEach, vi } from 'vitest';
import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';
import { renderWithProviders } from '../../test-utils/render.js';
import { ShortcutsHelp } from './ShortcutsHelp.js';

describe('ShortcutsHelp', () => {
const originalPlatform = process.platform;

beforeEach(() => vi.stubEnv('FORCE_GENERIC_KEYBINDING_HINTS', ''));

afterEach(() => {
Object.defineProperty(process, 'platform', {
value: originalPlatform,
});
vi.unstubAllEnvs();
vi.restoreAllMocks();
});

Expand Down Expand Up @@ -52,10 +55,10 @@ describe('ShortcutsHelp', () => {
},
);

it('always shows Tab Tab focus UI shortcut', async () => {
it('always shows Tab focus UI shortcut', async () => {
const rendered = renderWithProviders(<ShortcutsHelp />);
await rendered.waitUntilReady();
expect(rendered.lastFrame()).toContain('Tab Tab');
expect(rendered.lastFrame()).toContain('Tab focus UI');
rendered.unmount();
});
});
46 changes: 29 additions & 17 deletions packages/cli/src/ui/components/ShortcutsHelp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,41 @@ import { theme } from '../semantic-colors.js';
import { isNarrowWidth } from '../utils/isNarrowWidth.js';
import { SectionHeader } from './shared/SectionHeader.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { Command } from '../../config/keyBindings.js';
import { formatCommand } from '../utils/keybindingUtils.js';

type ShortcutItem = {
key: string;
description: string;
};

const buildShortcutItems = (): ShortcutItem[] => {
const isMac = process.platform === 'darwin';
const altLabel = isMac ? 'Option' : 'Alt';

return [
{ key: '!', description: 'shell mode' },
{ key: '@', description: 'select file or folder' },
{ key: 'Esc Esc', description: 'clear & rewind' },
{ key: 'Tab Tab', description: 'focus UI' },
{ key: 'Ctrl+Y', description: 'YOLO mode' },
{ key: 'Shift+Tab', description: 'cycle mode' },
{ key: 'Ctrl+V', description: 'paste images' },
{ key: `${altLabel}+M`, description: 'raw markdown mode' },
{ key: 'Ctrl+R', description: 'reverse-search history' },
{ key: 'Ctrl+X', description: 'open external editor' },
];
};
const buildShortcutItems = (): ShortcutItem[] => [
{ key: '!', description: 'shell mode' },
{ key: '@', description: 'select file or folder' },
{ key: formatCommand(Command.REWIND), description: 'clear & rewind' },
{ key: formatCommand(Command.FOCUS_SHELL_INPUT), description: 'focus UI' },
{ key: formatCommand(Command.TOGGLE_YOLO), description: 'YOLO mode' },
{
key: formatCommand(Command.CYCLE_APPROVAL_MODE),
description: 'cycle mode',
},
{
key: formatCommand(Command.PASTE_CLIPBOARD),
description: 'paste images',
},
{
key: formatCommand(Command.TOGGLE_MARKDOWN),
description: 'raw markdown mode',
},
{
key: formatCommand(Command.REVERSE_SEARCH),
description: 'reverse-search history',
},
{
key: formatCommand(Command.OPEN_EXTERNAL_EDITOR),
description: 'open external editor',
},
];

const Shortcut: React.FC<{ item: ShortcutItem }> = ({ item }) => (
<Box flexDirection="row">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`ApprovalModeIndicator > renders correctly for AUTO_EDIT mode 1`] = `
"auto-accept edits shift+tab to manual
"auto-accept edits Shift+Tab to manual
"
`;

exports[`ApprovalModeIndicator > renders correctly for AUTO_EDIT mode with plan enabled 1`] = `
"auto-accept edits shift+tab to plan
"auto-accept edits Shift+Tab to plan
"
`;

exports[`ApprovalModeIndicator > renders correctly for DEFAULT mode 1`] = `
"shift+tab to accept edits
"Shift+Tab to accept edits
"
`;

exports[`ApprovalModeIndicator > renders correctly for DEFAULT mode with plan enabled 1`] = `
"shift+tab to accept edits
"Shift+Tab to accept edits
"
`;

exports[`ApprovalModeIndicator > renders correctly for PLAN mode 1`] = `
"plan shift+tab to manual
"plan Shift+Tab to manual
"
`;

exports[`ApprovalModeIndicator > renders correctly for YOLO mode 1`] = `
"YOLO ctrl+y
"YOLO Ctrl+Y
"
`;
Loading
Loading