mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-14 22:02:59 -07:00
feat(cli): full drawer collapse during tool approval and compact context summary
This commit is contained in:
@@ -62,7 +62,7 @@ they appear in the UI.
|
|||||||
| Hide Model Info | `ui.footer.hideModelInfo` | Hide the model name and context usage in the footer. | `false` |
|
| Hide Model Info | `ui.footer.hideModelInfo` | Hide the model name and context usage in the footer. | `false` |
|
||||||
| Hide Context Window Percentage | `ui.footer.hideContextPercentage` | Hides the context window remaining percentage. | `true` |
|
| Hide Context Window Percentage | `ui.footer.hideContextPercentage` | Hides the context window remaining percentage. | `true` |
|
||||||
| Hide Footer | `ui.hideFooter` | Hide the footer from the UI | `false` |
|
| Hide Footer | `ui.hideFooter` | Hide the footer from the UI | `false` |
|
||||||
| Hide Footer During Approval | `ui.hideFooterDuringApproval` | Hide the footer when a tool approval request is displayed. | `true` |
|
| Collapse Drawer During Approval | `ui.collapseDrawerDuringApproval` | Collapse the entire drawer (status, context, input, footer) when a tool approval request is displayed. | `true` |
|
||||||
| New Footer Layout | `ui.newFooterLayout` | Use the new 2-row layout with inline tips. | `"legacy"` |
|
| New Footer Layout | `ui.newFooterLayout` | Use the new 2-row layout with inline tips. | `"legacy"` |
|
||||||
| Show Tips | `ui.showTips` | Show informative tips on the right side of the status line. | `true` |
|
| Show Tips | `ui.showTips` | Show informative tips on the right side of the status line. | `true` |
|
||||||
| Show Witty Phrases | `ui.showWit` | Show witty phrases while waiting. | `true` |
|
| Show Witty Phrases | `ui.showWit` | Show witty phrases while waiting. | `true` |
|
||||||
|
|||||||
@@ -275,8 +275,9 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
- **Description:** Hide the footer from the UI
|
- **Description:** Hide the footer from the UI
|
||||||
- **Default:** `false`
|
- **Default:** `false`
|
||||||
|
|
||||||
- **`ui.hideFooterDuringApproval`** (boolean):
|
- **`ui.collapseDrawerDuringApproval`** (boolean):
|
||||||
- **Description:** Hide the footer when a tool approval request is displayed.
|
- **Description:** Collapse the entire drawer (status, context, input, footer)
|
||||||
|
when a tool approval request is displayed.
|
||||||
- **Default:** `true`
|
- **Default:** `true`
|
||||||
|
|
||||||
- **`ui.newFooterLayout`** (enum):
|
- **`ui.newFooterLayout`** (enum):
|
||||||
|
|||||||
@@ -619,14 +619,14 @@ const SETTINGS_SCHEMA = {
|
|||||||
description: 'Hide the footer from the UI',
|
description: 'Hide the footer from the UI',
|
||||||
showInDialog: true,
|
showInDialog: true,
|
||||||
},
|
},
|
||||||
hideFooterDuringApproval: {
|
collapseDrawerDuringApproval: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
label: 'Hide Footer During Approval',
|
label: 'Collapse Drawer During Approval',
|
||||||
category: 'UI',
|
category: 'UI',
|
||||||
requiresRestart: false,
|
requiresRestart: false,
|
||||||
default: true,
|
default: true,
|
||||||
description:
|
description:
|
||||||
'Hide the footer when a tool approval request is displayed.',
|
'Collapse the entire drawer (status, context, input, footer) when a tool approval request is displayed.',
|
||||||
showInDialog: true,
|
showInDialog: true,
|
||||||
},
|
},
|
||||||
newFooterLayout: {
|
newFooterLayout: {
|
||||||
|
|||||||
@@ -264,6 +264,9 @@ export class AppRig {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
hasSeenNudge: true,
|
hasSeenNudge: true,
|
||||||
},
|
},
|
||||||
|
ui: {
|
||||||
|
collapseDrawerDuringApproval: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -457,9 +457,8 @@ describe('Composer', () => {
|
|||||||
|
|
||||||
const { lastFrame } = await renderComposer(uiState);
|
const { lastFrame } = await renderComposer(uiState);
|
||||||
|
|
||||||
const output = lastFrame();
|
const output = lastFrame({ allowEmpty: true });
|
||||||
expect(output).not.toContain('LoadingIndicator');
|
expect(output).toBe('');
|
||||||
expect(output).not.toContain('esc to cancel');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders LoadingIndicator when embedded shell is focused but background shell is visible', async () => {
|
it('renders LoadingIndicator when embedded shell is focused but background shell is visible', async () => {
|
||||||
@@ -712,9 +711,7 @@ describe('Composer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { lastFrame } = await renderComposer(uiState);
|
const { lastFrame } = await renderComposer(uiState);
|
||||||
const output = lastFrame();
|
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||||
expect(output).not.toContain('plan');
|
|
||||||
expect(output).not.toContain('ShortcutsHint');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows Esc rewind prompt in minimal mode without showing full UI', async () => {
|
it('shows Esc rewind prompt in minimal mode without showing full UI', async () => {
|
||||||
@@ -867,9 +864,10 @@ describe('Composer', () => {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { lastFrame } = await renderComposer(uiState);
|
const { lastFrame, unmount } = await renderComposer(uiState);
|
||||||
|
|
||||||
expect(lastFrame()).not.toContain('ShortcutsHint');
|
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||||
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps shortcuts hint visible when no action is required', async () => {
|
it('keeps shortcuts hint visible when no action is required', async () => {
|
||||||
@@ -1003,24 +1001,22 @@ describe('Composer', () => {
|
|||||||
expect(lastFrame()).not.toContain('ShortcutsHelp');
|
expect(lastFrame()).not.toContain('ShortcutsHelp');
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hides shortcuts help when action is required', async () => {
|
it('hides shortcuts help when action is required', async () => {
|
||||||
const uiState = createMockUIState({
|
const uiState = createMockUIState({
|
||||||
shortcutsHelpVisible: true,
|
shortcutsHelpVisible: true,
|
||||||
customDialog: (
|
customDialog: (
|
||||||
<Box>
|
<Box>
|
||||||
<Text>Dialog content</Text>
|
<Text>Test Dialog</Text>
|
||||||
</Box>
|
</Box>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { lastFrame, unmount } = await renderComposer(uiState);
|
const { lastFrame, unmount } = await renderComposer(uiState);
|
||||||
|
|
||||||
expect(lastFrame()).not.toContain('ShortcutsHelp');
|
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Snapshots', () => {
|
describe('Snapshots', () => {
|
||||||
it('matches snapshot in idle state', async () => {
|
it('matches snapshot in idle state', async () => {
|
||||||
const uiState = createMockUIState();
|
const uiState = createMockUIState();
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
|||||||
Boolean(uiState.quota.proQuotaRequest) ||
|
Boolean(uiState.quota.proQuotaRequest) ||
|
||||||
Boolean(uiState.quota.validationRequest) ||
|
Boolean(uiState.quota.validationRequest) ||
|
||||||
Boolean(uiState.customDialog);
|
Boolean(uiState.customDialog);
|
||||||
|
|
||||||
const isPassiveShortcutsHelpState =
|
const isPassiveShortcutsHelpState =
|
||||||
uiState.isInputActive &&
|
uiState.isInputActive &&
|
||||||
uiState.streamingState === StreamingState.Idle &&
|
uiState.streamingState === StreamingState.Idle &&
|
||||||
@@ -180,6 +181,13 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
|||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
}, [canShowShortcutsHint]);
|
}, [canShowShortcutsHint]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
hasPendingActionRequired &&
|
||||||
|
settings.merged.ui.collapseDrawerDuringApproval
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const showShortcutsHint =
|
const showShortcutsHint =
|
||||||
settings.merged.ui.showShortcutsHint &&
|
settings.merged.ui.showShortcutsHint &&
|
||||||
!hideShortcutsHintForSuggestions &&
|
!hideShortcutsHintForSuggestions &&
|
||||||
@@ -751,8 +759,6 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
|||||||
|
|
||||||
{showUiDetails &&
|
{showUiDetails &&
|
||||||
!settings.merged.ui.hideFooter &&
|
!settings.merged.ui.hideFooter &&
|
||||||
(!hasPendingActionRequired ||
|
|
||||||
!settings.merged.ui.hideFooterDuringApproval) &&
|
|
||||||
!isScreenReaderEnabled && <Footer />}
|
!isScreenReaderEnabled && <Footer />}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -78,32 +78,6 @@ describe('<ContextSummaryDisplay />', () => {
|
|||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should switch layout at the 80-column breakpoint', async () => {
|
|
||||||
const props = {
|
|
||||||
...baseProps,
|
|
||||||
geminiMdFileCount: 1,
|
|
||||||
contextFileNames: ['GEMINI.md'],
|
|
||||||
mcpServers: { 'test-server': { command: 'test' } },
|
|
||||||
ideContext: {
|
|
||||||
workspaceState: {
|
|
||||||
openFiles: [{ path: '/a/b/c', timestamp: Date.now() }],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// At 80 columns, should be on one line
|
|
||||||
const { lastFrame: wideFrame, unmount: unmountWide } =
|
|
||||||
await renderWithWidth(80, props);
|
|
||||||
expect(wideFrame().trim().includes('\n')).toBe(false);
|
|
||||||
unmountWide();
|
|
||||||
|
|
||||||
// At 79 columns, should be on multiple lines
|
|
||||||
const { lastFrame: narrowFrame, unmount: unmountNarrow } =
|
|
||||||
await renderWithWidth(79, props);
|
|
||||||
expect(narrowFrame().trim().includes('\n')).toBe(true);
|
|
||||||
expect(narrowFrame().trim().split('\n').length).toBe(4);
|
|
||||||
unmountNarrow();
|
|
||||||
});
|
|
||||||
it('should not render empty parts', async () => {
|
it('should not render empty parts', async () => {
|
||||||
const props = {
|
const props = {
|
||||||
...baseProps,
|
...baseProps,
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import type React from 'react';
|
|||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { theme } from '../semantic-colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
import { type IdeContext, type MCPServerConfig } from '@google/gemini-cli-core';
|
import { type IdeContext, type MCPServerConfig } from '@google/gemini-cli-core';
|
||||||
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
|
||||||
import { isNarrowWidth } from '../utils/isNarrowWidth.js';
|
|
||||||
|
|
||||||
interface ContextSummaryDisplayProps {
|
interface ContextSummaryDisplayProps {
|
||||||
geminiMdFileCount: number;
|
geminiMdFileCount: number;
|
||||||
@@ -30,8 +28,6 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
|
|||||||
skillCount,
|
skillCount,
|
||||||
backgroundProcessCount = 0,
|
backgroundProcessCount = 0,
|
||||||
}) => {
|
}) => {
|
||||||
const { columns: terminalWidth } = useTerminalSize();
|
|
||||||
const isNarrow = isNarrowWidth(terminalWidth);
|
|
||||||
const mcpServerCount = Object.keys(mcpServers || {}).length;
|
const mcpServerCount = Object.keys(mcpServers || {}).length;
|
||||||
const blockedMcpServerCount = blockedMcpServers?.length || 0;
|
const blockedMcpServerCount = blockedMcpServers?.length || 0;
|
||||||
const openFileCount = ideContext?.workspaceState?.openFiles?.length ?? 0;
|
const openFileCount = ideContext?.workspaceState?.openFiles?.length ?? 0;
|
||||||
@@ -113,21 +109,14 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
|
|||||||
backgroundText,
|
backgroundText,
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
if (isNarrow) {
|
|
||||||
return (
|
|
||||||
<Box flexDirection="column" paddingX={1}>
|
|
||||||
{summaryParts.map((part, index) => (
|
|
||||||
<Text key={index} color={theme.text.secondary}>
|
|
||||||
- {part}
|
|
||||||
</Text>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box paddingX={1}>
|
<Box paddingX={1} flexDirection="row" flexWrap="wrap">
|
||||||
<Text color={theme.text.secondary}>{summaryParts.join(' | ')}</Text>
|
{summaryParts.map((part, index) => (
|
||||||
|
<Box key={index} flexDirection="row">
|
||||||
|
{index > 0 && <Text color={theme.text.secondary}>{' · '}</Text>}
|
||||||
|
<Text color={theme.text.secondary}>{part}</Text>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`<ContextSummaryDisplay /> > should not render empty parts 1`] = `
|
exports[`<ContextSummaryDisplay /> > should not render empty parts 1`] = `
|
||||||
" - 1 open file (ctrl+g to view)
|
" 1 open file (ctrl+g to view)
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<ContextSummaryDisplay /> > should render on a single line on a wide screen 1`] = `
|
exports[`<ContextSummaryDisplay /> > should render on a single line on a wide screen 1`] = `
|
||||||
" 1 open file (ctrl+g to view) | 1 GEMINI.md file | 1 MCP server | 1 skill
|
" 1 open file (ctrl+g to view) · 1 GEMINI.md file · 1 MCP server · 1 skill
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<ContextSummaryDisplay /> > should render on multiple lines on a narrow screen 1`] = `
|
exports[`<ContextSummaryDisplay /> > should render on multiple lines on a narrow screen 1`] = `
|
||||||
" - 1 open file (ctrl+g to view)
|
" 1 open file (ctrl+g to view) · 1 GEMINI.md file · 1 MCP server · 1 skill
|
||||||
- 1 GEMINI.md file
|
|
||||||
- 1 MCP server
|
|
||||||
- 1 skill
|
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -365,10 +365,10 @@
|
|||||||
"default": false,
|
"default": false,
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"hideFooterDuringApproval": {
|
"collapseDrawerDuringApproval": {
|
||||||
"title": "Hide Footer During Approval",
|
"title": "Collapse Drawer During Approval",
|
||||||
"description": "Hide the footer when a tool approval request is displayed.",
|
"description": "Collapse the entire drawer (status, context, input, footer) when a tool approval request is displayed.",
|
||||||
"markdownDescription": "Hide the footer when a tool approval request is displayed.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `true`",
|
"markdownDescription": "Collapse the entire drawer (status, context, input, footer) when a tool approval request is displayed.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `true`",
|
||||||
"default": true,
|
"default": true,
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user