mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-14 05:42:54 -07:00
chore: cleanup
This commit is contained in:
committed by
Keith Guerin
parent
3c58639102
commit
0ea3aedc18
@@ -47,7 +47,7 @@ export const ALL_ITEMS: FooterItem[] = [
|
|||||||
{
|
{
|
||||||
id: 'quota',
|
id: 'quota',
|
||||||
label: 'quota',
|
label: 'quota',
|
||||||
description: 'Remaining quota and reset time',
|
description: 'Remaining usage on daily limit',
|
||||||
defaultEnabled: true,
|
defaultEnabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ import process from 'node:process';
|
|||||||
import { MemoryUsageDisplay } from './MemoryUsageDisplay.js';
|
import { MemoryUsageDisplay } from './MemoryUsageDisplay.js';
|
||||||
import { ContextUsageDisplay } from './ContextUsageDisplay.js';
|
import { ContextUsageDisplay } from './ContextUsageDisplay.js';
|
||||||
import { QuotaDisplay } from './QuotaDisplay.js';
|
import { QuotaDisplay } from './QuotaDisplay.js';
|
||||||
|
import {
|
||||||
|
getStatusColor,
|
||||||
|
QUOTA_THRESHOLD_HIGH,
|
||||||
|
QUOTA_THRESHOLD_MEDIUM,
|
||||||
|
} from '../utils/displayUtils.js';
|
||||||
import { DebugProfiler } from './DebugProfiler.js';
|
import { DebugProfiler } from './DebugProfiler.js';
|
||||||
import { isDevelopment } from '../../utils/installationInfo.js';
|
import { isDevelopment } from '../../utils/installationInfo.js';
|
||||||
import { useUIState } from '../contexts/UIStateContext.js';
|
import { useUIState } from '../contexts/UIStateContext.js';
|
||||||
@@ -55,10 +60,16 @@ const CwdIndicator: React.FC<CwdIndicatorProps> = ({
|
|||||||
|
|
||||||
interface BranchIndicatorProps {
|
interface BranchIndicatorProps {
|
||||||
branchName: string;
|
branchName: string;
|
||||||
|
showParentheses?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BranchIndicator: React.FC<BranchIndicatorProps> = ({ branchName }) => (
|
const BranchIndicator: React.FC<BranchIndicatorProps> = ({
|
||||||
<Text color={theme.text.secondary}>({branchName}*)</Text>
|
branchName,
|
||||||
|
showParentheses = true,
|
||||||
|
}) => (
|
||||||
|
<Text color={theme.text.secondary}>
|
||||||
|
{showParentheses ? `(${branchName}*)` : `${branchName}*`}
|
||||||
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
interface SandboxIndicatorProps {
|
interface SandboxIndicatorProps {
|
||||||
@@ -322,7 +333,10 @@ export const Footer: React.FC = () => {
|
|||||||
}
|
}
|
||||||
case 'git-branch': {
|
case 'git-branch': {
|
||||||
if (branchName) {
|
if (branchName) {
|
||||||
addElement(id, <BranchIndicator branchName={branchName} />);
|
addElement(
|
||||||
|
id,
|
||||||
|
<BranchIndicator branchName={branchName} showParentheses={false} />,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -355,16 +369,21 @@ export const Footer: React.FC = () => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'quota': {
|
case 'quota': {
|
||||||
if (quotaStats) {
|
if (
|
||||||
addElement(
|
quotaStats &&
|
||||||
id,
|
quotaStats.remaining !== undefined &&
|
||||||
<QuotaDisplay
|
quotaStats.limit
|
||||||
remaining={quotaStats.remaining}
|
) {
|
||||||
limit={quotaStats.limit}
|
const percentage = (quotaStats.remaining / quotaStats.limit) * 100;
|
||||||
resetTime={quotaStats.resetTime}
|
const color = getStatusColor(percentage, {
|
||||||
terse={true}
|
green: QUOTA_THRESHOLD_HIGH,
|
||||||
/>,
|
yellow: QUOTA_THRESHOLD_MEDIUM,
|
||||||
);
|
});
|
||||||
|
const text =
|
||||||
|
quotaStats.remaining === 0
|
||||||
|
? 'limit reached'
|
||||||
|
: `daily ${percentage.toFixed(0)}%`;
|
||||||
|
addElement(id, <Text color={color}>{text}</Text>);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ describe('<FooterConfigDialog />', () => {
|
|||||||
|
|
||||||
// Initial state: 'cwd' is active.
|
// Initial state: 'cwd' is active.
|
||||||
// Verify 'cwd' content exists in the preview area
|
// Verify 'cwd' content exists in the preview area
|
||||||
expect(lastFrame()).toContain('~/dev/gemini-cli');
|
expect(lastFrame()).toContain('~/project/path');
|
||||||
|
|
||||||
// Move focus down to 'git-branch'
|
// Move focus down to 'git-branch'
|
||||||
act(() => {
|
act(() => {
|
||||||
@@ -146,4 +146,34 @@ describe('<FooterConfigDialog />', () => {
|
|||||||
expect(output).toContain('main*');
|
expect(output).toContain('main*');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows an empty preview when all items are deselected', async () => {
|
||||||
|
const settings = createMockSettings();
|
||||||
|
const { lastFrame, stdin } = renderWithProviders(
|
||||||
|
<FooterConfigDialog onClose={mockOnClose} />,
|
||||||
|
{ settings },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Deselect all items (assuming we know which ones are selected by default)
|
||||||
|
// By default: cwd, git-branch, sandbox-status, model-name, quota are selected.
|
||||||
|
// They are at indices 0, 1, 2, 3, 4.
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
act(() => {
|
||||||
|
stdin.write('\r'); // Toggle (deselect)
|
||||||
|
stdin.write('\u001b[B'); // Down arrow
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const output = lastFrame();
|
||||||
|
expect(output).toBeDefined();
|
||||||
|
expect(output).toContain('Preview:');
|
||||||
|
// The preview area should not contain any of the mock values
|
||||||
|
expect(output).not.toContain('~/project/path');
|
||||||
|
expect(output).not.toContain('main*');
|
||||||
|
expect(output).not.toContain('docker');
|
||||||
|
expect(output).not.toContain('gemini-2.5-pro');
|
||||||
|
expect(output).not.toContain('1.2k left');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -96,7 +96,31 @@ export const FooterConfigDialog: React.FC<FooterConfigDialogProps> = ({
|
|||||||
setScrollOffset(0);
|
setScrollOffset(0);
|
||||||
}, [searchQuery]);
|
}, [searchQuery]);
|
||||||
|
|
||||||
|
// The reset action lives one index past the filtered item list
|
||||||
|
const isResetFocused = activeIndex === filteredItems.length;
|
||||||
|
|
||||||
|
const handleResetToDefaults = useCallback(() => {
|
||||||
|
// Clear the custom items setting so the legacy footer path is used
|
||||||
|
setSetting(SettingScope.User, 'ui.footer.items', undefined);
|
||||||
|
|
||||||
|
// Reset local state to reflect legacy-derived items
|
||||||
|
const validIds = new Set(ALL_ITEMS.map((i) => i.id));
|
||||||
|
const derived = deriveItemsFromLegacySettings(settings.merged).filter(
|
||||||
|
(id) => validIds.has(id),
|
||||||
|
);
|
||||||
|
const others = DEFAULT_ORDER.filter((id) => !derived.includes(id));
|
||||||
|
setOrderedIds([...derived, ...others]);
|
||||||
|
setSelectedIds(new Set(derived));
|
||||||
|
setActiveIndex(0);
|
||||||
|
setScrollOffset(0);
|
||||||
|
}, [setSetting, settings.merged]);
|
||||||
|
|
||||||
const handleConfirm = useCallback(async () => {
|
const handleConfirm = useCallback(async () => {
|
||||||
|
if (isResetFocused) {
|
||||||
|
handleResetToDefaults();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const item = filteredItems[activeIndex];
|
const item = filteredItems[activeIndex];
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
@@ -111,7 +135,15 @@ export const FooterConfigDialog: React.FC<FooterConfigDialogProps> = ({
|
|||||||
// Save immediately on toggle
|
// Save immediately on toggle
|
||||||
const finalItems = orderedIds.filter((id) => next.has(id));
|
const finalItems = orderedIds.filter((id) => next.has(id));
|
||||||
setSetting(SettingScope.User, 'ui.footer.items', finalItems);
|
setSetting(SettingScope.User, 'ui.footer.items', finalItems);
|
||||||
}, [filteredItems, activeIndex, orderedIds, setSetting, selectedIds]);
|
}, [
|
||||||
|
filteredItems,
|
||||||
|
activeIndex,
|
||||||
|
orderedIds,
|
||||||
|
setSetting,
|
||||||
|
selectedIds,
|
||||||
|
isResetFocused,
|
||||||
|
handleResetToDefaults,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleReorder = useCallback(
|
const handleReorder = useCallback(
|
||||||
(direction: number) => {
|
(direction: number) => {
|
||||||
@@ -165,24 +197,31 @@ export const FooterConfigDialog: React.FC<FooterConfigDialogProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (keyMatchers[Command.DIALOG_NAVIGATION_UP](key)) {
|
if (keyMatchers[Command.DIALOG_NAVIGATION_UP](key)) {
|
||||||
const newIndex =
|
// Navigation wraps: items 0..filteredItems.length-1, then reset row at filteredItems.length
|
||||||
activeIndex > 0 ? activeIndex - 1 : filteredItems.length - 1;
|
const totalSlots = filteredItems.length + 1;
|
||||||
|
const newIndex = activeIndex > 0 ? activeIndex - 1 : totalSlots - 1;
|
||||||
setActiveIndex(newIndex);
|
setActiveIndex(newIndex);
|
||||||
if (newIndex === filteredItems.length - 1) {
|
// Only adjust scroll when within the item list
|
||||||
setScrollOffset(Math.max(0, filteredItems.length - maxItemsToShow));
|
if (newIndex < filteredItems.length) {
|
||||||
} else if (newIndex < scrollOffset) {
|
if (newIndex === filteredItems.length - 1) {
|
||||||
setScrollOffset(newIndex);
|
setScrollOffset(Math.max(0, filteredItems.length - maxItemsToShow));
|
||||||
|
} else if (newIndex < scrollOffset) {
|
||||||
|
setScrollOffset(newIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyMatchers[Command.DIALOG_NAVIGATION_DOWN](key)) {
|
if (keyMatchers[Command.DIALOG_NAVIGATION_DOWN](key)) {
|
||||||
const newIndex =
|
const totalSlots = filteredItems.length + 1;
|
||||||
activeIndex < filteredItems.length - 1 ? activeIndex + 1 : 0;
|
const newIndex = activeIndex < totalSlots - 1 ? activeIndex + 1 : 0;
|
||||||
setActiveIndex(newIndex);
|
setActiveIndex(newIndex);
|
||||||
if (newIndex === 0) {
|
if (newIndex === 0) {
|
||||||
setScrollOffset(0);
|
setScrollOffset(0);
|
||||||
} else if (newIndex >= scrollOffset + maxItemsToShow) {
|
} else if (
|
||||||
|
newIndex < filteredItems.length &&
|
||||||
|
newIndex >= scrollOffset + maxItemsToShow
|
||||||
|
) {
|
||||||
setScrollOffset(newIndex - maxItemsToShow + 1);
|
setScrollOffset(newIndex - maxItemsToShow + 1);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -217,15 +256,23 @@ export const FooterConfigDialog: React.FC<FooterConfigDialogProps> = ({
|
|||||||
|
|
||||||
// Preview logic
|
// Preview logic
|
||||||
const previewText = useMemo(() => {
|
const previewText = useMemo(() => {
|
||||||
|
if (isResetFocused) {
|
||||||
|
return (
|
||||||
|
<Text color={theme.text.secondary} italic>
|
||||||
|
Default footer (uses legacy settings)
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const itemsToPreview = orderedIds.filter((id) => selectedIds.has(id));
|
const itemsToPreview = orderedIds.filter((id) => selectedIds.has(id));
|
||||||
if (itemsToPreview.length === 0) return 'Empty Footer';
|
if (itemsToPreview.length === 0) return null;
|
||||||
|
|
||||||
const getColor = (id: string, defaultColor?: string) =>
|
const getColor = (id: string, defaultColor?: string) =>
|
||||||
id === activeId ? 'white' : defaultColor || theme.text.secondary;
|
id === activeId ? 'white' : defaultColor || theme.text.secondary;
|
||||||
|
|
||||||
// Mock values for preview
|
// Mock values for preview
|
||||||
const mockValues: Record<string, React.ReactNode> = {
|
const mockValues: Record<string, React.ReactNode> = {
|
||||||
cwd: <Text color={getColor('cwd')}>~/dev/gemini-cli</Text>,
|
cwd: <Text color={getColor('cwd')}>~/project/path</Text>,
|
||||||
'git-branch': <Text color={getColor('git-branch')}>main*</Text>,
|
'git-branch': <Text color={getColor('git-branch')}>main*</Text>,
|
||||||
'sandbox-status': (
|
'sandbox-status': (
|
||||||
<Text color={getColor('sandbox-status', 'green')}>docker</Text>
|
<Text color={getColor('sandbox-status', 'green')}>docker</Text>
|
||||||
@@ -236,9 +283,9 @@ export const FooterConfigDialog: React.FC<FooterConfigDialogProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
),
|
),
|
||||||
'context-remaining': (
|
'context-remaining': (
|
||||||
<Text color={getColor('context-remaining')}>85%</Text>
|
<Text color={getColor('context-remaining')}>85% context left</Text>
|
||||||
),
|
),
|
||||||
quota: <Text color={getColor('quota')}>1.2k left</Text>,
|
quota: <Text color={getColor('quota')}>daily 97%</Text>,
|
||||||
'memory-usage': <Text color={getColor('memory-usage')}>124MB</Text>,
|
'memory-usage': <Text color={getColor('memory-usage')}>124MB</Text>,
|
||||||
'session-id': <Text color={getColor('session-id')}>769992f9</Text>,
|
'session-id': <Text color={getColor('session-id')}>769992f9</Text>,
|
||||||
'code-changes': (
|
'code-changes': (
|
||||||
@@ -266,7 +313,7 @@ export const FooterConfigDialog: React.FC<FooterConfigDialogProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return elements;
|
return elements;
|
||||||
}, [orderedIds, selectedIds, activeId]);
|
}, [orderedIds, selectedIds, activeId, isResetFocused]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -321,6 +368,15 @@ export const FooterConfigDialog: React.FC<FooterConfigDialogProps> = ({
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
<Box marginTop={1}>
|
||||||
|
<Text
|
||||||
|
color={isResetFocused ? theme.status.warning : theme.text.secondary}
|
||||||
|
>
|
||||||
|
{isResetFocused ? '> ' : ' '}
|
||||||
|
Reset to default footer
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box marginTop={1} flexDirection="column">
|
<Box marginTop={1} flexDirection="column">
|
||||||
<Text color={theme.text.secondary}>
|
<Text color={theme.text.secondary}>
|
||||||
↑/↓ navigate · ←/→ reorder · enter select · esc close
|
↑/↓ navigate · ←/→ reorder · enter select · esc close
|
||||||
|
|||||||
Reference in New Issue
Block a user