mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-22 03:51:22 -07:00
feat(ui): implement standardized selection background with interpolation
This commit is contained in:
@@ -25,6 +25,7 @@ describe('ColorsDisplay', () => {
|
||||
primary: '#000000',
|
||||
message: '#111111',
|
||||
input: '#222222',
|
||||
selection: '#333333',
|
||||
diff: {
|
||||
added: '#003300',
|
||||
removed: '#330000',
|
||||
|
||||
@@ -32,6 +32,30 @@ type ColorRow = StandardColorRow | GradientColorRow | BackgroundColorRow;
|
||||
|
||||
const VALUE_COLUMN_WIDTH = 10;
|
||||
|
||||
const COLOR_DESCRIPTIONS: Record<string, string> = {
|
||||
'text.primary': 'Primary text color (uses terminal default if blank)',
|
||||
'text.secondary': 'Secondary/dimmed text color',
|
||||
'text.link': 'Hyperlink and highlighting color',
|
||||
'text.accent': 'Accent color for emphasis',
|
||||
'text.response':
|
||||
'Color for model response text (uses terminal default if blank)',
|
||||
'background.primary': 'Main terminal background color',
|
||||
'background.message': 'Subtle background for message blocks',
|
||||
'background.input': 'Background for the input prompt',
|
||||
'background.selection': 'Background highlight for selected/focused items',
|
||||
'background.diff.added': 'Background for added lines in diffs',
|
||||
'background.diff.removed': 'Background for removed lines in diffs',
|
||||
'border.default': 'Standard border color',
|
||||
'border.focused': 'Border color when an element is focused',
|
||||
'ui.comment': 'Color for code comments and metadata',
|
||||
'ui.symbol': 'Color for technical symbols and UI icons',
|
||||
'ui.dark': 'Deeply dimmed color for subtle UI elements',
|
||||
'ui.focus': 'Color for focused or selected UI elements (e.g. menu items)',
|
||||
'status.error': 'Color for error messages and critical status',
|
||||
'status.success': 'Color for success messages and positive status',
|
||||
'status.warning': 'Color for warnings and cautionary status',
|
||||
};
|
||||
|
||||
interface ColorsDisplayProps {
|
||||
activeTheme: Theme;
|
||||
}
|
||||
@@ -179,20 +203,28 @@ export const ColorsDisplay: React.FC<ColorsDisplayProps> = ({
|
||||
function renderStandardRow({ name, value }: StandardColorRow) {
|
||||
const isHex = value.startsWith('#');
|
||||
const displayColor = isHex ? value : theme.text.primary;
|
||||
const description = COLOR_DESCRIPTIONS[name] || '';
|
||||
|
||||
return (
|
||||
<Box key={name} flexDirection="row" paddingX={1}>
|
||||
<Box width={VALUE_COLUMN_WIDTH}>
|
||||
<Text color={displayColor}>{value || '(blank)'}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Text color={displayColor}>{name}</Text>
|
||||
<Box flexGrow={1} flexDirection="row">
|
||||
<Box width="30%">
|
||||
<Text color={displayColor}>{name}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} paddingLeft={1}>
|
||||
<Text color={theme.text.secondary}>{description}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function renderGradientRow({ name, value }: GradientColorRow) {
|
||||
const description = COLOR_DESCRIPTIONS[name] || '';
|
||||
|
||||
return (
|
||||
<Box key={name} flexDirection="row" paddingX={1}>
|
||||
<Box width={VALUE_COLUMN_WIDTH} flexDirection="column">
|
||||
@@ -202,16 +234,23 @@ function renderGradientRow({ name, value }: GradientColorRow) {
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Gradient colors={value}>
|
||||
<Text>{name}</Text>
|
||||
</Gradient>
|
||||
<Box flexGrow={1} flexDirection="row">
|
||||
<Box width="30%">
|
||||
<Gradient colors={value}>
|
||||
<Text>{name}</Text>
|
||||
</Gradient>
|
||||
</Box>
|
||||
<Box flexGrow={1} paddingLeft={1}>
|
||||
<Text color={theme.text.secondary}>{description}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function renderBackgroundRow({ name, value }: BackgroundColorRow) {
|
||||
const description = COLOR_DESCRIPTIONS[name] || '';
|
||||
|
||||
return (
|
||||
<Box key={name} flexDirection="row" paddingX={1}>
|
||||
<Box
|
||||
@@ -224,8 +263,13 @@ function renderBackgroundRow({ name, value }: BackgroundColorRow) {
|
||||
{value || 'default'}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} paddingLeft={1}>
|
||||
<Text color={theme.text.primary}>{name}</Text>
|
||||
<Box flexGrow={1} flexDirection="row" paddingLeft={1}>
|
||||
<Box width="30%">
|
||||
<Text color={theme.text.primary}>{name}</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} paddingLeft={1}>
|
||||
<Text color={theme.text.secondary}>{description}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -98,6 +98,7 @@ describe('<Header />', () => {
|
||||
primary: '',
|
||||
message: '',
|
||||
input: '',
|
||||
selection: '',
|
||||
diff: { added: '', removed: '' },
|
||||
},
|
||||
border: {
|
||||
|
||||
@@ -484,7 +484,10 @@ const SessionItem = ({
|
||||
));
|
||||
|
||||
return (
|
||||
<Box flexDirection="row">
|
||||
<Box
|
||||
flexDirection="row"
|
||||
backgroundColor={isActive ? theme.background.selection : undefined}
|
||||
>
|
||||
<Text color={textColor()} dimColor={isDisabled}>
|
||||
{prefix}
|
||||
</Text>
|
||||
|
||||
@@ -97,7 +97,11 @@ export function SuggestionsDisplay({
|
||||
);
|
||||
|
||||
return (
|
||||
<Box key={`${suggestion.value}-${originalIndex}`} flexDirection="row">
|
||||
<Box
|
||||
key={`${suggestion.value}-${originalIndex}`}
|
||||
flexDirection="row"
|
||||
backgroundColor={isActive ? theme.background.selection : undefined}
|
||||
>
|
||||
<Box
|
||||
{...(mode === 'slash'
|
||||
? { width: commandColumnWidth, flexShrink: 0 as const }
|
||||
|
||||
@@ -18,43 +18,68 @@ exports[`Initial Theme Selection > should default to a dark theme when terminal
|
||||
│ 11. Ayu Light │ │ │
|
||||
│ 12. Default Light └─────────────────────────────────────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ╭────────────────────────────────────────────────╮ │
|
||||
│ │ DEVELOPER TOOLS (Not visible to users) │ │
|
||||
│ │ │ │
|
||||
│ │ How do colors get applied? │ │
|
||||
│ │ • Hex: Rendered exactly by modern terminals. │ │
|
||||
│ │ Not overridden by app themes. │ │
|
||||
│ │ • Blank: Uses your terminal's default │ │
|
||||
│ │ foreground/background. │ │
|
||||
│ │ • Compatibility: On older terminals, hex is │ │
|
||||
│ │ approximated to the nearest ANSI color. │ │
|
||||
│ │ • ANSI Names: 'red', 'green', etc. are │ │
|
||||
│ │ mapped to your terminal app's palette. │ │
|
||||
│ │ │ │
|
||||
│ │ Value Name │ │
|
||||
│ │ #1E1E2E background.primary │ │
|
||||
│ │ #2a2b3c background.message │ │
|
||||
│ │ #313243 background.input │ │
|
||||
│ │ #28350B background.diff.added │ │
|
||||
│ │ #430000 background.diff.removed │ │
|
||||
│ │ (blank) text.primary │ │
|
||||
│ │ #6C7086 text.secondary │ │
|
||||
│ │ #89B4FA text.link │ │
|
||||
│ │ #CBA6F7 text.accent │ │
|
||||
│ │ (blank) text.response │ │
|
||||
│ │ #3d3f51 border.default │ │
|
||||
│ │ #89B4FA border.focused │ │
|
||||
│ │ #6C7086 ui.comment │ │
|
||||
│ │ #89DCEB ui.symbol │ │
|
||||
│ │ #3d3f51 ui.dark │ │
|
||||
│ │ #89B4FA ui.focus │ │
|
||||
│ │ #F38BA8 status.error │ │
|
||||
│ │ #A6E3A1 status.success │ │
|
||||
│ │ #F9E2AF status.warning │ │
|
||||
│ │ #4796E4 ui.gradient │ │
|
||||
│ │ #847ACE │ │
|
||||
│ │ #C3677F │ │
|
||||
│ ╰────────────────────────────────────────────────╯ │
|
||||
│ ╭─────────────────────────────────────────────────╮ │
|
||||
│ │ DEVELOPER TOOLS (Not visible to users) │ │
|
||||
│ │ │ │
|
||||
│ │ How do colors get applied? │ │
|
||||
│ │ • Hex: Rendered exactly by modern terminals. │ │
|
||||
│ │ Not overridden by app themes. │ │
|
||||
│ │ • Blank: Uses your terminal's default │ │
|
||||
│ │ foreground/background. │ │
|
||||
│ │ • Compatibility: On older terminals, hex is │ │
|
||||
│ │ approximated to the nearest ANSI color. │ │
|
||||
│ │ • ANSI Names: 'red', 'green', etc. are mapped │ │
|
||||
│ │ to your terminal app's palette. │ │
|
||||
│ │ │ │
|
||||
│ │ Value Name │ │
|
||||
│ │ #1E1E… backgroun Main terminal background │ │
|
||||
│ │ d.primary color │ │
|
||||
│ │ #2a2… backgroun Subtle background for │ │
|
||||
│ │ d.message message blocks │ │
|
||||
│ │ #313… backgroun Background for the input │ │
|
||||
│ │ d.input prompt │ │
|
||||
│ │ #39… background. Background highlight for │ │
|
||||
│ │ selection selected/focused items │ │
|
||||
│ │ #283… backgrou Background for added lines │ │
|
||||
│ │ nd.diff. in diffs │ │
|
||||
│ │ added │ │
|
||||
│ │ #430… backgroun Background for removed │ │
|
||||
│ │ d.diff.re lines in diffs │ │
|
||||
│ │ moved │ │
|
||||
│ │ (blank text.prim Primary text color (uses │ │
|
||||
│ │ ) ary terminal default if blank) │ │
|
||||
│ │ #6C7086 text.secon Secondary/dimmed text │ │
|
||||
│ │ dary color │ │
|
||||
│ │ #89B4FA text.link Hyperlink and highlighting │ │
|
||||
│ │ color │ │
|
||||
│ │ #CBA6F7 text.accen Accent color for │ │
|
||||
│ │ t emphasis │ │
|
||||
│ │ (blank) text.res Color for model response │ │
|
||||
│ │ ponse text (uses terminal default │ │
|
||||
│ │ if blank) │ │
|
||||
│ │ #3d3f51 border.def Standard border color │ │
|
||||
│ │ ault │ │
|
||||
│ │ #89B4FAborder.fo Border color when an │ │
|
||||
│ │ cused element is focused │ │
|
||||
│ │ #6C7086ui.comme Color for code comments and │ │
|
||||
│ │ nt metadata │ │
|
||||
│ │ #89DCE ui.symbol Color for technical symbols │ │
|
||||
│ │ B and UI icons │ │
|
||||
│ │ #3d3f5 ui.dark Deeply dimmed color for │ │
|
||||
│ │ 1 subtle UI elements │ │
|
||||
│ │ #89B4F ui.focus Color for focused or │ │
|
||||
│ │ A selected UI elements (e.g. │ │
|
||||
│ │ menu items) │ │
|
||||
│ │ #F38BA8status.err Color for error messages │ │
|
||||
│ │ or and critical status │ │
|
||||
│ │ #A6E3A1status.suc Color for success messages │ │
|
||||
│ │ cess and positive status │ │
|
||||
│ │ #F9E2A status.wa Color for warnings and │ │
|
||||
│ │ F rning cautionary status │ │
|
||||
│ │ #4796E4 ui.gradien │ │
|
||||
│ │ #847ACE t │ │
|
||||
│ │ #C3677F │ │
|
||||
│ ╰─────────────────────────────────────────────────╯ │
|
||||
│ │
|
||||
│ (Use Enter to select, Tab to configure scope, Esc to close) │
|
||||
│ │
|
||||
@@ -80,43 +105,68 @@ exports[`Initial Theme Selection > should default to a light theme when terminal
|
||||
│ 11. Default Dark (Incompatible) │ │ │
|
||||
│ 12. Dracula Dark (Incompatible) └─────────────────────────────────────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ╭────────────────────────────────────────────────╮ │
|
||||
│ │ DEVELOPER TOOLS (Not visible to users) │ │
|
||||
│ │ │ │
|
||||
│ │ How do colors get applied? │ │
|
||||
│ │ • Hex: Rendered exactly by modern terminals. │ │
|
||||
│ │ Not overridden by app themes. │ │
|
||||
│ │ • Blank: Uses your terminal's default │ │
|
||||
│ │ foreground/background. │ │
|
||||
│ │ • Compatibility: On older terminals, hex is │ │
|
||||
│ │ approximated to the nearest ANSI color. │ │
|
||||
│ │ • ANSI Names: 'red', 'green', etc. are │ │
|
||||
│ │ mapped to your terminal app's palette. │ │
|
||||
│ │ │ │
|
||||
│ │ Value Name │ │
|
||||
│ │ #FAFAFA background.primary │ │
|
||||
│ │ #eaecee background.message │ │
|
||||
│ │ #e2e4e8 background.input │ │
|
||||
│ │ #C6EAD8 background.diff.added │ │
|
||||
│ │ #FFCCCC background.diff.removed │ │
|
||||
│ │ (blank) text.primary │ │
|
||||
│ │ #97a0b0 text.secondary │ │
|
||||
│ │ #3B82F6 text.link │ │
|
||||
│ │ #8B5CF6 text.accent │ │
|
||||
│ │ (blank) text.response │ │
|
||||
│ │ #d2d6dc border.default │ │
|
||||
│ │ #3B82F6 border.focused │ │
|
||||
│ │ #97a0b0 ui.comment │ │
|
||||
│ │ #06B6D4 ui.symbol │ │
|
||||
│ │ #d2d6dc ui.dark │ │
|
||||
│ │ #3B82F6 ui.focus │ │
|
||||
│ │ #DD4C4C status.error │ │
|
||||
│ │ #3CA84B status.success │ │
|
||||
│ │ #D5A40A status.warning │ │
|
||||
│ │ #4796E4 ui.gradient │ │
|
||||
│ │ #847ACE │ │
|
||||
│ │ #C3677F │ │
|
||||
│ ╰────────────────────────────────────────────────╯ │
|
||||
│ ╭─────────────────────────────────────────────────╮ │
|
||||
│ │ DEVELOPER TOOLS (Not visible to users) │ │
|
||||
│ │ │ │
|
||||
│ │ How do colors get applied? │ │
|
||||
│ │ • Hex: Rendered exactly by modern terminals. │ │
|
||||
│ │ Not overridden by app themes. │ │
|
||||
│ │ • Blank: Uses your terminal's default │ │
|
||||
│ │ foreground/background. │ │
|
||||
│ │ • Compatibility: On older terminals, hex is │ │
|
||||
│ │ approximated to the nearest ANSI color. │ │
|
||||
│ │ • ANSI Names: 'red', 'green', etc. are mapped │ │
|
||||
│ │ to your terminal app's palette. │ │
|
||||
│ │ │ │
|
||||
│ │ Value Name │ │
|
||||
│ │ #FAFA… backgroun Main terminal background │ │
|
||||
│ │ d.primary color │ │
|
||||
│ │ #eae… backgroun Subtle background for │ │
|
||||
│ │ d.message message blocks │ │
|
||||
│ │ #e2e… backgroun Background for the input │ │
|
||||
│ │ d.input prompt │ │
|
||||
│ │ #d4… background. Background highlight for │ │
|
||||
│ │ selection selected/focused items │ │
|
||||
│ │ #C6E… backgrou Background for added lines │ │
|
||||
│ │ nd.diff. in diffs │ │
|
||||
│ │ added │ │
|
||||
│ │ #FFC… backgroun Background for removed │ │
|
||||
│ │ d.diff.re lines in diffs │ │
|
||||
│ │ moved │ │
|
||||
│ │ (blank text.prim Primary text color (uses │ │
|
||||
│ │ ) ary terminal default if blank) │ │
|
||||
│ │ #97a0b0 text.secon Secondary/dimmed text │ │
|
||||
│ │ dary color │ │
|
||||
│ │ #3B82F6 text.link Hyperlink and highlighting │ │
|
||||
│ │ color │ │
|
||||
│ │ #8B5CF6 text.accen Accent color for │ │
|
||||
│ │ t emphasis │ │
|
||||
│ │ (blank) text.res Color for model response │ │
|
||||
│ │ ponse text (uses terminal default │ │
|
||||
│ │ if blank) │ │
|
||||
│ │ #d2d6dc border.def Standard border color │ │
|
||||
│ │ ault │ │
|
||||
│ │ #3B82F6border.fo Border color when an │ │
|
||||
│ │ cused element is focused │ │
|
||||
│ │ #97a0b0ui.comme Color for code comments and │ │
|
||||
│ │ nt metadata │ │
|
||||
│ │ #06B6D ui.symbol Color for technical symbols │ │
|
||||
│ │ 4 and UI icons │ │
|
||||
│ │ #d2d6d ui.dark Deeply dimmed color for │ │
|
||||
│ │ c subtle UI elements │ │
|
||||
│ │ #3B82F ui.focus Color for focused or │ │
|
||||
│ │ 6 selected UI elements (e.g. │ │
|
||||
│ │ menu items) │ │
|
||||
│ │ #DD4C4Cstatus.err Color for error messages │ │
|
||||
│ │ or and critical status │ │
|
||||
│ │ #3CA84Bstatus.suc Color for success messages │ │
|
||||
│ │ cess and positive status │ │
|
||||
│ │ #D5A40 status.wa Color for warnings and │ │
|
||||
│ │ A rning cautionary status │ │
|
||||
│ │ #4796E4 ui.gradien │ │
|
||||
│ │ #847ACE t │ │
|
||||
│ │ #C3677F │ │
|
||||
│ ╰─────────────────────────────────────────────────╯ │
|
||||
│ │
|
||||
│ (Use Enter to select, Tab to configure scope, Esc to close) │
|
||||
│ │
|
||||
@@ -142,43 +192,68 @@ exports[`Initial Theme Selection > should use the theme from settings even if te
|
||||
│ 11. Ayu Light │ │ │
|
||||
│ 12. Default Light └─────────────────────────────────────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ╭────────────────────────────────────────────────╮ │
|
||||
│ │ DEVELOPER TOOLS (Not visible to users) │ │
|
||||
│ │ │ │
|
||||
│ │ How do colors get applied? │ │
|
||||
│ │ • Hex: Rendered exactly by modern terminals. │ │
|
||||
│ │ Not overridden by app themes. │ │
|
||||
│ │ • Blank: Uses your terminal's default │ │
|
||||
│ │ foreground/background. │ │
|
||||
│ │ • Compatibility: On older terminals, hex is │ │
|
||||
│ │ approximated to the nearest ANSI color. │ │
|
||||
│ │ • ANSI Names: 'red', 'green', etc. are │ │
|
||||
│ │ mapped to your terminal app's palette. │ │
|
||||
│ │ │ │
|
||||
│ │ Value Name │ │
|
||||
│ │ #1E1E2E background.primary │ │
|
||||
│ │ #2a2b3c background.message │ │
|
||||
│ │ #313243 background.input │ │
|
||||
│ │ #28350B background.diff.added │ │
|
||||
│ │ #430000 background.diff.removed │ │
|
||||
│ │ (blank) text.primary │ │
|
||||
│ │ #6C7086 text.secondary │ │
|
||||
│ │ #89B4FA text.link │ │
|
||||
│ │ #CBA6F7 text.accent │ │
|
||||
│ │ (blank) text.response │ │
|
||||
│ │ #3d3f51 border.default │ │
|
||||
│ │ #89B4FA border.focused │ │
|
||||
│ │ #6C7086 ui.comment │ │
|
||||
│ │ #89DCEB ui.symbol │ │
|
||||
│ │ #3d3f51 ui.dark │ │
|
||||
│ │ #89B4FA ui.focus │ │
|
||||
│ │ #F38BA8 status.error │ │
|
||||
│ │ #A6E3A1 status.success │ │
|
||||
│ │ #F9E2AF status.warning │ │
|
||||
│ │ #4796E4 ui.gradient │ │
|
||||
│ │ #847ACE │ │
|
||||
│ │ #C3677F │ │
|
||||
│ ╰────────────────────────────────────────────────╯ │
|
||||
│ ╭─────────────────────────────────────────────────╮ │
|
||||
│ │ DEVELOPER TOOLS (Not visible to users) │ │
|
||||
│ │ │ │
|
||||
│ │ How do colors get applied? │ │
|
||||
│ │ • Hex: Rendered exactly by modern terminals. │ │
|
||||
│ │ Not overridden by app themes. │ │
|
||||
│ │ • Blank: Uses your terminal's default │ │
|
||||
│ │ foreground/background. │ │
|
||||
│ │ • Compatibility: On older terminals, hex is │ │
|
||||
│ │ approximated to the nearest ANSI color. │ │
|
||||
│ │ • ANSI Names: 'red', 'green', etc. are mapped │ │
|
||||
│ │ to your terminal app's palette. │ │
|
||||
│ │ │ │
|
||||
│ │ Value Name │ │
|
||||
│ │ #1E1E… backgroun Main terminal background │ │
|
||||
│ │ d.primary color │ │
|
||||
│ │ #2a2… backgroun Subtle background for │ │
|
||||
│ │ d.message message blocks │ │
|
||||
│ │ #313… backgroun Background for the input │ │
|
||||
│ │ d.input prompt │ │
|
||||
│ │ #39… background. Background highlight for │ │
|
||||
│ │ selection selected/focused items │ │
|
||||
│ │ #283… backgrou Background for added lines │ │
|
||||
│ │ nd.diff. in diffs │ │
|
||||
│ │ added │ │
|
||||
│ │ #430… backgroun Background for removed │ │
|
||||
│ │ d.diff.re lines in diffs │ │
|
||||
│ │ moved │ │
|
||||
│ │ (blank text.prim Primary text color (uses │ │
|
||||
│ │ ) ary terminal default if blank) │ │
|
||||
│ │ #6C7086 text.secon Secondary/dimmed text │ │
|
||||
│ │ dary color │ │
|
||||
│ │ #89B4FA text.link Hyperlink and highlighting │ │
|
||||
│ │ color │ │
|
||||
│ │ #CBA6F7 text.accen Accent color for │ │
|
||||
│ │ t emphasis │ │
|
||||
│ │ (blank) text.res Color for model response │ │
|
||||
│ │ ponse text (uses terminal default │ │
|
||||
│ │ if blank) │ │
|
||||
│ │ #3d3f51 border.def Standard border color │ │
|
||||
│ │ ault │ │
|
||||
│ │ #89B4FAborder.fo Border color when an │ │
|
||||
│ │ cused element is focused │ │
|
||||
│ │ #6C7086ui.comme Color for code comments and │ │
|
||||
│ │ nt metadata │ │
|
||||
│ │ #89DCE ui.symbol Color for technical symbols │ │
|
||||
│ │ B and UI icons │ │
|
||||
│ │ #3d3f5 ui.dark Deeply dimmed color for │ │
|
||||
│ │ 1 subtle UI elements │ │
|
||||
│ │ #89B4F ui.focus Color for focused or │ │
|
||||
│ │ A selected UI elements (e.g. │ │
|
||||
│ │ menu items) │ │
|
||||
│ │ #F38BA8status.err Color for error messages │ │
|
||||
│ │ or and critical status │ │
|
||||
│ │ #A6E3A1status.suc Color for success messages │ │
|
||||
│ │ cess and positive status │ │
|
||||
│ │ #F9E2A status.wa Color for warnings and │ │
|
||||
│ │ F rning cautionary status │ │
|
||||
│ │ #4796E4 ui.gradien │ │
|
||||
│ │ #847ACE t │ │
|
||||
│ │ #C3677F │ │
|
||||
│ ╰─────────────────────────────────────────────────╯ │
|
||||
│ │
|
||||
│ (Use Enter to select, Tab to configure scope, Esc to close) │
|
||||
│ │
|
||||
@@ -218,43 +293,66 @@ exports[`ThemeDialog Snapshots > should render correctly in theme selection mode
|
||||
│ 11. Ayu Light │ │ │
|
||||
│ 12. Default Light └─────────────────────────────────────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ╭────────────────────────────────────────────────╮ │
|
||||
│ │ DEVELOPER TOOLS (Not visible to users) │ │
|
||||
│ │ │ │
|
||||
│ │ How do colors get applied? │ │
|
||||
│ │ • Hex: Rendered exactly by modern terminals. │ │
|
||||
│ │ Not overridden by app themes. │ │
|
||||
│ │ • Blank: Uses your terminal's default │ │
|
||||
│ │ foreground/background. │ │
|
||||
│ │ • Compatibility: On older terminals, hex is │ │
|
||||
│ │ approximated to the nearest ANSI color. │ │
|
||||
│ │ • ANSI Names: 'red', 'green', etc. are │ │
|
||||
│ │ mapped to your terminal app's palette. │ │
|
||||
│ │ │ │
|
||||
│ │ Value Name │ │
|
||||
│ │ #1E1E2E background.primary │ │
|
||||
│ │ #2a2b3c background.message │ │
|
||||
│ │ #313243 background.input │ │
|
||||
│ │ #28350B background.diff.added │ │
|
||||
│ │ #430000 background.diff.removed │ │
|
||||
│ │ (blank) text.primary │ │
|
||||
│ │ #6C7086 text.secondary │ │
|
||||
│ │ #89B4FA text.link │ │
|
||||
│ │ #CBA6F7 text.accent │ │
|
||||
│ │ (blank) text.response │ │
|
||||
│ │ #3d3f51 border.default │ │
|
||||
│ │ #89B4FA border.focused │ │
|
||||
│ │ #6C7086 ui.comment │ │
|
||||
│ │ #6C7086 ui.symbol │ │
|
||||
│ │ #3d3f51 ui.dark │ │
|
||||
│ │ #89B4FA ui.focus │ │
|
||||
│ │ #F38BA8 status.error │ │
|
||||
│ │ #A6E3A1 status.success │ │
|
||||
│ │ #F9E2AF status.warning │ │
|
||||
│ │ #4796E4 ui.gradient │ │
|
||||
│ │ #847ACE │ │
|
||||
│ │ #C3677F │ │
|
||||
│ ╰────────────────────────────────────────────────╯ │
|
||||
│ ╭─────────────────────────────────────────────────╮ │
|
||||
│ │ DEVELOPER TOOLS (Not visible to users) │ │
|
||||
│ │ │ │
|
||||
│ │ How do colors get applied? │ │
|
||||
│ │ • Hex: Rendered exactly by modern terminals. │ │
|
||||
│ │ Not overridden by app themes. │ │
|
||||
│ │ • Blank: Uses your terminal's default │ │
|
||||
│ │ foreground/background. │ │
|
||||
│ │ • Compatibility: On older terminals, hex is │ │
|
||||
│ │ approximated to the nearest ANSI color. │ │
|
||||
│ │ • ANSI Names: 'red', 'green', etc. are mapped │ │
|
||||
│ │ to your terminal app's palette. │ │
|
||||
│ │ │ │
|
||||
│ │ Value Name │ │
|
||||
│ │ #1E1E… backgroun Main terminal background │ │
|
||||
│ │ d.primary color │ │
|
||||
│ │ #2a2… backgroun Subtle background for │ │
|
||||
│ │ d.message message blocks │ │
|
||||
│ │ #313… backgroun Background for the input │ │
|
||||
│ │ d.input prompt │ │
|
||||
│ │ #283… backgrou Background for added lines │ │
|
||||
│ │ nd.diff. in diffs │ │
|
||||
│ │ added │ │
|
||||
│ │ #430… backgroun Background for removed │ │
|
||||
│ │ d.diff.re lines in diffs │ │
|
||||
│ │ moved │ │
|
||||
│ │ (blank text.prim Primary text color (uses │ │
|
||||
│ │ ) ary terminal default if blank) │ │
|
||||
│ │ #6C7086 text.secon Secondary/dimmed text │ │
|
||||
│ │ dary color │ │
|
||||
│ │ #89B4FA text.link Hyperlink and highlighting │ │
|
||||
│ │ color │ │
|
||||
│ │ #CBA6F7 text.accen Accent color for │ │
|
||||
│ │ t emphasis │ │
|
||||
│ │ (blank) text.res Color for model response │ │
|
||||
│ │ ponse text (uses terminal default │ │
|
||||
│ │ if blank) │ │
|
||||
│ │ #3d3f51 border.def Standard border color │ │
|
||||
│ │ ault │ │
|
||||
│ │ #89B4FAborder.fo Border color when an │ │
|
||||
│ │ cused element is focused │ │
|
||||
│ │ #6C7086ui.comme Color for code comments and │ │
|
||||
│ │ nt metadata │ │
|
||||
│ │ #6C708 ui.symbol Color for technical symbols │ │
|
||||
│ │ 6 and UI icons │ │
|
||||
│ │ #3d3f5 ui.dark Deeply dimmed color for │ │
|
||||
│ │ 1 subtle UI elements │ │
|
||||
│ │ #89B4F ui.focus Color for focused or │ │
|
||||
│ │ A selected UI elements (e.g. │ │
|
||||
│ │ menu items) │ │
|
||||
│ │ #F38BA8status.err Color for error messages │ │
|
||||
│ │ or and critical status │ │
|
||||
│ │ #A6E3A1status.suc Color for success messages │ │
|
||||
│ │ cess and positive status │ │
|
||||
│ │ #F9E2A status.wa Color for warnings and │ │
|
||||
│ │ F rning cautionary status │ │
|
||||
│ │ #4796E4 ui.gradien │ │
|
||||
│ │ #847ACE t │ │
|
||||
│ │ #C3677F │ │
|
||||
│ ╰─────────────────────────────────────────────────╯ │
|
||||
│ │
|
||||
│ (Use Enter to select, Tab to configure scope, Esc to close) │
|
||||
│ │
|
||||
|
||||
@@ -137,7 +137,13 @@ export function BaseSelectionList<
|
||||
)}.`;
|
||||
|
||||
return (
|
||||
<Box key={item.key} alignItems="flex-start">
|
||||
<Box
|
||||
key={item.key}
|
||||
alignItems="flex-start"
|
||||
backgroundColor={
|
||||
isSelected ? theme.background.selection : undefined
|
||||
}
|
||||
>
|
||||
{/* Radio button indicator */}
|
||||
<Box minWidth={2} flexShrink={0}>
|
||||
<Text
|
||||
|
||||
@@ -514,7 +514,14 @@ export function BaseSettingsDialog({
|
||||
|
||||
return (
|
||||
<React.Fragment key={item.key}>
|
||||
<Box marginX={1} flexDirection="row" alignItems="flex-start">
|
||||
<Box
|
||||
marginX={1}
|
||||
flexDirection="row"
|
||||
alignItems="flex-start"
|
||||
backgroundColor={
|
||||
isActive ? theme.background.selection : undefined
|
||||
}
|
||||
>
|
||||
<Box minWidth={2} flexShrink={0}>
|
||||
<Text
|
||||
color={isActive ? theme.ui.focus : theme.text.secondary}
|
||||
|
||||
@@ -37,6 +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_SELECTION_OPACITY = 0.2;
|
||||
export const DEFAULT_BORDER_OPACITY = 0.4;
|
||||
|
||||
export const KEYBOARD_SHORTCUTS_URL =
|
||||
|
||||
@@ -23,6 +23,7 @@ const ansiColors: ColorsTheme = {
|
||||
Comment: 'gray',
|
||||
Gray: 'gray',
|
||||
DarkGray: 'gray',
|
||||
SelectionBackground: 'black',
|
||||
GradientColors: ['cyan', 'green'],
|
||||
};
|
||||
|
||||
|
||||
@@ -4,38 +4,25 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { debugLogger } from '@google/gemini-cli-core';
|
||||
import tinygradient from 'tinygradient';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import {
|
||||
resolveColor,
|
||||
interpolateColor,
|
||||
getThemeTypeFromBackgroundColor,
|
||||
INK_SUPPORTED_NAMES,
|
||||
INK_NAME_TO_HEX_MAP,
|
||||
getLuminance,
|
||||
CSS_NAME_TO_HEX_MAP,
|
||||
} from './theme.js';
|
||||
|
||||
// Define the set of Ink's named colors for quick lookup
|
||||
export const INK_SUPPORTED_NAMES = new Set([
|
||||
'black',
|
||||
'red',
|
||||
'green',
|
||||
'yellow',
|
||||
'blue',
|
||||
'cyan',
|
||||
'magenta',
|
||||
'white',
|
||||
'gray',
|
||||
'grey',
|
||||
'blackbright',
|
||||
'redbright',
|
||||
'greenbright',
|
||||
'yellowbright',
|
||||
'bluebright',
|
||||
'cyanbright',
|
||||
'magentabright',
|
||||
'whitebright',
|
||||
]);
|
||||
|
||||
// Use tinycolor's built-in names map for CSS colors, excluding ones Ink supports
|
||||
export const CSS_NAME_TO_HEX_MAP = Object.fromEntries(
|
||||
Object.entries(tinycolor.names)
|
||||
.filter(([name]) => !INK_SUPPORTED_NAMES.has(name))
|
||||
.map(([name, hex]) => [name, `#${hex}`]),
|
||||
);
|
||||
export {
|
||||
resolveColor,
|
||||
interpolateColor,
|
||||
getThemeTypeFromBackgroundColor,
|
||||
INK_SUPPORTED_NAMES,
|
||||
INK_NAME_TO_HEX_MAP,
|
||||
getLuminance,
|
||||
CSS_NAME_TO_HEX_MAP,
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a color string is valid (hex, Ink-supported color name, or CSS color name).
|
||||
@@ -66,45 +53,6 @@ export function isValidColor(color: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a CSS color value (name or hex) into an Ink-compatible color string.
|
||||
* @param colorValue The raw color string (e.g., 'blue', '#ff0000', 'darkkhaki').
|
||||
* @returns An Ink-compatible color string (hex or name), or undefined if not resolvable.
|
||||
*/
|
||||
export function resolveColor(colorValue: string): string | undefined {
|
||||
const lowerColor = colorValue.toLowerCase();
|
||||
|
||||
// 1. Check if it's already a hex code and valid
|
||||
if (lowerColor.startsWith('#')) {
|
||||
if (/^#[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/.test(colorValue)) {
|
||||
return lowerColor;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle hex codes without #
|
||||
if (/^[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/.test(colorValue)) {
|
||||
return `#${lowerColor}`;
|
||||
}
|
||||
|
||||
// 2. Check if it's an Ink supported name (lowercase)
|
||||
if (INK_SUPPORTED_NAMES.has(lowerColor)) {
|
||||
return lowerColor; // Use Ink name directly
|
||||
}
|
||||
|
||||
// 3. Check if it's a known CSS name we can map to hex
|
||||
if (CSS_NAME_TO_HEX_MAP[lowerColor]) {
|
||||
return CSS_NAME_TO_HEX_MAP[lowerColor]; // Use mapped hex
|
||||
}
|
||||
|
||||
// 4. Could not resolve
|
||||
debugLogger.warn(
|
||||
`[ColorUtils] Could not resolve color "${colorValue}" to an Ink-compatible format.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a "safe" background color to use in low-color terminals if the
|
||||
* terminal background is a standard black or white.
|
||||
@@ -132,73 +80,6 @@ export function getSafeLowColorBackground(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function interpolateColor(
|
||||
color1: string,
|
||||
color2: string,
|
||||
factor: number,
|
||||
) {
|
||||
if (factor <= 0 && color1) {
|
||||
return color1;
|
||||
}
|
||||
if (factor >= 1 && color2) {
|
||||
return color2;
|
||||
}
|
||||
if (!color1 || !color2) {
|
||||
return '';
|
||||
}
|
||||
const gradient = tinygradient(color1, color2);
|
||||
const color = gradient.rgbAt(factor);
|
||||
return color.toHexString();
|
||||
}
|
||||
|
||||
export function getThemeTypeFromBackgroundColor(
|
||||
backgroundColor: string | undefined,
|
||||
): 'light' | 'dark' | undefined {
|
||||
if (!backgroundColor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const resolvedColor = resolveColor(backgroundColor);
|
||||
if (!resolvedColor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const luminance = getLuminance(resolvedColor);
|
||||
return luminance > 128 ? 'light' : 'dark';
|
||||
}
|
||||
|
||||
// Mapping for ANSI bright colors that are not in tinycolor's standard CSS names
|
||||
export const INK_NAME_TO_HEX_MAP: Readonly<Record<string, string>> = {
|
||||
blackbright: '#555555',
|
||||
redbright: '#ff5555',
|
||||
greenbright: '#55ff55',
|
||||
yellowbright: '#ffff55',
|
||||
bluebright: '#5555ff',
|
||||
magentabright: '#ff55ff',
|
||||
cyanbright: '#55ffff',
|
||||
whitebright: '#ffffff',
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the relative luminance of a color.
|
||||
* See https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
*
|
||||
* @param color Color string (hex or Ink-supported name)
|
||||
* @returns Luminance value (0-255)
|
||||
*/
|
||||
export function getLuminance(color: string): number {
|
||||
const resolved = color.toLowerCase();
|
||||
const hex = INK_NAME_TO_HEX_MAP[resolved] || resolved;
|
||||
|
||||
const colorObj = tinycolor(hex);
|
||||
if (!colorObj.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// tinycolor returns 0-1, we need 0-255
|
||||
return colorObj.getLuminance() * 255;
|
||||
}
|
||||
|
||||
// Hysteresis thresholds to prevent flickering when the background color
|
||||
// is ambiguous (near the midpoint).
|
||||
export const LIGHT_THEME_LUMINANCE_THRESHOLD = 140;
|
||||
|
||||
@@ -26,6 +26,7 @@ const noColorColorsTheme: ColorsTheme = {
|
||||
DarkGray: '',
|
||||
InputBackground: '',
|
||||
MessageBackground: '',
|
||||
SelectionBackground: '',
|
||||
};
|
||||
|
||||
const noColorSemanticColors: SemanticColors = {
|
||||
@@ -40,6 +41,7 @@ const noColorSemanticColors: SemanticColors = {
|
||||
primary: '',
|
||||
message: '',
|
||||
input: '',
|
||||
selection: '',
|
||||
diff: {
|
||||
added: '',
|
||||
removed: '',
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface SemanticColors {
|
||||
primary: string;
|
||||
message: string;
|
||||
input: string;
|
||||
selection: string;
|
||||
diff: {
|
||||
added: string;
|
||||
removed: string;
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { type ColorsTheme, Theme } from './theme.js';
|
||||
import { type ColorsTheme, Theme, interpolateColor } from './theme.js';
|
||||
import { type SemanticColors } from './semantic-tokens.js';
|
||||
import { DEFAULT_SELECTION_OPACITY } from '../constants.js';
|
||||
|
||||
const solarizedDarkColors: ColorsTheme = {
|
||||
type: 'dark',
|
||||
@@ -38,6 +39,11 @@ const semanticColors: SemanticColors = {
|
||||
primary: '#002b36',
|
||||
message: '#073642',
|
||||
input: '#073642',
|
||||
selection: interpolateColor(
|
||||
'#002b36',
|
||||
'#859900',
|
||||
DEFAULT_SELECTION_OPACITY,
|
||||
),
|
||||
diff: {
|
||||
added: '#00382f',
|
||||
removed: '#3d0115',
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { type ColorsTheme, Theme } from './theme.js';
|
||||
import { type ColorsTheme, Theme, interpolateColor } from './theme.js';
|
||||
import { type SemanticColors } from './semantic-tokens.js';
|
||||
import { DEFAULT_SELECTION_OPACITY } from '../constants.js';
|
||||
|
||||
const solarizedLightColors: ColorsTheme = {
|
||||
type: 'light',
|
||||
@@ -38,6 +39,11 @@ const semanticColors: SemanticColors = {
|
||||
primary: '#fdf6e3',
|
||||
message: '#eee8d5',
|
||||
input: '#eee8d5',
|
||||
selection: interpolateColor(
|
||||
'#fdf6e3',
|
||||
'#859900',
|
||||
DEFAULT_SELECTION_OPACITY,
|
||||
),
|
||||
diff: {
|
||||
added: '#d7f2d7',
|
||||
removed: '#f2d7d7',
|
||||
|
||||
@@ -22,16 +22,16 @@ import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import type { Theme, ThemeType, ColorsTheme } from './theme.js';
|
||||
import type { CustomTheme } from '@google/gemini-cli-core';
|
||||
import { createCustomTheme, validateCustomTheme } from './theme.js';
|
||||
import type { SemanticColors } from './semantic-tokens.js';
|
||||
import {
|
||||
import { createCustomTheme, validateCustomTheme ,
|
||||
interpolateColor,
|
||||
getThemeTypeFromBackgroundColor,
|
||||
resolveColor,
|
||||
} from './color-utils.js';
|
||||
} from './theme.js';
|
||||
import type { SemanticColors } from './semantic-tokens.js';
|
||||
import {
|
||||
DEFAULT_BACKGROUND_OPACITY,
|
||||
DEFAULT_INPUT_BACKGROUND_OPACITY,
|
||||
DEFAULT_SELECTION_OPACITY,
|
||||
DEFAULT_BORDER_OPACITY,
|
||||
} from '../constants.js';
|
||||
import { ANSI } from './ansi.js';
|
||||
@@ -369,6 +369,11 @@ class ThemeManager {
|
||||
colors.Gray,
|
||||
DEFAULT_BACKGROUND_OPACITY,
|
||||
),
|
||||
SelectionBackground: interpolateColor(
|
||||
this.terminalBackground,
|
||||
colors.AccentGreen,
|
||||
DEFAULT_SELECTION_OPACITY,
|
||||
),
|
||||
};
|
||||
} else {
|
||||
this.cachedColors = colors;
|
||||
@@ -402,6 +407,7 @@ class ThemeManager {
|
||||
primary: this.terminalBackground,
|
||||
message: colors.MessageBackground!,
|
||||
input: colors.InputBackground!,
|
||||
selection: colors.SelectionBackground!,
|
||||
},
|
||||
border: {
|
||||
...semanticColors.border,
|
||||
|
||||
@@ -32,6 +32,7 @@ describe('createCustomTheme', () => {
|
||||
DiffRemoved: '#FF0000',
|
||||
Comment: '#808080',
|
||||
Gray: '#cccccc',
|
||||
SelectionBackground: '#004000',
|
||||
// DarkGray intentionally omitted to test fallback
|
||||
};
|
||||
|
||||
@@ -103,6 +104,7 @@ describe('validateCustomTheme', () => {
|
||||
DiffRemoved: '#FF0000',
|
||||
Comment: '#808080',
|
||||
Gray: '#808080',
|
||||
SelectionBackground: '#004000',
|
||||
};
|
||||
|
||||
it('should return isValid: true for a valid theme', () => {
|
||||
@@ -153,6 +155,7 @@ describe('themeManager.loadCustomThemes', () => {
|
||||
AccentRed: '#F00',
|
||||
Comment: '#888',
|
||||
Gray: '#888',
|
||||
SelectionBackground: '#040',
|
||||
};
|
||||
|
||||
it('should use values from DEFAULT_THEME when DiffAdded and DiffRemoved are not provided', () => {
|
||||
|
||||
@@ -18,8 +18,150 @@ import type { CustomTheme } from '@google/gemini-cli-core';
|
||||
import {
|
||||
DEFAULT_BACKGROUND_OPACITY,
|
||||
DEFAULT_INPUT_BACKGROUND_OPACITY,
|
||||
DEFAULT_SELECTION_OPACITY,
|
||||
DEFAULT_BORDER_OPACITY,
|
||||
} from '../constants.js';
|
||||
import tinygradient from 'tinygradient';
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
// Define the set of Ink's named colors for quick lookup
|
||||
export const INK_SUPPORTED_NAMES = new Set([
|
||||
'black',
|
||||
'red',
|
||||
'green',
|
||||
'yellow',
|
||||
'blue',
|
||||
'cyan',
|
||||
'magenta',
|
||||
'white',
|
||||
'gray',
|
||||
'grey',
|
||||
'blackbright',
|
||||
'redbright',
|
||||
'greenbright',
|
||||
'yellowbright',
|
||||
'bluebright',
|
||||
'cyanbright',
|
||||
'magentabright',
|
||||
'whitebright',
|
||||
]);
|
||||
|
||||
// Use tinycolor's built-in names map for CSS colors, excluding ones Ink supports
|
||||
export const CSS_NAME_TO_HEX_MAP = Object.fromEntries(
|
||||
Object.entries(tinycolor.names)
|
||||
.filter(([name]) => !INK_SUPPORTED_NAMES.has(name))
|
||||
.map(([name, hex]) => [name, `#${hex}`]),
|
||||
);
|
||||
|
||||
// Mapping for ANSI bright colors that are not in tinycolor's standard CSS names
|
||||
export const INK_NAME_TO_HEX_MAP: Readonly<Record<string, string>> = {
|
||||
blackbright: '#555555',
|
||||
redbright: '#ff5555',
|
||||
greenbright: '#55ff55',
|
||||
yellowbright: '#ffff55',
|
||||
bluebright: '#5555ff',
|
||||
magentabright: '#ff55ff',
|
||||
cyanbright: '#55ffff',
|
||||
whitebright: '#ffffff',
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the relative luminance of a color.
|
||||
* See https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
*
|
||||
* @param color Color string (hex or Ink-supported name)
|
||||
* @returns Luminance value (0-255)
|
||||
*/
|
||||
export function getLuminance(color: string): number {
|
||||
const resolved = color.toLowerCase();
|
||||
const hex = INK_NAME_TO_HEX_MAP[resolved] || resolved;
|
||||
|
||||
const colorObj = tinycolor(hex);
|
||||
if (!colorObj.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// tinycolor returns 0-1, we need 0-255
|
||||
return colorObj.getLuminance() * 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a CSS color value (name or hex) into an Ink-compatible color string.
|
||||
* @param colorValue The raw color string (e.g., 'blue', '#ff0000', 'darkkhaki').
|
||||
* @returns An Ink-compatible color string (hex or name), or undefined if not resolvable.
|
||||
*/
|
||||
export function resolveColor(colorValue: string): string | undefined {
|
||||
const lowerColor = colorValue.toLowerCase();
|
||||
|
||||
// 1. Check if it's already a hex code and valid
|
||||
if (lowerColor.startsWith('#')) {
|
||||
if (/^#[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/.test(colorValue)) {
|
||||
return lowerColor;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle hex codes without #
|
||||
if (/^[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/.test(colorValue)) {
|
||||
return `#${lowerColor}`;
|
||||
}
|
||||
|
||||
// 2. Check if it's an Ink supported name (lowercase)
|
||||
if (INK_SUPPORTED_NAMES.has(lowerColor)) {
|
||||
return lowerColor; // Use Ink name directly
|
||||
}
|
||||
|
||||
// 3. Check if it's a known CSS name we can map to hex
|
||||
// We can't import CSS_NAME_TO_HEX_MAP here due to circular deps,
|
||||
// but we can use tinycolor directly for named colors.
|
||||
const colorObj = tinycolor(lowerColor);
|
||||
if (colorObj.isValid()) {
|
||||
return colorObj.toHexString();
|
||||
}
|
||||
|
||||
// 4. Could not resolve
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function interpolateColor(
|
||||
color1: string,
|
||||
color2: string,
|
||||
factor: number,
|
||||
) {
|
||||
if (factor <= 0 && color1) {
|
||||
return color1;
|
||||
}
|
||||
if (factor >= 1 && color2) {
|
||||
return color2;
|
||||
}
|
||||
if (!color1 || !color2) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const gradient = tinygradient(color1, color2);
|
||||
const color = gradient.rgbAt(factor);
|
||||
return color.toHexString();
|
||||
} catch (_e) {
|
||||
return color1;
|
||||
}
|
||||
}
|
||||
|
||||
export function getThemeTypeFromBackgroundColor(
|
||||
backgroundColor: string | undefined,
|
||||
): 'light' | 'dark' | undefined {
|
||||
if (!backgroundColor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const resolvedColor = resolveColor(backgroundColor);
|
||||
if (!resolvedColor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const luminance = getLuminance(resolvedColor);
|
||||
return luminance > 128 ? 'light' : 'dark';
|
||||
}
|
||||
|
||||
export type { CustomTheme };
|
||||
|
||||
@@ -43,6 +185,7 @@ export interface ColorsTheme {
|
||||
DarkGray: string;
|
||||
InputBackground?: string;
|
||||
MessageBackground?: string;
|
||||
SelectionBackground?: string;
|
||||
GradientColors?: string[];
|
||||
}
|
||||
|
||||
@@ -72,6 +215,11 @@ export const lightTheme: ColorsTheme = {
|
||||
'#97a0b0',
|
||||
DEFAULT_BACKGROUND_OPACITY,
|
||||
),
|
||||
SelectionBackground: interpolateColor(
|
||||
'#FAFAFA',
|
||||
'#3CA84B',
|
||||
DEFAULT_SELECTION_OPACITY,
|
||||
),
|
||||
GradientColors: ['#4796E4', '#847ACE', '#C3677F'],
|
||||
};
|
||||
|
||||
@@ -101,6 +249,11 @@ export const darkTheme: ColorsTheme = {
|
||||
'#6C7086',
|
||||
DEFAULT_BACKGROUND_OPACITY,
|
||||
),
|
||||
SelectionBackground: interpolateColor(
|
||||
'#1E1E2E',
|
||||
'#A6E3A1',
|
||||
DEFAULT_SELECTION_OPACITY,
|
||||
),
|
||||
GradientColors: ['#4796E4', '#847ACE', '#C3677F'],
|
||||
};
|
||||
|
||||
@@ -122,6 +275,7 @@ export const ansiTheme: ColorsTheme = {
|
||||
DarkGray: 'gray',
|
||||
InputBackground: 'black',
|
||||
MessageBackground: 'black',
|
||||
SelectionBackground: 'black',
|
||||
};
|
||||
|
||||
export class Theme {
|
||||
@@ -173,6 +327,13 @@ export class Theme {
|
||||
this.colors.Gray,
|
||||
DEFAULT_INPUT_BACKGROUND_OPACITY,
|
||||
),
|
||||
selection:
|
||||
this.colors.SelectionBackground ??
|
||||
interpolateColor(
|
||||
this.colors.Background,
|
||||
this.colors.AccentGreen,
|
||||
DEFAULT_SELECTION_OPACITY,
|
||||
),
|
||||
diff: {
|
||||
added: this.colors.DiffAdded,
|
||||
removed: this.colors.DiffRemoved,
|
||||
@@ -295,6 +456,11 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
|
||||
customTheme.text?.secondary ?? customTheme.Gray ?? '',
|
||||
DEFAULT_BACKGROUND_OPACITY,
|
||||
),
|
||||
SelectionBackground: interpolateColor(
|
||||
customTheme.background?.primary ?? customTheme.Background ?? '',
|
||||
customTheme.status?.success ?? customTheme.AccentGreen ?? '#3CA84B', // Fallback to a default green if not found
|
||||
DEFAULT_SELECTION_OPACITY,
|
||||
),
|
||||
GradientColors: customTheme.ui?.gradient ?? customTheme.GradientColors,
|
||||
};
|
||||
|
||||
@@ -451,6 +617,7 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
|
||||
primary: customTheme.background?.primary ?? colors.Background,
|
||||
message: colors.MessageBackground!,
|
||||
input: colors.InputBackground!,
|
||||
selection: colors.SelectionBackground!,
|
||||
diff: {
|
||||
added: customTheme.background?.diff?.added ?? colors.DiffAdded,
|
||||
removed: customTheme.background?.diff?.removed ?? colors.DiffRemoved,
|
||||
|
||||
Reference in New Issue
Block a user