feat(cli): respect the NO_COLOR env variable (#772)
This commit is contained in:
parent
c80ff146d2
commit
4262f5b0de
|
@ -15,6 +15,7 @@ import {
|
||||||
AccessibilitySettings,
|
AccessibilitySettings,
|
||||||
} from '@gemini-code/core';
|
} from '@gemini-code/core';
|
||||||
import { LoadedSettings, SettingsFile, Settings } from '../config/settings.js';
|
import { LoadedSettings, SettingsFile, Settings } from '../config/settings.js';
|
||||||
|
import process from 'node:process';
|
||||||
|
|
||||||
// Define a more complete mock server config based on actual Config
|
// Define a more complete mock server config based on actual Config
|
||||||
interface MockServerConfig {
|
interface MockServerConfig {
|
||||||
|
@ -345,10 +346,23 @@ describe('App UI', () => {
|
||||||
expect(lastFrame()).toContain('Using 2 MCP servers');
|
expect(lastFrame()).toContain('Using 2 MCP servers');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display theme dialog if no theme is set in settings', async () => {
|
describe('when no theme is set', () => {
|
||||||
|
let originalNoColor: string | undefined;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalNoColor = process.env.NO_COLOR;
|
||||||
|
// Ensure no theme is set for these tests
|
||||||
mockSettings = createMockSettings({});
|
mockSettings = createMockSettings({});
|
||||||
mockConfig.getDebugMode.mockReturnValue(false);
|
mockConfig.getDebugMode.mockReturnValue(false);
|
||||||
mockConfig.getShowMemoryUsage.mockReturnValue(false);
|
mockConfig.getShowMemoryUsage.mockReturnValue(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.NO_COLOR = originalNoColor;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display theme dialog if NO_COLOR is not set', async () => {
|
||||||
|
delete process.env.NO_COLOR;
|
||||||
|
|
||||||
const { lastFrame, unmount } = render(
|
const { lastFrame, unmount } = render(
|
||||||
<App
|
<App
|
||||||
|
@ -361,4 +375,23 @@ describe('App UI', () => {
|
||||||
|
|
||||||
expect(lastFrame()).toContain('Select Theme');
|
expect(lastFrame()).toContain('Select Theme');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should display a message if NO_COLOR is set', async () => {
|
||||||
|
process.env.NO_COLOR = 'true';
|
||||||
|
|
||||||
|
const { lastFrame, unmount } = render(
|
||||||
|
<App
|
||||||
|
config={mockConfig as unknown as ServerConfig}
|
||||||
|
settings={mockSettings}
|
||||||
|
cliVersion="1.0.0"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
currentUnmount = unmount;
|
||||||
|
|
||||||
|
expect(lastFrame()).toContain(
|
||||||
|
'Theme configuration unavailable due to NO_COLOR env variable.',
|
||||||
|
);
|
||||||
|
expect(lastFrame()).not.toContain('Select Theme');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -136,7 +136,7 @@ export const App = ({
|
||||||
openThemeDialog,
|
openThemeDialog,
|
||||||
handleThemeSelect,
|
handleThemeSelect,
|
||||||
handleThemeHighlight,
|
handleThemeHighlight,
|
||||||
} = useThemeCommand(settings, setThemeError);
|
} = useThemeCommand(settings, setThemeError, addItem);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (config) {
|
if (config) {
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import { themeManager } from '../themes/theme-manager.js';
|
import { themeManager } from '../themes/theme-manager.js';
|
||||||
import { LoadedSettings, SettingScope } from '../../config/settings.js'; // Import LoadedSettings, AppSettings, MergedSetting
|
import { LoadedSettings, SettingScope } from '../../config/settings.js'; // Import LoadedSettings, AppSettings, MergedSetting
|
||||||
|
import { type HistoryItem, MessageType } from '../types.js';
|
||||||
|
import process from 'node:process';
|
||||||
|
|
||||||
interface UseThemeCommandReturn {
|
interface UseThemeCommandReturn {
|
||||||
isThemeDialogOpen: boolean;
|
isThemeDialogOpen: boolean;
|
||||||
|
@ -21,34 +23,55 @@ interface UseThemeCommandReturn {
|
||||||
export const useThemeCommand = (
|
export const useThemeCommand = (
|
||||||
loadedSettings: LoadedSettings,
|
loadedSettings: LoadedSettings,
|
||||||
setThemeError: (error: string | null) => void,
|
setThemeError: (error: string | null) => void,
|
||||||
|
addItem: (item: Omit<HistoryItem, 'id'>, timestamp: number) => void,
|
||||||
): UseThemeCommandReturn => {
|
): UseThemeCommandReturn => {
|
||||||
// Determine the effective theme
|
// Determine the effective theme
|
||||||
const effectiveTheme = loadedSettings.merged.theme;
|
const effectiveTheme = loadedSettings.merged.theme;
|
||||||
|
|
||||||
// Initial state: Open dialog if no theme is set in either user or workspace settings
|
// Initial state: Open dialog if no theme is set in either user or workspace settings
|
||||||
const [isThemeDialogOpen, setIsThemeDialogOpen] = useState(
|
const [isThemeDialogOpen, setIsThemeDialogOpen] = useState(
|
||||||
effectiveTheme === undefined, // Open dialog if no theme is set initially
|
effectiveTheme === undefined && !process.env.NO_COLOR,
|
||||||
);
|
);
|
||||||
// TODO: refactor how theme's are accessed to avoid requiring a forced render.
|
// TODO: refactor how theme's are accessed to avoid requiring a forced render.
|
||||||
const [, setForceRender] = useState(0);
|
const [, setForceRender] = useState(0);
|
||||||
|
|
||||||
// Apply initial theme on component mount
|
// Apply initial theme on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only try to set a theme if one is actually defined.
|
if (effectiveTheme === undefined) {
|
||||||
// If effectiveTheme was undefined, the dialog is already open due to useState above.
|
if (process.env.NO_COLOR) {
|
||||||
if (effectiveTheme !== undefined) {
|
addItem(
|
||||||
|
{
|
||||||
|
type: MessageType.INFO,
|
||||||
|
text: 'Theme configuration unavailable due to NO_COLOR env variable.',
|
||||||
|
},
|
||||||
|
Date.now(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// If no theme is set and NO_COLOR is not set, the dialog is already open.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!themeManager.setActiveTheme(effectiveTheme)) {
|
if (!themeManager.setActiveTheme(effectiveTheme)) {
|
||||||
setIsThemeDialogOpen(true);
|
setIsThemeDialogOpen(true);
|
||||||
setThemeError(`Theme "${effectiveTheme}" not found.`);
|
setThemeError(`Theme "${effectiveTheme}" not found.`);
|
||||||
} else {
|
} else {
|
||||||
setThemeError(null); // Clear any previous theme error on success
|
setThemeError(null);
|
||||||
}
|
}
|
||||||
}
|
}, [effectiveTheme, setThemeError, addItem]); // Re-run if effectiveTheme or setThemeError changes
|
||||||
}, [effectiveTheme, setThemeError]); // Re-run if effectiveTheme or setThemeError changes
|
|
||||||
|
|
||||||
const openThemeDialog = useCallback(() => {
|
const openThemeDialog = useCallback(() => {
|
||||||
|
if (process.env.NO_COLOR) {
|
||||||
|
addItem(
|
||||||
|
{
|
||||||
|
type: MessageType.INFO,
|
||||||
|
text: 'Theme configuration unavailable due to NO_COLOR env variable.',
|
||||||
|
},
|
||||||
|
Date.now(),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setIsThemeDialogOpen(true);
|
setIsThemeDialogOpen(true);
|
||||||
}, []);
|
}, [addItem]);
|
||||||
|
|
||||||
const applyTheme = useCallback(
|
const applyTheme = useCallback(
|
||||||
(themeName: string | undefined) => {
|
(themeName: string | undefined) => {
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Theme, ColorsTheme } from './theme.js';
|
||||||
|
|
||||||
|
const noColorColorsTheme: ColorsTheme = {
|
||||||
|
type: 'ansi',
|
||||||
|
Background: '',
|
||||||
|
Foreground: '',
|
||||||
|
LightBlue: '',
|
||||||
|
AccentBlue: '',
|
||||||
|
AccentPurple: '',
|
||||||
|
AccentCyan: '',
|
||||||
|
AccentGreen: '',
|
||||||
|
AccentYellow: '',
|
||||||
|
AccentRed: '',
|
||||||
|
SubtleComment: '',
|
||||||
|
Gray: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NoColorTheme: Theme = new Theme(
|
||||||
|
'No Color',
|
||||||
|
'dark',
|
||||||
|
{
|
||||||
|
hljs: {
|
||||||
|
display: 'block',
|
||||||
|
overflowX: 'auto',
|
||||||
|
padding: '0.5em',
|
||||||
|
},
|
||||||
|
'hljs-keyword': {},
|
||||||
|
'hljs-literal': {},
|
||||||
|
'hljs-symbol': {},
|
||||||
|
'hljs-name': {},
|
||||||
|
'hljs-link': {
|
||||||
|
textDecoration: 'underline',
|
||||||
|
},
|
||||||
|
'hljs-built_in': {},
|
||||||
|
'hljs-type': {},
|
||||||
|
'hljs-number': {},
|
||||||
|
'hljs-class': {},
|
||||||
|
'hljs-string': {},
|
||||||
|
'hljs-meta-string': {},
|
||||||
|
'hljs-regexp': {},
|
||||||
|
'hljs-template-tag': {},
|
||||||
|
'hljs-subst': {},
|
||||||
|
'hljs-function': {},
|
||||||
|
'hljs-title': {},
|
||||||
|
'hljs-params': {},
|
||||||
|
'hljs-formula': {},
|
||||||
|
'hljs-comment': {
|
||||||
|
fontStyle: 'italic',
|
||||||
|
},
|
||||||
|
'hljs-quote': {
|
||||||
|
fontStyle: 'italic',
|
||||||
|
},
|
||||||
|
'hljs-doctag': {},
|
||||||
|
'hljs-meta': {},
|
||||||
|
'hljs-meta-keyword': {},
|
||||||
|
'hljs-tag': {},
|
||||||
|
'hljs-variable': {},
|
||||||
|
'hljs-template-variable': {},
|
||||||
|
'hljs-attr': {},
|
||||||
|
'hljs-attribute': {},
|
||||||
|
'hljs-builtin-name': {},
|
||||||
|
'hljs-section': {},
|
||||||
|
'hljs-emphasis': {
|
||||||
|
fontStyle: 'italic',
|
||||||
|
},
|
||||||
|
'hljs-strong': {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
'hljs-bullet': {},
|
||||||
|
'hljs-selector-tag': {},
|
||||||
|
'hljs-selector-id': {},
|
||||||
|
'hljs-selector-class': {},
|
||||||
|
'hljs-selector-attr': {},
|
||||||
|
'hljs-selector-pseudo': {},
|
||||||
|
'hljs-addition': {
|
||||||
|
display: 'inline-block',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
'hljs-deletion': {
|
||||||
|
display: 'inline-block',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
noColorColorsTheme,
|
||||||
|
);
|
|
@ -17,6 +17,8 @@ import { XCode } from './xcode.js';
|
||||||
import { Theme, ThemeType } from './theme.js';
|
import { Theme, ThemeType } from './theme.js';
|
||||||
import { ANSI } from './ansi.js';
|
import { ANSI } from './ansi.js';
|
||||||
import { ANSILight } from './ansi-light.js';
|
import { ANSILight } from './ansi-light.js';
|
||||||
|
import { NoColorTheme } from './no-color.js';
|
||||||
|
import process from 'node:process';
|
||||||
|
|
||||||
export interface ThemeDisplay {
|
export interface ThemeDisplay {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -110,6 +112,9 @@ class ThemeManager {
|
||||||
* Returns the currently active theme object.
|
* Returns the currently active theme object.
|
||||||
*/
|
*/
|
||||||
getActiveTheme(): Theme {
|
getActiveTheme(): Theme {
|
||||||
|
if (process.env.NO_COLOR) {
|
||||||
|
return NoColorTheme;
|
||||||
|
}
|
||||||
return this.activeTheme;
|
return this.activeTheme;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue