refactor(ui): Update and simplify use of gray colors in themes (#20141)

This commit is contained in:
Keith Guerin
2026-02-24 01:21:10 -08:00
committed by GitHub
parent e69e23e4a0
commit d143a83d5b
13 changed files with 124 additions and 58 deletions

View File

@@ -53,6 +53,12 @@ export const Colors: ColorsTheme = {
get DarkGray() {
return themeManager.getColors().DarkGray;
},
get InputBackground() {
return themeManager.getColors().InputBackground;
},
get MessageBackground() {
return themeManager.getColors().MessageBackground;
},
get GradientColors() {
return themeManager.getActiveTheme().colors.GradientColors;
},

View File

@@ -96,6 +96,8 @@ describe('<Header />', () => {
},
background: {
primary: '',
message: '',
input: '',
diff: { added: '', removed: '' },
},
border: {

View File

@@ -56,10 +56,6 @@ import {
} from '../utils/commandUtils.js';
import * as path from 'node:path';
import { SCREEN_READER_USER_PREFIX } from '../textConstants.js';
import {
DEFAULT_BACKGROUND_OPACITY,
DEFAULT_INPUT_BACKGROUND_OPACITY,
} from '../constants.js';
import { getSafeLowColorBackground } from '../themes/color-utils.js';
import { isLowColorDepth } from '../utils/terminalUtils.js';
import { useShellFocusState } from '../contexts/ShellFocusContext.js';
@@ -226,7 +222,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
backgroundShells,
backgroundShellHeight,
shortcutsHelpVisible,
hintMode,
} = useUIState();
const [suppressCompletion, setSuppressCompletion] = useState(false);
const { handlePress: registerPlainTabPress, resetCount: resetPlainTabPress } =
@@ -1422,14 +1417,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
/>
) : null}
<HalfLinePaddedBox
backgroundBaseColor={
hintMode ? theme.text.accent : theme.text.secondary
}
backgroundOpacity={
showCursor
? DEFAULT_INPUT_BACKGROUND_OPACITY
: DEFAULT_BACKGROUND_OPACITY
}
backgroundBaseColor={theme.background.input}
backgroundOpacity={1}
useBackgroundColor={useBackgroundColor}
>
<Box

View File

@@ -15,7 +15,6 @@ import {
calculateTransformedLine,
} from '../shared/text-buffer.js';
import { HalfLinePaddedBox } from '../shared/HalfLinePaddedBox.js';
import { DEFAULT_BACKGROUND_OPACITY } from '../../constants.js';
import { useConfig } from '../../contexts/ConfigContext.js';
interface UserMessageProps {
@@ -52,8 +51,8 @@ export const UserMessage: React.FC<UserMessageProps> = ({ text, width }) => {
return (
<HalfLinePaddedBox
backgroundBaseColor={theme.text.secondary}
backgroundOpacity={DEFAULT_BACKGROUND_OPACITY}
backgroundBaseColor={theme.background.message}
backgroundOpacity={1}
useBackgroundColor={useBackgroundColor}
>
<Box

View File

@@ -8,7 +8,6 @@ import type React from 'react';
import { Box, Text } from 'ink';
import { theme } from '../../semantic-colors.js';
import { HalfLinePaddedBox } from '../shared/HalfLinePaddedBox.js';
import { DEFAULT_BACKGROUND_OPACITY } from '../../constants.js';
import { useConfig } from '../../contexts/ConfigContext.js';
interface UserShellMessageProps {
@@ -28,8 +27,8 @@ export const UserShellMessage: React.FC<UserShellMessageProps> = ({
return (
<HalfLinePaddedBox
backgroundBaseColor={theme.text.secondary}
backgroundOpacity={DEFAULT_BACKGROUND_OPACITY}
backgroundBaseColor={theme.background.message}
backgroundOpacity={1}
useBackgroundColor={useBackgroundColor}
>
<Box

View File

@@ -37,7 +37,7 @@ export const EXPAND_HINT_DURATION_MS = 5000;
export const DEFAULT_BACKGROUND_OPACITY = 0.16;
export const DEFAULT_INPUT_BACKGROUND_OPACITY = 0.24;
export const DEFAULT_BORDER_OPACITY = 0.2;
export const DEFAULT_BORDER_OPACITY = 0.4;
export const KEYBOARD_SHORTCUTS_URL =
'https://geminicli.com/docs/cli/keyboard-shortcuts/';

View File

@@ -24,6 +24,8 @@ const noColorColorsTheme: ColorsTheme = {
Comment: '',
Gray: '',
DarkGray: '',
InputBackground: '',
MessageBackground: '',
};
const noColorSemanticColors: SemanticColors = {
@@ -36,6 +38,8 @@ const noColorSemanticColors: SemanticColors = {
},
background: {
primary: '',
message: '',
input: '',
diff: {
added: '',
removed: '',

View File

@@ -16,6 +16,8 @@ export interface SemanticColors {
};
background: {
primary: string;
message: string;
input: string;
diff: {
added: string;
removed: string;
@@ -48,13 +50,15 @@ export const lightSemanticColors: SemanticColors = {
},
background: {
primary: lightTheme.Background,
message: lightTheme.MessageBackground!,
input: lightTheme.InputBackground!,
diff: {
added: lightTheme.DiffAdded,
removed: lightTheme.DiffRemoved,
},
},
border: {
default: lightTheme.Gray,
default: lightTheme.DarkGray,
focused: lightTheme.AccentBlue,
},
ui: {
@@ -80,13 +84,15 @@ export const darkSemanticColors: SemanticColors = {
},
background: {
primary: darkTheme.Background,
message: darkTheme.MessageBackground!,
input: darkTheme.InputBackground!,
diff: {
added: darkTheme.DiffAdded,
removed: darkTheme.DiffRemoved,
},
},
border: {
default: darkTheme.Gray,
default: darkTheme.DarkGray,
focused: darkTheme.AccentBlue,
},
ui: {

View File

@@ -36,6 +36,8 @@ const semanticColors: SemanticColors = {
},
background: {
primary: '#002b36',
message: '#073642',
input: '#073642',
diff: {
added: '#00382f',
removed: '#3d0115',

View File

@@ -36,6 +36,8 @@ const semanticColors: SemanticColors = {
},
background: {
primary: '#fdf6e3',
message: '#eee8d5',
input: '#eee8d5',
diff: {
added: '#d7f2d7',
removed: '#f2d7d7',

View File

@@ -29,7 +29,11 @@ import {
getThemeTypeFromBackgroundColor,
resolveColor,
} from './color-utils.js';
import { DEFAULT_BORDER_OPACITY } from '../constants.js';
import {
DEFAULT_BACKGROUND_OPACITY,
DEFAULT_INPUT_BACKGROUND_OPACITY,
DEFAULT_BORDER_OPACITY,
} from '../constants.js';
import { ANSI } from './ansi.js';
import { ANSILight } from './ansi-light.js';
import { NoColorTheme } from './no-color.js';
@@ -310,7 +314,21 @@ class ThemeManager {
this.cachedColors = {
...colors,
Background: this.terminalBackground,
DarkGray: interpolateColor(colors.Gray, this.terminalBackground, 0.5),
DarkGray: interpolateColor(
this.terminalBackground,
colors.Gray,
DEFAULT_BORDER_OPACITY,
),
InputBackground: interpolateColor(
this.terminalBackground,
colors.Gray,
DEFAULT_INPUT_BACKGROUND_OPACITY,
),
MessageBackground: interpolateColor(
this.terminalBackground,
colors.Gray,
DEFAULT_BACKGROUND_OPACITY,
),
};
} else {
this.cachedColors = colors;
@@ -336,27 +354,22 @@ class ThemeManager {
this.terminalBackground &&
this.isThemeCompatible(activeTheme, this.terminalBackground)
) {
const colors = this.getColors();
this.cachedSemanticColors = {
...semanticColors,
background: {
...semanticColors.background,
primary: this.terminalBackground,
message: colors.MessageBackground!,
input: colors.InputBackground!,
},
border: {
...semanticColors.border,
default: interpolateColor(
this.terminalBackground,
activeTheme.colors.Gray,
DEFAULT_BORDER_OPACITY,
),
default: colors.DarkGray,
},
ui: {
...semanticColors.ui,
dark: interpolateColor(
activeTheme.colors.Gray,
this.terminalBackground,
0.5,
),
dark: colors.DarkGray,
},
};
} else {

View File

@@ -37,11 +37,11 @@ describe('createCustomTheme', () => {
it('should interpolate DarkGray when not provided', () => {
const theme = createCustomTheme(baseTheme);
// Interpolate between Gray (#cccccc) and Background (#000000) at 0.5
// Interpolate between Background (#000000) and Gray (#cccccc) at 0.4
// #cccccc is RGB(204, 204, 204)
// #000000 is RGB(0, 0, 0)
// Midpoint is RGB(102, 102, 102) which is #666666
expect(theme.colors.DarkGray).toBe('#666666');
// Result is RGB(82, 82, 82) which is #525252
expect(theme.colors.DarkGray).toBe('#525252');
});
it('should use provided DarkGray', () => {
@@ -64,8 +64,8 @@ describe('createCustomTheme', () => {
},
};
const theme = createCustomTheme(customTheme);
// Should be interpolated between #cccccc and #000000 at 0.5 -> #666666
expect(theme.colors.DarkGray).toBe('#666666');
// Should be interpolated between #000000 and #cccccc at 0.4 -> #525252
expect(theme.colors.DarkGray).toBe('#525252');
});
it('should prefer text.secondary over Gray for interpolation', () => {
@@ -81,8 +81,8 @@ describe('createCustomTheme', () => {
},
};
const theme = createCustomTheme(customTheme);
// Interpolate between #cccccc and #000000 -> #666666
expect(theme.colors.DarkGray).toBe('#666666');
// Interpolate between #000000 and #cccccc -> #525252
expect(theme.colors.DarkGray).toBe('#525252');
});
});

View File

@@ -15,7 +15,11 @@ import {
} from './color-utils.js';
import type { CustomTheme } from '@google/gemini-cli-core';
import { DEFAULT_BORDER_OPACITY } from '../constants.js';
import {
DEFAULT_BACKGROUND_OPACITY,
DEFAULT_INPUT_BACKGROUND_OPACITY,
DEFAULT_BORDER_OPACITY,
} from '../constants.js';
export type { CustomTheme };
@@ -37,6 +41,8 @@ export interface ColorsTheme {
Comment: string;
Gray: string;
DarkGray: string;
InputBackground?: string;
MessageBackground?: string;
GradientColors?: string[];
}
@@ -55,7 +61,17 @@ export const lightTheme: ColorsTheme = {
DiffRemoved: '#FFCCCC',
Comment: '#008000',
Gray: '#97a0b0',
DarkGray: interpolateColor('#97a0b0', '#FAFAFA', 0.5),
DarkGray: interpolateColor('#FAFAFA', '#97a0b0', DEFAULT_BORDER_OPACITY),
InputBackground: interpolateColor(
'#FAFAFA',
'#97a0b0',
DEFAULT_INPUT_BACKGROUND_OPACITY,
),
MessageBackground: interpolateColor(
'#FAFAFA',
'#97a0b0',
DEFAULT_BACKGROUND_OPACITY,
),
GradientColors: ['#4796E4', '#847ACE', '#C3677F'],
};
@@ -74,7 +90,17 @@ export const darkTheme: ColorsTheme = {
DiffRemoved: '#430000',
Comment: '#6C7086',
Gray: '#6C7086',
DarkGray: interpolateColor('#6C7086', '#1E1E2E', 0.5),
DarkGray: interpolateColor('#1E1E2E', '#6C7086', DEFAULT_BORDER_OPACITY),
InputBackground: interpolateColor(
'#1E1E2E',
'#6C7086',
DEFAULT_INPUT_BACKGROUND_OPACITY,
),
MessageBackground: interpolateColor(
'#1E1E2E',
'#6C7086',
DEFAULT_BACKGROUND_OPACITY,
),
GradientColors: ['#4796E4', '#847ACE', '#C3677F'],
};
@@ -94,6 +120,8 @@ export const ansiTheme: ColorsTheme = {
Comment: 'gray',
Gray: 'gray',
DarkGray: 'gray',
InputBackground: 'black',
MessageBackground: 'black',
};
export class Theme {
@@ -131,17 +159,27 @@ export class Theme {
},
background: {
primary: this.colors.Background,
message:
this.colors.MessageBackground ??
interpolateColor(
this.colors.Background,
this.colors.Gray,
DEFAULT_BACKGROUND_OPACITY,
),
input:
this.colors.InputBackground ??
interpolateColor(
this.colors.Background,
this.colors.Gray,
DEFAULT_INPUT_BACKGROUND_OPACITY,
),
diff: {
added: this.colors.DiffAdded,
removed: this.colors.DiffRemoved,
},
},
border: {
default: interpolateColor(
this.colors.Background,
this.colors.Gray,
DEFAULT_BORDER_OPACITY,
),
default: this.colors.DarkGray,
focused: this.colors.AccentBlue,
},
ui: {
@@ -242,10 +280,20 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
DarkGray:
customTheme.DarkGray ??
interpolateColor(
customTheme.text?.secondary ?? customTheme.Gray ?? '',
customTheme.background?.primary ?? customTheme.Background ?? '',
0.5,
customTheme.text?.secondary ?? customTheme.Gray ?? '',
DEFAULT_BORDER_OPACITY,
),
InputBackground: interpolateColor(
customTheme.background?.primary ?? customTheme.Background ?? '',
customTheme.text?.secondary ?? customTheme.Gray ?? '',
DEFAULT_INPUT_BACKGROUND_OPACITY,
),
MessageBackground: interpolateColor(
customTheme.background?.primary ?? customTheme.Background ?? '',
customTheme.text?.secondary ?? customTheme.Gray ?? '',
DEFAULT_BACKGROUND_OPACITY,
),
GradientColors: customTheme.ui?.gradient ?? customTheme.GradientColors,
};
@@ -400,19 +448,15 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
},
background: {
primary: customTheme.background?.primary ?? colors.Background,
message: colors.MessageBackground!,
input: colors.InputBackground!,
diff: {
added: customTheme.background?.diff?.added ?? colors.DiffAdded,
removed: customTheme.background?.diff?.removed ?? colors.DiffRemoved,
},
},
border: {
default:
customTheme.border?.default ??
interpolateColor(
colors.Background,
colors.Gray,
DEFAULT_BORDER_OPACITY,
),
default: colors.DarkGray,
focused: customTheme.border?.focused ?? colors.AccentBlue,
},
ui: {