mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-19 02:20:42 -07:00
feat: Detect background color (#15132)
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
interpolateColor,
|
||||
CSS_NAME_TO_HEX_MAP,
|
||||
INK_SUPPORTED_NAMES,
|
||||
getThemeTypeFromBackgroundColor,
|
||||
} from './color-utils.js';
|
||||
|
||||
describe('Color Utils', () => {
|
||||
@@ -255,4 +256,27 @@ describe('Color Utils', () => {
|
||||
expect(interpolateColor('#ffffff', '', 1)).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getThemeTypeFromBackgroundColor', () => {
|
||||
it('should return light for light backgrounds', () => {
|
||||
expect(getThemeTypeFromBackgroundColor('#ffffff')).toBe('light');
|
||||
expect(getThemeTypeFromBackgroundColor('#f0f0f0')).toBe('light');
|
||||
expect(getThemeTypeFromBackgroundColor('#cccccc')).toBe('light');
|
||||
});
|
||||
|
||||
it('should return dark for dark backgrounds', () => {
|
||||
expect(getThemeTypeFromBackgroundColor('#000000')).toBe('dark');
|
||||
expect(getThemeTypeFromBackgroundColor('#1a1a1a')).toBe('dark');
|
||||
expect(getThemeTypeFromBackgroundColor('#333333')).toBe('dark');
|
||||
});
|
||||
|
||||
it('should return undefined for undefined background', () => {
|
||||
expect(getThemeTypeFromBackgroundColor(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle colors without # prefix', () => {
|
||||
expect(getThemeTypeFromBackgroundColor('ffffff')).toBe('light');
|
||||
expect(getThemeTypeFromBackgroundColor('000000')).toBe('dark');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -251,3 +251,22 @@ export function interpolateColor(
|
||||
const color = gradient.rgbAt(factor);
|
||||
return color.toHexString();
|
||||
}
|
||||
|
||||
export function getThemeTypeFromBackgroundColor(
|
||||
backgroundColor: string | undefined,
|
||||
): 'light' | 'dark' | undefined {
|
||||
if (!backgroundColor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Parse hex color
|
||||
const hex = backgroundColor.replace(/^#/, '');
|
||||
const r = parseInt(hex.substring(0, 2), 16);
|
||||
const g = parseInt(hex.substring(2, 4), 16);
|
||||
const b = parseInt(hex.substring(4, 6), 16);
|
||||
|
||||
// Calculate luminance
|
||||
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
|
||||
return luminance > 128 ? 'light' : 'dark';
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { interpolateColor } from './color-utils.js';
|
||||
const shadesOfPurpleColors: ColorsTheme = {
|
||||
type: 'dark',
|
||||
// Required colors for ColorsTheme interface
|
||||
Background: '#2d2b57', // Main background
|
||||
Background: '#1e1e3f', // Main background in the VSCode terminal.
|
||||
Foreground: '#e3dfff', // Default text color (hljs, hljs-subst)
|
||||
LightBlue: '#847ace', // Light blue/purple accent
|
||||
AccentBlue: '#a599e9', // Borders, secondary blue
|
||||
|
||||
@@ -228,6 +228,14 @@ class ThemeManager {
|
||||
return this.findThemeByName(themeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all available themes.
|
||||
* @returns A list of all available themes.
|
||||
*/
|
||||
getAllThemes(): Theme[] {
|
||||
return [...this.availableThemes, ...Array.from(this.customThemes.values())];
|
||||
}
|
||||
|
||||
private isPath(themeName: string): boolean {
|
||||
return (
|
||||
themeName.endsWith('.json') ||
|
||||
|
||||
@@ -168,3 +168,42 @@ describe('themeManager.loadCustomThemes', () => {
|
||||
expect(result.name).toBe(legacyTheme.name);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pickDefaultThemeName', () => {
|
||||
const { pickDefaultThemeName } = themeModule;
|
||||
const mockThemes = [
|
||||
{ name: 'Dark Theme', type: 'dark', colors: { Background: '#000000' } },
|
||||
{ name: 'Light Theme', type: 'light', colors: { Background: '#ffffff' } },
|
||||
{ name: 'Blue Theme', type: 'dark', colors: { Background: '#0000ff' } },
|
||||
] as unknown as themeModule.Theme[];
|
||||
|
||||
it('should return exact match if found', () => {
|
||||
expect(
|
||||
pickDefaultThemeName('#0000ff', mockThemes, 'Dark Theme', 'Light Theme'),
|
||||
).toBe('Blue Theme');
|
||||
});
|
||||
|
||||
it('should return exact match (case insensitive)', () => {
|
||||
expect(
|
||||
pickDefaultThemeName('#FFFFFF', mockThemes, 'Dark Theme', 'Light Theme'),
|
||||
).toBe('Light Theme');
|
||||
});
|
||||
|
||||
it('should return default light theme for light background if no match', () => {
|
||||
expect(
|
||||
pickDefaultThemeName('#eeeeee', mockThemes, 'Dark Theme', 'Light Theme'),
|
||||
).toBe('Light Theme');
|
||||
});
|
||||
|
||||
it('should return default dark theme for dark background if no match', () => {
|
||||
expect(
|
||||
pickDefaultThemeName('#111111', mockThemes, 'Dark Theme', 'Light Theme'),
|
||||
).toBe('Dark Theme');
|
||||
});
|
||||
|
||||
it('should return default dark theme if background is undefined', () => {
|
||||
expect(
|
||||
pickDefaultThemeName(undefined, mockThemes, 'Dark Theme', 'Light Theme'),
|
||||
).toBe('Dark Theme');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
|
||||
import type { CSSProperties } from 'react';
|
||||
import type { SemanticColors } from './semantic-tokens.js';
|
||||
import { resolveColor, interpolateColor } from './color-utils.js';
|
||||
import {
|
||||
resolveColor,
|
||||
interpolateColor,
|
||||
getThemeTypeFromBackgroundColor,
|
||||
} from './color-utils.js';
|
||||
|
||||
export type ThemeType = 'light' | 'dark' | 'ansi' | 'custom';
|
||||
|
||||
@@ -499,3 +503,40 @@ function isValidThemeName(name: string): boolean {
|
||||
// Theme name should be non-empty and not contain invalid characters
|
||||
return name.trim().length > 0 && name.trim().length <= 50;
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks a default theme name based on terminal background color.
|
||||
* It first tries to find a theme with an exact background color match.
|
||||
* If no match is found, it falls back to a light or dark theme based on the
|
||||
* luminance of the background color.
|
||||
* @param terminalBackground The hex color string of the terminal background.
|
||||
* @param availableThemes A list of available themes to search through.
|
||||
* @param defaultDarkThemeName The name of the fallback dark theme.
|
||||
* @param defaultLightThemeName The name of the fallback light theme.
|
||||
* @returns The name of the chosen theme.
|
||||
*/
|
||||
export function pickDefaultThemeName(
|
||||
terminalBackground: string | undefined,
|
||||
availableThemes: readonly Theme[],
|
||||
defaultDarkThemeName: string,
|
||||
defaultLightThemeName: string,
|
||||
): string {
|
||||
if (terminalBackground) {
|
||||
const lowerTerminalBackground = terminalBackground.toLowerCase();
|
||||
for (const theme of availableThemes) {
|
||||
if (!theme.colors.Background) continue;
|
||||
// resolveColor can return undefined
|
||||
const themeBg = resolveColor(theme.colors.Background)?.toLowerCase();
|
||||
if (themeBg === lowerTerminalBackground) {
|
||||
return theme.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const themeType = getThemeTypeFromBackgroundColor(terminalBackground);
|
||||
if (themeType === 'light') {
|
||||
return defaultLightThemeName;
|
||||
}
|
||||
|
||||
return defaultDarkThemeName;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user