mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-26 13:04:49 -07:00
feat(cli): add global setting to disable UI spinners (#17234)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -60,6 +60,7 @@ they appear in the UI.
|
|||||||
| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` |
|
| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` |
|
||||||
| Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` |
|
| Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` |
|
||||||
| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` |
|
| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` |
|
||||||
|
| Show Spinner | `ui.showSpinner` | Show the spinner during operations. | `true` |
|
||||||
| Enable Loading Phrases | `ui.accessibility.enableLoadingPhrases` | Enable loading phrases during operations. | `true` |
|
| Enable Loading Phrases | `ui.accessibility.enableLoadingPhrases` | Enable loading phrases during operations. | `true` |
|
||||||
| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible | `false` |
|
| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible | `false` |
|
||||||
|
|
||||||
|
|||||||
@@ -261,6 +261,10 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
- **Default:** `true`
|
- **Default:** `true`
|
||||||
- **Requires restart:** Yes
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
|
- **`ui.showSpinner`** (boolean):
|
||||||
|
- **Description:** Show the spinner during operations.
|
||||||
|
- **Default:** `true`
|
||||||
|
|
||||||
- **`ui.customWittyPhrases`** (array):
|
- **`ui.customWittyPhrases`** (array):
|
||||||
- **Description:** Custom witty phrases to display during loading. When
|
- **Description:** Custom witty phrases to display during loading. When
|
||||||
provided, the CLI cycles through these instead of the defaults.
|
provided, the CLI cycles through these instead of the defaults.
|
||||||
|
|||||||
@@ -555,6 +555,15 @@ const SETTINGS_SCHEMA = {
|
|||||||
'Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled.',
|
'Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled.',
|
||||||
showInDialog: true,
|
showInDialog: true,
|
||||||
},
|
},
|
||||||
|
showSpinner: {
|
||||||
|
type: 'boolean',
|
||||||
|
label: 'Show Spinner',
|
||||||
|
category: 'UI',
|
||||||
|
requiresRestart: false,
|
||||||
|
default: true,
|
||||||
|
description: 'Show the spinner during operations.',
|
||||||
|
showInDialog: true,
|
||||||
|
},
|
||||||
customWittyPhrases: {
|
customWittyPhrases: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
label: 'Custom Witty Phrases',
|
label: 'Custom Witty Phrases',
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { render } from '../../test-utils/render.js';
|
import {
|
||||||
|
renderWithProviders,
|
||||||
|
createMockSettings,
|
||||||
|
} from '../../test-utils/render.js';
|
||||||
import { CliSpinner } from './CliSpinner.js';
|
import { CliSpinner } from './CliSpinner.js';
|
||||||
import { debugState } from '../debug.js';
|
import { debugState } from '../debug.js';
|
||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
@@ -16,9 +19,15 @@ describe('<CliSpinner />', () => {
|
|||||||
|
|
||||||
it('should increment debugNumAnimatedComponents on mount and decrement on unmount', () => {
|
it('should increment debugNumAnimatedComponents on mount and decrement on unmount', () => {
|
||||||
expect(debugState.debugNumAnimatedComponents).toBe(0);
|
expect(debugState.debugNumAnimatedComponents).toBe(0);
|
||||||
const { unmount } = render(<CliSpinner />);
|
const { unmount } = renderWithProviders(<CliSpinner />);
|
||||||
expect(debugState.debugNumAnimatedComponents).toBe(1);
|
expect(debugState.debugNumAnimatedComponents).toBe(1);
|
||||||
unmount();
|
unmount();
|
||||||
expect(debugState.debugNumAnimatedComponents).toBe(0);
|
expect(debugState.debugNumAnimatedComponents).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not render when showSpinner is false', () => {
|
||||||
|
const settings = createMockSettings({ ui: { showSpinner: false } });
|
||||||
|
const { lastFrame } = renderWithProviders(<CliSpinner />, { settings });
|
||||||
|
expect(lastFrame()).toBe('');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,16 +7,27 @@
|
|||||||
import Spinner from 'ink-spinner';
|
import Spinner from 'ink-spinner';
|
||||||
import { type ComponentProps, useEffect } from 'react';
|
import { type ComponentProps, useEffect } from 'react';
|
||||||
import { debugState } from '../debug.js';
|
import { debugState } from '../debug.js';
|
||||||
|
import { useSettings } from '../contexts/SettingsContext.js';
|
||||||
|
|
||||||
export type SpinnerProps = ComponentProps<typeof Spinner>;
|
export type SpinnerProps = ComponentProps<typeof Spinner>;
|
||||||
|
|
||||||
export const CliSpinner = (props: SpinnerProps) => {
|
export const CliSpinner = (props: SpinnerProps) => {
|
||||||
|
const settings = useSettings();
|
||||||
|
const shouldShow = settings.merged.ui?.showSpinner !== false;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
debugState.debugNumAnimatedComponents++;
|
if (shouldShow) {
|
||||||
return () => {
|
debugState.debugNumAnimatedComponents++;
|
||||||
debugState.debugNumAnimatedComponents--;
|
return () => {
|
||||||
};
|
debugState.debugNumAnimatedComponents--;
|
||||||
}, []);
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [shouldShow]);
|
||||||
|
|
||||||
|
if (!shouldShow) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return <Spinner {...props} />;
|
return <Spinner {...props} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { render } from '../../../test-utils/render.js';
|
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||||
import type { CompressionDisplayProps } from './CompressionMessage.js';
|
import type { CompressionDisplayProps } from './CompressionMessage.js';
|
||||||
import { CompressionMessage } from './CompressionMessage.js';
|
import { CompressionMessage } from './CompressionMessage.js';
|
||||||
import { CompressionStatus } from '@google/gemini-cli-core';
|
import { CompressionStatus } from '@google/gemini-cli-core';
|
||||||
@@ -27,7 +27,9 @@ describe('<CompressionMessage />', () => {
|
|||||||
describe('pending state', () => {
|
describe('pending state', () => {
|
||||||
it('renders pending message when compression is in progress', () => {
|
it('renders pending message when compression is in progress', () => {
|
||||||
const props = createCompressionProps({ isPending: true });
|
const props = createCompressionProps({ isPending: true });
|
||||||
const { lastFrame, unmount } = render(<CompressionMessage {...props} />);
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
|
<CompressionMessage {...props} />,
|
||||||
|
);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
|
|
||||||
expect(output).toContain('Compressing chat history');
|
expect(output).toContain('Compressing chat history');
|
||||||
@@ -43,7 +45,9 @@ describe('<CompressionMessage />', () => {
|
|||||||
newTokenCount: 50,
|
newTokenCount: 50,
|
||||||
compressionStatus: CompressionStatus.COMPRESSED,
|
compressionStatus: CompressionStatus.COMPRESSED,
|
||||||
});
|
});
|
||||||
const { lastFrame, unmount } = render(<CompressionMessage {...props} />);
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
|
<CompressionMessage {...props} />,
|
||||||
|
);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
|
|
||||||
expect(output).toContain('✦');
|
expect(output).toContain('✦');
|
||||||
@@ -66,7 +70,7 @@ describe('<CompressionMessage />', () => {
|
|||||||
newTokenCount: newTokens,
|
newTokenCount: newTokens,
|
||||||
compressionStatus: CompressionStatus.COMPRESSED,
|
compressionStatus: CompressionStatus.COMPRESSED,
|
||||||
});
|
});
|
||||||
const { lastFrame, unmount } = render(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<CompressionMessage {...props} />,
|
<CompressionMessage {...props} />,
|
||||||
);
|
);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
@@ -91,7 +95,9 @@ describe('<CompressionMessage />', () => {
|
|||||||
compressionStatus:
|
compressionStatus:
|
||||||
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
||||||
});
|
});
|
||||||
const { lastFrame, unmount } = render(<CompressionMessage {...props} />);
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
|
<CompressionMessage {...props} />,
|
||||||
|
);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
|
|
||||||
expect(output).toContain('✦');
|
expect(output).toContain('✦');
|
||||||
@@ -109,7 +115,9 @@ describe('<CompressionMessage />', () => {
|
|||||||
compressionStatus:
|
compressionStatus:
|
||||||
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
||||||
});
|
});
|
||||||
const { lastFrame, unmount } = render(<CompressionMessage {...props} />);
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
|
<CompressionMessage {...props} />,
|
||||||
|
);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
|
|
||||||
expect(output).toContain(
|
expect(output).toContain(
|
||||||
@@ -146,7 +154,7 @@ describe('<CompressionMessage />', () => {
|
|||||||
newTokenCount: newTokens,
|
newTokenCount: newTokens,
|
||||||
compressionStatus: CompressionStatus.COMPRESSED,
|
compressionStatus: CompressionStatus.COMPRESSED,
|
||||||
});
|
});
|
||||||
const { lastFrame, unmount } = render(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<CompressionMessage {...props} />,
|
<CompressionMessage {...props} />,
|
||||||
);
|
);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
@@ -171,7 +179,7 @@ describe('<CompressionMessage />', () => {
|
|||||||
compressionStatus:
|
compressionStatus:
|
||||||
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
||||||
});
|
});
|
||||||
const { lastFrame, unmount } = render(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<CompressionMessage {...props} />,
|
<CompressionMessage {...props} />,
|
||||||
);
|
);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
@@ -199,7 +207,7 @@ describe('<CompressionMessage />', () => {
|
|||||||
compressionStatus:
|
compressionStatus:
|
||||||
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
||||||
});
|
});
|
||||||
const { lastFrame, unmount } = render(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<CompressionMessage {...props} />,
|
<CompressionMessage {...props} />,
|
||||||
);
|
);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
@@ -218,7 +226,9 @@ describe('<CompressionMessage />', () => {
|
|||||||
isPending: false,
|
isPending: false,
|
||||||
compressionStatus: CompressionStatus.COMPRESSION_FAILED_EMPTY_SUMMARY,
|
compressionStatus: CompressionStatus.COMPRESSION_FAILED_EMPTY_SUMMARY,
|
||||||
});
|
});
|
||||||
const { lastFrame, unmount } = render(<CompressionMessage {...props} />);
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
|
<CompressionMessage {...props} />,
|
||||||
|
);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
|
|
||||||
expect(output).toContain('✦');
|
expect(output).toContain('✦');
|
||||||
@@ -234,7 +244,9 @@ describe('<CompressionMessage />', () => {
|
|||||||
compressionStatus:
|
compressionStatus:
|
||||||
CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR,
|
CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR,
|
||||||
});
|
});
|
||||||
const { lastFrame, unmount } = render(<CompressionMessage {...props} />);
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
|
<CompressionMessage {...props} />,
|
||||||
|
);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
|
|
||||||
expect(output).toContain(
|
expect(output).toContain(
|
||||||
|
|||||||
@@ -323,6 +323,13 @@
|
|||||||
"default": true,
|
"default": true,
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"showSpinner": {
|
||||||
|
"title": "Show Spinner",
|
||||||
|
"description": "Show the spinner during operations.",
|
||||||
|
"markdownDescription": "Show the spinner during operations.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `true`",
|
||||||
|
"default": true,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"customWittyPhrases": {
|
"customWittyPhrases": {
|
||||||
"title": "Custom Witty Phrases",
|
"title": "Custom Witty Phrases",
|
||||||
"description": "Custom witty phrases to display during loading. When provided, the CLI cycles through these instead of the defaults.",
|
"description": "Custom witty phrases to display during loading. When provided, the CLI cycles through these instead of the defaults.",
|
||||||
|
|||||||
Reference in New Issue
Block a user