Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
88da17d
Add shortcuts panel toggle and ? key behavior
LyalinDotCom Jan 31, 2026
614cc15
Refine shortcuts UI header and highlighting
LyalinDotCom Jan 31, 2026
9409a80
Polish shortcuts layout and header
LyalinDotCom Jan 31, 2026
b92d9e2
Refine shortcuts header and status alignment
LyalinDotCom Jan 31, 2026
419299b
Remove shortcut hint separator
LyalinDotCom Jan 31, 2026
136efd6
Update shortcuts panel with paste images
LyalinDotCom Jan 31, 2026
8c4a091
Relax indicator tests for wrapped output
LyalinDotCom Feb 1, 2026
014846a
Update shortcuts help behavior and docs
LyalinDotCom Feb 1, 2026
e8a8aab
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 2, 2026
b6b4031
Limit shortcuts panel to empty prompt
LyalinDotCom Feb 2, 2026
d925d08
Wrap shortcuts panel text
LyalinDotCom Feb 2, 2026
18a1ad8
Merge branch 'show_help_questionmark' of https://github.com/LyalinDot…
LyalinDotCom Feb 2, 2026
b856560
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 2, 2026
5c713af
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 3, 2026
937bd3c
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 3, 2026
f65e24e
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 3, 2026
90bc491
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 3, 2026
df462ed
Simplify ShortcutsHint and fix test regex patterns
LyalinDotCom Feb 3, 2026
1d30b17
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 4, 2026
b5f67ce
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 4, 2026
a1c0674
Fix composer indicator assertions
LyalinDotCom Feb 4, 2026
be2d593
Merge branch 'show_help_questionmark' of https://github.com/LyalinDot…
LyalinDotCom Feb 5, 2026
636e600
Move loading indicator inline and hide status
LyalinDotCom Feb 5, 2026
7681b19
Fix failing CI tests for LoadingIndicator and Composer
LyalinDotCom Feb 5, 2026
5aa4620
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 5, 2026
78b2e50
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 5, 2026
c98f815
Fix Escape key closing shortcuts panel and cancelling operation simul…
LyalinDotCom Feb 5, 2026
2049906
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 5, 2026
341502b
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 6, 2026
6b1b8f7
Refine shortcuts panel layout and loading status behavior
LyalinDotCom Feb 6, 2026
b26bb9a
Fix shortcuts panel key behavior documentation
LyalinDotCom Feb 6, 2026
83724b1
Merge remote-tracking branch 'origin/show_help_questionmark' into sho…
LyalinDotCom Feb 6, 2026
9d6fff7
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 6, 2026
a7bc6b1
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 6, 2026
085a68d
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 6, 2026
902f286
Merge branch 'main' into show_help_questionmark
LyalinDotCom Feb 6, 2026
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
6 changes: 5 additions & 1 deletion docs/cli/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,14 @@ Slash commands provide meta-level control over the CLI itself.
- **Description:** Lists all active extensions in the current Gemini CLI
session. See [Gemini CLI Extensions](../extensions/index.md).

- **`/help`** (or **`/?`**)
- **`/help`**
- **Description:** Display help information about Gemini CLI, including
available commands and their usage.

- **`/shortcuts`**
- **Description:** Toggle the shortcuts panel above the input.
- **Shortcut:** Press `?` when the prompt is empty.

- **`/hooks`**
- **Description:** Manage hooks, which allow you to intercept and customize
Gemini CLI behavior at specific lifecycle events.
Expand Down
3 changes: 3 additions & 0 deletions docs/cli/keyboard-shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ available combinations.
- `Option+B/F/M` (macOS only): Are interpreted as `Cmd+B/F/M` even if your
terminal isn't configured to send Meta with Option.
- `!` on an empty prompt: Enter or exit shell mode.
- `?` 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
the panel and insert a `?` into the prompt.
- `\` (at end of a line) + `Enter`: Insert a newline without leaving single-line
mode.
- `Esc` pressed twice quickly: Clear the input prompt if it is not empty,
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/services/BuiltinCommandLoader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ vi.mock('../ui/commands/extensionsCommand.js', () => ({
extensionsCommand: () => ({}),
}));
vi.mock('../ui/commands/helpCommand.js', () => ({ helpCommand: {} }));
vi.mock('../ui/commands/shortcutsCommand.js', () => ({
shortcutsCommand: {},
}));
vi.mock('../ui/commands/memoryCommand.js', () => ({ memoryCommand: {} }));
vi.mock('../ui/commands/modelCommand.js', () => ({
modelCommand: { name: 'model' },
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/services/BuiltinCommandLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { directoryCommand } from '../ui/commands/directoryCommand.js';
import { editorCommand } from '../ui/commands/editorCommand.js';
import { extensionsCommand } from '../ui/commands/extensionsCommand.js';
import { helpCommand } from '../ui/commands/helpCommand.js';
import { shortcutsCommand } from '../ui/commands/shortcutsCommand.js';
import { rewindCommand } from '../ui/commands/rewindCommand.js';
import { hooksCommand } from '../ui/commands/hooksCommand.js';
import { ideCommand } from '../ui/commands/ideCommand.js';
Expand Down Expand Up @@ -116,6 +117,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
]
: [extensionsCommand(this.config?.getEnableExtensionReloading())]),
helpCommand,
shortcutsCommand,
...(this.config?.getEnableHooksUI() ? [hooksCommand] : []),
rewindCommand,
await ideCommand(),
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/test-utils/mockCommandContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const createMockCommandContext = (
setPendingItem: vi.fn(),
loadHistory: vi.fn(),
toggleCorgiMode: vi.fn(),
toggleShortcutsHelp: vi.fn(),
toggleVimEnabled: vi.fn(),
openAgentConfigDialog: vi.fn(),
closeAgentConfigDialog: vi.fn(),
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/test-utils/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ const mockUIActions: UIActions = {
handleApiKeySubmit: vi.fn(),
handleApiKeyCancel: vi.fn(),
setBannerVisible: vi.fn(),
setShortcutsHelpVisible: vi.fn(),
setEmbeddedShellFocused: vi.fn(),
dismissBackgroundShell: vi.fn(),
setActiveBackgroundShellPid: vi.fn(),
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/src/ui/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
const setIsBackgroundShellListOpenRef = useRef<(open: boolean) => void>(
() => {},
);
const [shortcutsHelpVisible, setShortcutsHelpVisible] = useState(false);

const slashCommandActions = useMemo(
() => ({
Expand Down Expand Up @@ -795,6 +796,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
}
}
},
toggleShortcutsHelp: () => setShortcutsHelpVisible((visible) => !visible),
setText: stableSetText,
}),
[
Expand All @@ -813,6 +815,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
openPermissionsDialog,
addConfirmUpdateExtensionRequest,
toggleDebugProfiler,
setShortcutsHelpVisible,
stableSetText,
],
);
Expand Down Expand Up @@ -1840,6 +1843,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
ctrlCPressedOnce: ctrlCPressCount >= 1,
ctrlDPressedOnce: ctrlDPressCount >= 1,
showEscapePrompt,
shortcutsHelpVisible,
isFocused,
elapsedTime,
currentLoadingPhrase,
Expand Down Expand Up @@ -1945,6 +1949,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
ctrlCPressCount,
ctrlDPressCount,
showEscapePrompt,
shortcutsHelpVisible,
isFocused,
elapsedTime,
currentLoadingPhrase,
Expand Down Expand Up @@ -2044,6 +2049,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
handleApiKeySubmit,
handleApiKeyCancel,
setBannerVisible,
setShortcutsHelpVisible,
handleWarning,
setEmbeddedShellFocused,
dismissBackgroundShell,
Expand Down Expand Up @@ -2120,6 +2126,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
handleApiKeySubmit,
handleApiKeyCancel,
setBannerVisible,
setShortcutsHelpVisible,
handleWarning,
setEmbeddedShellFocused,
dismissBackgroundShell,
Expand Down
1 change: 0 additions & 1 deletion packages/cli/src/ui/commands/helpCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { MessageType, type HistoryItemHelp } from '../types.js';

export const helpCommand: SlashCommand = {
name: 'help',
altNames: ['?'],
kind: CommandKind.BUILT_IN,
description: 'For help on gemini-cli',
autoExecute: true,
Expand Down
19 changes: 19 additions & 0 deletions packages/cli/src/ui/commands/shortcutsCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import type { SlashCommand } from './types.js';
import { CommandKind } from './types.js';

export const shortcutsCommand: SlashCommand = {
name: 'shortcuts',
altNames: [],
kind: CommandKind.BUILT_IN,
description: 'Toggle the shortcuts panel above the input',
autoExecute: true,
action: (context) => {
context.ui.toggleShortcutsHelp();
},
};
1 change: 1 addition & 0 deletions packages/cli/src/ui/commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export interface CommandContext {
setConfirmationRequest: (value: ConfirmationRequest) => void;
removeComponent: () => void;
toggleBackgroundShell: () => void;
toggleShortcutsHelp: () => void;
};
// Session-specific data
session: {
Expand Down
63 changes: 56 additions & 7 deletions packages/cli/src/ui/components/Composer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ vi.mock('../contexts/VimModeContext.js', () => ({
})),
}));
import { ApprovalMode } from '@google/gemini-cli-core';
import { StreamingState } from '../types.js';
import { StreamingState, ToolCallStatus } from '../types.js';

// Mock child components
vi.mock('./LoadingIndicator.js', () => ({
Expand All @@ -49,6 +49,14 @@ vi.mock('./ShellModeIndicator.js', () => ({
ShellModeIndicator: () => <Text>ShellModeIndicator</Text>,
}));

vi.mock('./ShortcutsHint.js', () => ({
ShortcutsHint: () => <Text>ShortcutsHint</Text>,
}));

vi.mock('./ShortcutsHelp.js', () => ({
ShortcutsHelp: () => <Text>ShortcutsHelp</Text>,
}));

vi.mock('./DetailedMessagesDisplay.js', () => ({
DetailedMessagesDisplay: () => <Text>DetailedMessagesDisplay</Text>,
}));
Expand Down Expand Up @@ -95,7 +103,8 @@ vi.mock('../contexts/OverflowContext.js', () => ({
// Create mock context providers
const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
({
streamingState: null,
streamingState: StreamingState.Idle,
isConfigInitialized: true,
contextFileNames: [],
showApprovalModeIndicator: ApprovalMode.DEFAULT,
messageQueue: [],
Expand All @@ -116,6 +125,7 @@ const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
ctrlCPressedOnce: false,
ctrlDPressedOnce: false,
showEscapePrompt: false,
shortcutsHelpVisible: false,
ideContextState: null,
geminiMdFileCount: 0,
renderMarkdown: true,
Expand Down Expand Up @@ -268,6 +278,19 @@ describe('Composer', () => {
expect(output).toContain('LoadingIndicator');
});

it('keeps shortcuts hint visible while loading', () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
elapsedTime: 1,
});

const { lastFrame } = renderComposer(uiState);

const output = lastFrame();
expect(output).toContain('LoadingIndicator');
expect(output).toContain('ShortcutsHint');
});

it('renders LoadingIndicator without thought when accessibility disables loading phrases', () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
Expand All @@ -284,7 +307,7 @@ describe('Composer', () => {
expect(output).not.toContain('Should not show');
});

it('suppresses thought when waiting for confirmation', () => {
it('does not render LoadingIndicator when waiting for confirmation', () => {
const uiState = createMockUIState({
streamingState: StreamingState.WaitingForConfirmation,
thought: {
Expand All @@ -296,8 +319,34 @@ describe('Composer', () => {
const { lastFrame } = renderComposer(uiState);

const output = lastFrame();
expect(output).toContain('LoadingIndicator');
expect(output).not.toContain('Should not show during confirmation');
expect(output).not.toContain('LoadingIndicator');
});

it('does not render LoadingIndicator when a tool confirmation is pending', () => {
const uiState = createMockUIState({
streamingState: StreamingState.Responding,
pendingHistoryItems: [
{
type: 'tool_group',
tools: [
{
callId: 'call-1',
name: 'edit',
description: 'edit file',
status: ToolCallStatus.Confirming,
resultDisplay: undefined,
confirmationDetails: undefined,
},
],
},
],
});

const { lastFrame } = renderComposer(uiState);

const output = lastFrame();
expect(output).not.toContain('LoadingIndicator');
expect(output).not.toContain('esc to cancel');
});

it('renders LoadingIndicator when embedded shell is focused but background shell is visible', () => {
Expand Down Expand Up @@ -444,7 +493,7 @@ describe('Composer', () => {

const { lastFrame } = renderComposer(uiState);

expect(lastFrame()).toContain('ApprovalModeIndicator');
expect(lastFrame()).toMatch(/ApprovalModeIndic[\s\S]*ator/);
});

it('shows ShellModeIndicator when shell mode is active', () => {
Expand All @@ -454,7 +503,7 @@ describe('Composer', () => {

const { lastFrame } = renderComposer(uiState);

expect(lastFrame()).toContain('ShellModeIndicator');
expect(lastFrame()).toMatch(/ShellModeIndic[\s\S]*tor/);
});

it('shows RawMarkdownIndicator when renderMarkdown is false', () => {
Expand Down
Loading
Loading