Add DarkGray to the ColorTheme. (#12420)

This commit is contained in:
Jacob Richman
2025-11-01 16:50:51 -07:00
committed by GitHub
parent e3262f8766
commit d7243fb81f
20 changed files with 147 additions and 4 deletions

View File

@@ -50,6 +50,9 @@ export const Colors: ColorsTheme = {
get Gray() {
return themeManager.getActiveTheme().colors.Gray;
},
get DarkGray() {
return themeManager.getActiveTheme().colors.DarkGray;
},
get GradientColors() {
return themeManager.getActiveTheme().colors.GradientColors;
},

View File

@@ -22,6 +22,7 @@ const ansiLightColors: ColorsTheme = {
DiffRemoved: '#FFE5E5',
Comment: 'gray',
Gray: 'gray',
DarkGray: 'gray',
GradientColors: ['blue', 'green'],
};

View File

@@ -22,6 +22,7 @@ const ansiColors: ColorsTheme = {
DiffRemoved: '#4D0000',
Comment: 'gray',
Gray: 'gray',
DarkGray: 'gray',
GradientColors: ['cyan', 'green'],
};

View File

@@ -5,6 +5,7 @@
*/
import { type ColorsTheme, Theme } from './theme.js';
import { interpolateColor } from './color-utils.js';
const atomOneDarkColors: ColorsTheme = {
type: 'dark',
@@ -21,6 +22,7 @@ const atomOneDarkColors: ColorsTheme = {
DiffRemoved: '#562B2F',
Comment: '#5c6370',
Gray: '#5c6370',
DarkGray: interpolateColor('#5c6370', '#282c34', 0.5),
GradientColors: ['#61aeee', '#98c379'],
};

View File

@@ -5,6 +5,7 @@
*/
import { type ColorsTheme, Theme } from './theme.js';
import { interpolateColor } from './color-utils.js';
const ayuLightColors: ColorsTheme = {
type: 'light',
@@ -21,6 +22,7 @@ const ayuLightColors: ColorsTheme = {
DiffRemoved: '#FFCCCC',
Comment: '#ABADB1',
Gray: '#a6aaaf',
DarkGray: interpolateColor('#a6aaaf', '#f8f9fa', 0.5),
GradientColors: ['#399ee6', '#86b300'],
};

View File

@@ -5,6 +5,7 @@
*/
import { type ColorsTheme, Theme } from './theme.js';
import { interpolateColor } from './color-utils.js';
const ayuDarkColors: ColorsTheme = {
type: 'dark',
@@ -21,6 +22,7 @@ const ayuDarkColors: ColorsTheme = {
DiffRemoved: '#3D1215',
Comment: '#646A71',
Gray: '#3D4149',
DarkGray: interpolateColor('#3D4149', '#0b0e14', 0.5),
GradientColors: ['#FFB454', '#F26D78'],
};

View File

@@ -8,6 +8,7 @@ import { describe, it, expect } from 'vitest';
import {
isValidColor,
resolveColor,
interpolateColor,
CSS_NAME_TO_HEX_MAP,
INK_SUPPORTED_NAMES,
} from './color-utils.js';
@@ -218,4 +219,19 @@ describe('Color Utils', () => {
}
});
});
describe('interpolateColor', () => {
it('should interpolate between two colors', () => {
// Midpoint between black (#000000) and white (#ffffff) should be gray
expect(interpolateColor('#000000', '#ffffff', 0.5)).toBe('#7f7f7f');
});
it('should return start color when factor is 0', () => {
expect(interpolateColor('#ff0000', '#0000ff', 0)).toBe('#ff0000');
});
it('should return end color when factor is 1', () => {
expect(interpolateColor('#ff0000', '#0000ff', 1)).toBe('#0000ff');
});
});
});

View File

@@ -5,6 +5,7 @@
*/
import { debugLogger } from '@google/gemini-cli-core';
import tinygradient from 'tinygradient';
// Mapping from common CSS color names (lowercase) to hex codes (lowercase)
// Excludes names directly supported by Ink
@@ -231,3 +232,13 @@ export function resolveColor(colorValue: string): string | undefined {
);
return undefined;
}
export function interpolateColor(
color1: string,
color2: string,
factor: number,
) {
const gradient = tinygradient(color1, color2);
const color = gradient.rgbAt(factor);
return color.toHexString();
}

View File

@@ -5,6 +5,7 @@
*/
import { type ColorsTheme, Theme } from './theme.js';
import { interpolateColor } from './color-utils.js';
const draculaColors: ColorsTheme = {
type: 'dark',
@@ -21,6 +22,7 @@ const draculaColors: ColorsTheme = {
DiffRemoved: '#6e1818',
Comment: '#6272a4',
Gray: '#6272a4',
DarkGray: interpolateColor('#6272a4', '#282a36', 0.5),
GradientColors: ['#ff79c6', '#8be9fd'],
};

View File

@@ -5,6 +5,7 @@
*/
import { type ColorsTheme, Theme } from './theme.js';
import { interpolateColor } from './color-utils.js';
const githubDarkColors: ColorsTheme = {
type: 'dark',
@@ -21,6 +22,7 @@ const githubDarkColors: ColorsTheme = {
DiffRemoved: '#502125',
Comment: '#6A737D',
Gray: '#6A737D',
DarkGray: interpolateColor('#6A737D', '#24292e', 0.5),
GradientColors: ['#79B8FF', '#85E89D'],
};

View File

@@ -5,6 +5,7 @@
*/
import { type ColorsTheme, Theme } from './theme.js';
import { interpolateColor } from './color-utils.js';
const githubLightColors: ColorsTheme = {
type: 'light',
@@ -21,6 +22,7 @@ const githubLightColors: ColorsTheme = {
DiffRemoved: '#FFCCCC',
Comment: '#998',
Gray: '#999',
DarkGray: interpolateColor('#999', '#f8f8f8', 0.5),
GradientColors: ['#458', '#008080'],
};

View File

@@ -5,6 +5,7 @@
*/
import { type ColorsTheme, Theme, lightTheme } from './theme.js';
import { interpolateColor } from './color-utils.js';
const googleCodeColors: ColorsTheme = {
type: 'light',
@@ -21,6 +22,7 @@ const googleCodeColors: ColorsTheme = {
DiffRemoved: '#FEDEDE',
Comment: '#5f6368',
Gray: lightTheme.Gray,
DarkGray: interpolateColor(lightTheme.Gray, '#ffffff', 0.5),
GradientColors: ['#066', '#606'],
};

View File

@@ -23,6 +23,7 @@ const noColorColorsTheme: ColorsTheme = {
DiffRemoved: '',
Comment: '',
Gray: '',
DarkGray: '',
};
const noColorSemanticColors: SemanticColors = {
@@ -46,6 +47,7 @@ const noColorSemanticColors: SemanticColors = {
ui: {
comment: '',
symbol: '',
dark: '',
gradient: [],
},
status: {

View File

@@ -27,6 +27,7 @@ export interface SemanticColors {
ui: {
comment: string;
symbol: string;
dark: string;
gradient: string[] | undefined;
};
status: {
@@ -57,6 +58,7 @@ export const lightSemanticColors: SemanticColors = {
ui: {
comment: lightTheme.Comment,
symbol: lightTheme.Gray,
dark: lightTheme.DarkGray,
gradient: lightTheme.GradientColors,
},
status: {
@@ -87,6 +89,7 @@ export const darkSemanticColors: SemanticColors = {
ui: {
comment: darkTheme.Comment,
symbol: darkTheme.Gray,
dark: darkTheme.DarkGray,
gradient: darkTheme.GradientColors,
},
status: {
@@ -117,6 +120,7 @@ export const ansiSemanticColors: SemanticColors = {
ui: {
comment: ansiTheme.Comment,
symbol: ansiTheme.Gray,
dark: ansiTheme.DarkGray,
gradient: ansiTheme.GradientColors,
},
status: {

View File

@@ -9,6 +9,7 @@
* @author Ahmad Awais <https://twitter.com/mrahmadawais/>
*/
import { type ColorsTheme, Theme } from './theme.js';
import { interpolateColor } from './color-utils.js';
const shadesOfPurpleColors: ColorsTheme = {
type: 'dark',
@@ -26,6 +27,7 @@ const shadesOfPurpleColors: ColorsTheme = {
DiffRemoved: '#572244',
Comment: '#B362FF', // Comment color (same as AccentPurple)
Gray: '#726c86', // Gray color
DarkGray: interpolateColor('#726c86', '#2d2b57', 0.5),
GradientColors: ['#4d21fc', '#847ace', '#ff628c'],
};

View File

@@ -8,9 +8,80 @@ import { describe, it, expect } from 'vitest';
import * as themeModule from './theme.js';
import { themeManager } from './theme-manager.js';
const { validateCustomTheme } = themeModule;
const { validateCustomTheme, createCustomTheme } = themeModule;
type CustomTheme = themeModule.CustomTheme;
describe('createCustomTheme', () => {
const baseTheme: CustomTheme = {
type: 'custom',
name: 'Test Theme',
Background: '#000000',
Foreground: '#ffffff',
LightBlue: '#ADD8E6',
AccentBlue: '#0000FF',
AccentPurple: '#800080',
AccentCyan: '#00FFFF',
AccentGreen: '#008000',
AccentYellow: '#FFFF00',
AccentRed: '#FF0000',
DiffAdded: '#00FF00',
DiffRemoved: '#FF0000',
Comment: '#808080',
Gray: '#cccccc',
// DarkGray intentionally omitted to test fallback
};
it('should interpolate DarkGray when not provided', () => {
const theme = createCustomTheme(baseTheme);
// Interpolate between Gray (#cccccc) and Background (#000000) at 0.5
// #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');
});
it('should use provided DarkGray', () => {
const theme = createCustomTheme({
...baseTheme,
DarkGray: '#123456',
});
expect(theme.colors.DarkGray).toBe('#123456');
});
it('should interpolate DarkGray when text.secondary is provided but DarkGray is not', () => {
const customTheme: CustomTheme = {
type: 'custom',
name: 'Test',
text: {
secondary: '#cccccc', // Gray source
},
background: {
primary: '#000000', // Background source
},
};
const theme = createCustomTheme(customTheme);
// Should be interpolated between #cccccc and #000000 at 0.5 -> #666666
expect(theme.colors.DarkGray).toBe('#666666');
});
it('should prefer text.secondary over Gray for interpolation', () => {
const customTheme: CustomTheme = {
type: 'custom',
name: 'Test',
text: {
secondary: '#cccccc', // Should be used
},
Gray: '#aaaaaa', // Should be ignored
background: {
primary: '#000000',
},
};
const theme = createCustomTheme(customTheme);
// Interpolate between #cccccc and #000000 -> #666666
expect(theme.colors.DarkGray).toBe('#666666');
});
});
describe('validateCustomTheme', () => {
const validTheme: CustomTheme = {
type: 'custom',

View File

@@ -6,7 +6,7 @@
import type { CSSProperties } from 'react';
import type { SemanticColors } from './semantic-tokens.js';
import { resolveColor } from './color-utils.js';
import { resolveColor, interpolateColor } from './color-utils.js';
export type ThemeType = 'light' | 'dark' | 'ansi' | 'custom';
@@ -25,6 +25,7 @@ export interface ColorsTheme {
DiffRemoved: string;
Comment: string;
Gray: string;
DarkGray: string;
GradientColors?: string[];
}
@@ -74,13 +75,14 @@ export interface CustomTheme {
DiffRemoved?: string;
Comment?: string;
Gray?: string;
DarkGray?: string;
GradientColors?: string[];
}
export const lightTheme: ColorsTheme = {
type: 'light',
Background: '#FAFAFA',
Foreground: '',
Foreground: '#383A42',
LightBlue: '#89BDCD',
AccentBlue: '#3B82F6',
AccentPurple: '#8B5CF6',
@@ -92,13 +94,14 @@ export const lightTheme: ColorsTheme = {
DiffRemoved: '#FFCCCC',
Comment: '#008000',
Gray: '#97a0b0',
DarkGray: interpolateColor('#97a0b0', '#FAFAFA', 0.5),
GradientColors: ['#4796E4', '#847ACE', '#C3677F'],
};
export const darkTheme: ColorsTheme = {
type: 'dark',
Background: '#1E1E2E',
Foreground: '',
Foreground: '#CDD6F4',
LightBlue: '#ADD8E6',
AccentBlue: '#89B4FA',
AccentPurple: '#CBA6F7',
@@ -110,6 +113,7 @@ export const darkTheme: ColorsTheme = {
DiffRemoved: '#430000',
Comment: '#6C7086',
Gray: '#6C7086',
DarkGray: interpolateColor('#6C7086', '#1E1E2E', 0.5),
GradientColors: ['#4796E4', '#847ACE', '#C3677F'],
};
@@ -128,6 +132,7 @@ export const ansiTheme: ColorsTheme = {
DiffRemoved: 'red',
Comment: 'gray',
Gray: 'gray',
DarkGray: 'gray',
};
export class Theme {
@@ -176,6 +181,7 @@ export class Theme {
ui: {
comment: this.colors.Gray,
symbol: this.colors.AccentCyan,
dark: this.colors.DarkGray,
gradient: this.colors.GradientColors,
},
status: {
@@ -267,6 +273,13 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
customTheme.background?.diff?.removed ?? customTheme.DiffRemoved ?? '',
Comment: customTheme.ui?.comment ?? customTheme.Comment ?? '',
Gray: customTheme.text?.secondary ?? customTheme.Gray ?? '',
DarkGray:
customTheme.DarkGray ??
interpolateColor(
customTheme.text?.secondary ?? customTheme.Gray ?? '',
customTheme.background?.primary ?? customTheme.Background ?? '',
0.5,
),
GradientColors: customTheme.ui?.gradient ?? customTheme.GradientColors,
};
@@ -429,6 +442,7 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
ui: {
comment: customTheme.ui?.comment ?? colors.Comment,
symbol: customTheme.ui?.symbol ?? colors.Gray,
dark: colors.DarkGray,
gradient: customTheme.ui?.gradient ?? colors.GradientColors,
},
status: {

View File

@@ -5,6 +5,7 @@
*/
import { type ColorsTheme, Theme } from './theme.js';
import { interpolateColor } from './color-utils.js';
const xcodeColors: ColorsTheme = {
type: 'light',
@@ -21,6 +22,7 @@ const xcodeColors: ColorsTheme = {
DiffRemoved: '#FEDEDE',
Comment: '#007400',
Gray: '#c0c0c0',
DarkGray: interpolateColor('#c0c0c0', '#fff', 0.5),
GradientColors: ['#1c00cf', '#007400'],
};