From 7a02d7261aa5486ac051e86638b5768a486038b6 Mon Sep 17 00:00:00 2001 From: Dmitry Lyalin Date: Tue, 10 Feb 2026 21:46:20 -0500 Subject: [PATCH] feat(cli): add setting to hide shortcuts hint UI (#18562) --- docs/cli/keyboard-shortcuts.md | 3 ++- docs/cli/settings.md | 1 + docs/get-started/configuration.md | 4 +++ .../cli/src/config/settingsSchema.test.ts | 25 +++++++++++++++++++ packages/cli/src/config/settingsSchema.ts | 9 +++++++ .../cli/src/ui/components/Composer.test.tsx | 13 ++++++++++ packages/cli/src/ui/components/Composer.tsx | 3 ++- .../src/ui/components/InputPrompt.test.tsx | 24 ++++++++++++++++++ schemas/settings.schema.json | 7 ++++++ 9 files changed, 87 insertions(+), 2 deletions(-) diff --git a/docs/cli/keyboard-shortcuts.md b/docs/cli/keyboard-shortcuts.md index ce5990a906..d377cfd3e2 100644 --- a/docs/cli/keyboard-shortcuts.md +++ b/docs/cli/keyboard-shortcuts.md @@ -131,7 +131,8 @@ available combinations. - `!` 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. + the panel and insert a `?` into the prompt. You can hide only the hint text + via `ui.showShortcutsHint`, without changing this keyboard behavior. - `\` (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, diff --git a/docs/cli/settings.md b/docs/cli/settings.md index e7fe0dd1ee..70523c6fd1 100644 --- a/docs/cli/settings.md +++ b/docs/cli/settings.md @@ -49,6 +49,7 @@ they appear in the UI. | Dynamic Window Title | `ui.dynamicWindowTitle` | Update the terminal window title with current status icons (Ready: ◇, Action Required: ✋, Working: ✦) | `true` | | Show Home Directory Warning | `ui.showHomeDirectoryWarning` | Show a warning when running Gemini CLI in the home directory. | `true` | | Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `false` | +| Show Shortcuts Hint | `ui.showShortcutsHint` | Show the "? for shortcuts" hint above the input. | `true` | | Hide Banner | `ui.hideBanner` | Hide the application banner | `false` | | Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` | | Hide CWD | `ui.footer.hideCWD` | Hide the current working directory path in the footer. | `false` | diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index eba48e8d74..619dbf6869 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -220,6 +220,10 @@ their corresponding top-level category object in your `settings.json` file. - **Description:** Hide helpful tips in the UI - **Default:** `false` +- **`ui.showShortcutsHint`** (boolean): + - **Description:** Show the "? for shortcuts" hint above the input. + - **Default:** `true` + - **`ui.hideBanner`** (boolean): - **Description:** Hide the application banner - **Default:** `false` diff --git a/packages/cli/src/config/settingsSchema.test.ts b/packages/cli/src/config/settingsSchema.test.ts index d83ac705f7..bc558e77b8 100644 --- a/packages/cli/src/config/settingsSchema.test.ts +++ b/packages/cli/src/config/settingsSchema.test.ts @@ -186,6 +186,9 @@ describe('SettingsSchema', () => { expect(getSettingsSchema().ui.properties.hideTips.showInDialog).toBe( true, ); + expect( + getSettingsSchema().ui.properties.showShortcutsHint.showInDialog, + ).toBe(true); expect(getSettingsSchema().ui.properties.hideBanner.showInDialog).toBe( true, ); @@ -328,6 +331,28 @@ describe('SettingsSchema', () => { ).toBe('Enable debug logging of keystrokes to the console.'); }); + it('should have showShortcutsHint setting in schema', () => { + expect(getSettingsSchema().ui.properties.showShortcutsHint).toBeDefined(); + expect(getSettingsSchema().ui.properties.showShortcutsHint.type).toBe( + 'boolean', + ); + expect(getSettingsSchema().ui.properties.showShortcutsHint.category).toBe( + 'UI', + ); + expect(getSettingsSchema().ui.properties.showShortcutsHint.default).toBe( + true, + ); + expect( + getSettingsSchema().ui.properties.showShortcutsHint.requiresRestart, + ).toBe(false); + expect( + getSettingsSchema().ui.properties.showShortcutsHint.showInDialog, + ).toBe(true); + expect( + getSettingsSchema().ui.properties.showShortcutsHint.description, + ).toBe('Show the "? for shortcuts" hint above the input.'); + }); + it('should have enableAgents setting in schema', () => { const setting = getSettingsSchema().experimental.properties.enableAgents; expect(setting).toBeDefined(); diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index cc9e12f06f..d530ec4a53 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -462,6 +462,15 @@ const SETTINGS_SCHEMA = { description: 'Hide helpful tips in the UI', showInDialog: true, }, + showShortcutsHint: { + type: 'boolean', + label: 'Show Shortcuts Hint', + category: 'UI', + requiresRestart: false, + default: true, + description: 'Show the "? for shortcuts" hint above the input.', + showInDialog: true, + }, hideBanner: { type: 'boolean', label: 'Hide Banner', diff --git a/packages/cli/src/ui/components/Composer.test.tsx b/packages/cli/src/ui/components/Composer.test.tsx index da7b866391..ee3a441c04 100644 --- a/packages/cli/src/ui/components/Composer.test.tsx +++ b/packages/cli/src/ui/components/Composer.test.tsx @@ -650,6 +650,19 @@ describe('Composer', () => { }); describe('Shortcuts Hint', () => { + it('hides shortcuts hint when showShortcutsHint setting is false', () => { + const uiState = createMockUIState(); + const settings = createMockSettings({ + ui: { + showShortcutsHint: false, + }, + }); + + const { lastFrame } = renderComposer(uiState, settings); + + expect(lastFrame()).not.toContain('ShortcutsHint'); + }); + it('hides shortcuts hint when a action is required (e.g. dialog is open)', () => { const uiState = createMockUIState({ customDialog: ( diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx index 84001056a8..e87e86e801 100644 --- a/packages/cli/src/ui/components/Composer.tsx +++ b/packages/cli/src/ui/components/Composer.tsx @@ -133,7 +133,8 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { flexDirection="column" alignItems={isNarrow ? 'flex-start' : 'flex-end'} > - {!hasPendingActionRequired && } + {settings.merged.ui.showShortcutsHint && + !hasPendingActionRequired && } {uiState.shortcutsHelpVisible && } diff --git a/packages/cli/src/ui/components/InputPrompt.test.tsx b/packages/cli/src/ui/components/InputPrompt.test.tsx index 0903c0b066..0446f67381 100644 --- a/packages/cli/src/ui/components/InputPrompt.test.tsx +++ b/packages/cli/src/ui/components/InputPrompt.test.tsx @@ -4296,6 +4296,30 @@ describe('InputPrompt', () => { }); describe('shortcuts help visibility', () => { + it('opens shortcuts help with ? on empty prompt even when showShortcutsHint is false', async () => { + const setShortcutsHelpVisible = vi.fn(); + const settings = createMockSettings({ + ui: { showShortcutsHint: false }, + }); + + const { stdin, unmount } = renderWithProviders( + , + { + settings, + uiActions: { setShortcutsHelpVisible }, + }, + ); + + await act(async () => { + stdin.write('?'); + }); + + await waitFor(() => { + expect(setShortcutsHelpVisible).toHaveBeenCalledWith(true); + }); + unmount(); + }); + it.each([ { name: 'terminal paste event occurs', diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index d5146d46f8..9072077c2c 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -245,6 +245,13 @@ "default": false, "type": "boolean" }, + "showShortcutsHint": { + "title": "Show Shortcuts Hint", + "description": "Show the \"? for shortcuts\" hint above the input.", + "markdownDescription": "Show the \"? for shortcuts\" hint above the input.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `true`", + "default": true, + "type": "boolean" + }, "hideBanner": { "title": "Hide Banner", "description": "Hide the application banner",