Use semantic colors in themes (#5796)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
parent
4f2974dbfe
commit
785ee5d59a
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { Colors } from '../colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
import { shortenPath, tildeifyPath } from '@google/gemini-cli-core';
|
import { shortenPath, tildeifyPath } from '@google/gemini-cli-core';
|
||||||
import { ConsoleSummaryDisplay } from './ConsoleSummaryDisplay.js';
|
import { ConsoleSummaryDisplay } from './ConsoleSummaryDisplay.js';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
|
@ -67,22 +67,24 @@ export const Footer: React.FC<FooterProps> = ({
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
{debugMode && <DebugProfiler />}
|
{debugMode && <DebugProfiler />}
|
||||||
{vimMode && <Text color={Colors.Gray}>[{vimMode}] </Text>}
|
{vimMode && <Text color={theme.text.secondary}>[{vimMode}] </Text>}
|
||||||
{nightly ? (
|
{nightly ? (
|
||||||
<Gradient colors={Colors.GradientColors}>
|
<Gradient colors={theme.ui.gradient}>
|
||||||
<Text>
|
<Text>
|
||||||
{displayPath}
|
{displayPath}
|
||||||
{branchName && <Text> ({branchName}*)</Text>}
|
{branchName && <Text> ({branchName}*)</Text>}
|
||||||
</Text>
|
</Text>
|
||||||
</Gradient>
|
</Gradient>
|
||||||
) : (
|
) : (
|
||||||
<Text color={Colors.LightBlue}>
|
<Text color={theme.text.link}>
|
||||||
{displayPath}
|
{displayPath}
|
||||||
{branchName && <Text color={Colors.Gray}> ({branchName}*)</Text>}
|
{branchName && (
|
||||||
|
<Text color={theme.text.secondary}> ({branchName}*)</Text>
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{debugMode && (
|
{debugMode && (
|
||||||
<Text color={Colors.AccentRed}>
|
<Text color={theme.status.error}>
|
||||||
{' ' + (debugMessage || '--debug')}
|
{' ' + (debugMessage || '--debug')}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
@ -102,20 +104,22 @@ export const Footer: React.FC<FooterProps> = ({
|
||||||
{process.env.SANDBOX.replace(/^gemini-(?:cli-)?/, '')}
|
{process.env.SANDBOX.replace(/^gemini-(?:cli-)?/, '')}
|
||||||
</Text>
|
</Text>
|
||||||
) : process.env.SANDBOX === 'sandbox-exec' ? (
|
) : process.env.SANDBOX === 'sandbox-exec' ? (
|
||||||
<Text color={Colors.AccentYellow}>
|
<Text color={theme.status.warning}>
|
||||||
macOS Seatbelt{' '}
|
macOS Seatbelt{' '}
|
||||||
<Text color={Colors.Gray}>({process.env.SEATBELT_PROFILE})</Text>
|
<Text color={theme.text.secondary}>
|
||||||
|
({process.env.SEATBELT_PROFILE})
|
||||||
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text color={Colors.AccentRed}>
|
<Text color={theme.status.error}>
|
||||||
no sandbox <Text color={Colors.Gray}>(see /docs)</Text>
|
no sandbox <Text color={theme.text.secondary}>(see /docs)</Text>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Right Section: Gemini Label and Console Summary */}
|
{/* Right Section: Gemini Label and Console Summary */}
|
||||||
<Box alignItems="center" paddingTop={isNarrow ? 1 : 0}>
|
<Box alignItems="center" paddingTop={isNarrow ? 1 : 0}>
|
||||||
<Text color={Colors.AccentBlue}>
|
<Text color={theme.text.accent}>
|
||||||
{isNarrow ? '' : ' '}
|
{isNarrow ? '' : ' '}
|
||||||
{model}{' '}
|
{model}{' '}
|
||||||
<ContextUsageDisplay
|
<ContextUsageDisplay
|
||||||
|
@ -125,17 +129,17 @@ export const Footer: React.FC<FooterProps> = ({
|
||||||
</Text>
|
</Text>
|
||||||
{corgiMode && (
|
{corgiMode && (
|
||||||
<Text>
|
<Text>
|
||||||
<Text color={Colors.Gray}>| </Text>
|
<Text color={theme.ui.symbol}>| </Text>
|
||||||
<Text color={Colors.AccentRed}>▼</Text>
|
<Text color={theme.status.error}>▼</Text>
|
||||||
<Text color={Colors.Foreground}>(´</Text>
|
<Text color={theme.text.primary}>(´</Text>
|
||||||
<Text color={Colors.AccentRed}>ᴥ</Text>
|
<Text color={theme.status.error}>ᴥ</Text>
|
||||||
<Text color={Colors.Foreground}>`)</Text>
|
<Text color={theme.text.primary}>`)</Text>
|
||||||
<Text color={Colors.AccentRed}>▼ </Text>
|
<Text color={theme.status.error}>▼ </Text>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{!showErrorDetails && errorCount > 0 && (
|
{!showErrorDetails && errorCount > 0 && (
|
||||||
<Box>
|
<Box>
|
||||||
<Text color={Colors.Gray}>| </Text>
|
<Text color={theme.ui.symbol}>| </Text>
|
||||||
<ConsoleSummaryDisplay errorCount={errorCount} />
|
<ConsoleSummaryDisplay errorCount={errorCount} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { Colors } from '../colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
import { SuggestionsDisplay } from './SuggestionsDisplay.js';
|
import { SuggestionsDisplay } from './SuggestionsDisplay.js';
|
||||||
import { useInputHistory } from '../hooks/useInputHistory.js';
|
import { useInputHistory } from '../hooks/useInputHistory.js';
|
||||||
import { TextBuffer, logicalPosToOffset } from './shared/text-buffer.js';
|
import { TextBuffer, logicalPosToOffset } from './shared/text-buffer.js';
|
||||||
|
@ -469,15 +469,17 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
borderStyle="round"
|
borderStyle="round"
|
||||||
borderColor={shellModeActive ? Colors.AccentYellow : Colors.AccentBlue}
|
borderColor={
|
||||||
|
shellModeActive ? theme.status.warning : theme.border.focused
|
||||||
|
}
|
||||||
paddingX={1}
|
paddingX={1}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
color={shellModeActive ? Colors.AccentYellow : Colors.AccentPurple}
|
color={shellModeActive ? theme.status.warning : theme.text.accent}
|
||||||
>
|
>
|
||||||
{shellModeActive ? (
|
{shellModeActive ? (
|
||||||
reverseSearchActive ? (
|
reverseSearchActive ? (
|
||||||
<Text color={Colors.AccentCyan}>(r:) </Text>
|
<Text color={theme.text.link}>(r:) </Text>
|
||||||
) : (
|
) : (
|
||||||
'! '
|
'! '
|
||||||
)
|
)
|
||||||
|
@ -490,10 +492,10 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
focus ? (
|
focus ? (
|
||||||
<Text>
|
<Text>
|
||||||
{chalk.inverse(placeholder.slice(0, 1))}
|
{chalk.inverse(placeholder.slice(0, 1))}
|
||||||
<Text color={Colors.Gray}>{placeholder.slice(1)}</Text>
|
<Text color={theme.text.secondary}>{placeholder.slice(1)}</Text>
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text color={Colors.Gray}>{placeholder}</Text>
|
<Text color={theme.text.secondary}>{placeholder}</Text>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
linesToRender.map((lineText, visualIdxInRenderedSet) => {
|
linesToRender.map((lineText, visualIdxInRenderedSet) => {
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { themeManager } from './themes/theme-manager.js';
|
||||||
|
import { SemanticColors } from './themes/semantic-tokens.js';
|
||||||
|
|
||||||
|
export const theme: SemanticColors = {
|
||||||
|
get text() {
|
||||||
|
return themeManager.getSemanticColors().text;
|
||||||
|
},
|
||||||
|
get background() {
|
||||||
|
return themeManager.getSemanticColors().background;
|
||||||
|
},
|
||||||
|
get border() {
|
||||||
|
return themeManager.getSemanticColors().border;
|
||||||
|
},
|
||||||
|
get ui() {
|
||||||
|
return themeManager.getSemanticColors().ui;
|
||||||
|
},
|
||||||
|
get status() {
|
||||||
|
return themeManager.getSemanticColors().status;
|
||||||
|
},
|
||||||
|
};
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ColorsTheme, Theme } from './theme.js';
|
import { type ColorsTheme, Theme } from './theme.js';
|
||||||
|
import { lightSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
const ansiLightColors: ColorsTheme = {
|
const ansiLightColors: ColorsTheme = {
|
||||||
type: 'light',
|
type: 'light',
|
||||||
|
@ -145,4 +146,5 @@ export const ANSILight: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ansiLightColors,
|
ansiLightColors,
|
||||||
|
lightSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ColorsTheme, Theme } from './theme.js';
|
import { type ColorsTheme, Theme } from './theme.js';
|
||||||
|
import { darkSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
const ansiColors: ColorsTheme = {
|
const ansiColors: ColorsTheme = {
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
|
@ -154,4 +155,5 @@ export const ANSI: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ansiColors,
|
ansiColors,
|
||||||
|
darkSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ColorsTheme, Theme } from './theme.js';
|
import { type ColorsTheme, Theme } from './theme.js';
|
||||||
|
import { darkSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
const atomOneDarkColors: ColorsTheme = {
|
const atomOneDarkColors: ColorsTheme = {
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
|
@ -142,4 +143,5 @@ export const AtomOneDark: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
atomOneDarkColors,
|
atomOneDarkColors,
|
||||||
|
darkSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ColorsTheme, Theme } from './theme.js';
|
import { type ColorsTheme, Theme } from './theme.js';
|
||||||
|
import { lightSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
const ayuLightColors: ColorsTheme = {
|
const ayuLightColors: ColorsTheme = {
|
||||||
type: 'light',
|
type: 'light',
|
||||||
|
@ -134,4 +135,5 @@ export const AyuLight: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ayuLightColors,
|
ayuLightColors,
|
||||||
|
lightSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ColorsTheme, Theme } from './theme.js';
|
import { type ColorsTheme, Theme } from './theme.js';
|
||||||
|
import { darkSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
const ayuDarkColors: ColorsTheme = {
|
const ayuDarkColors: ColorsTheme = {
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
|
@ -108,4 +109,5 @@ export const AyuDark: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ayuDarkColors,
|
ayuDarkColors,
|
||||||
|
darkSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { lightTheme, Theme } from './theme.js';
|
import { lightTheme, Theme } from './theme.js';
|
||||||
|
import { lightSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
export const DefaultLight: Theme = new Theme(
|
export const DefaultLight: Theme = new Theme(
|
||||||
'Default Light',
|
'Default Light',
|
||||||
|
@ -103,4 +104,5 @@ export const DefaultLight: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
lightTheme,
|
lightTheme,
|
||||||
|
lightSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { darkTheme, Theme } from './theme.js';
|
import { darkTheme, Theme } from './theme.js';
|
||||||
|
import { darkSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
export const DefaultDark: Theme = new Theme(
|
export const DefaultDark: Theme = new Theme(
|
||||||
'Default',
|
'Default',
|
||||||
|
@ -146,4 +147,5 @@ export const DefaultDark: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
darkTheme,
|
darkTheme,
|
||||||
|
darkSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ColorsTheme, Theme } from './theme.js';
|
import { type ColorsTheme, Theme } from './theme.js';
|
||||||
|
import { darkSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
const draculaColors: ColorsTheme = {
|
const draculaColors: ColorsTheme = {
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
|
@ -119,4 +120,5 @@ export const Dracula: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
draculaColors,
|
draculaColors,
|
||||||
|
darkSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ColorsTheme, Theme } from './theme.js';
|
import { type ColorsTheme, Theme } from './theme.js';
|
||||||
|
import { darkSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
const githubDarkColors: ColorsTheme = {
|
const githubDarkColors: ColorsTheme = {
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
|
@ -142,4 +143,5 @@ export const GitHubDark: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
githubDarkColors,
|
githubDarkColors,
|
||||||
|
darkSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ColorsTheme, Theme } from './theme.js';
|
import { type ColorsTheme, Theme } from './theme.js';
|
||||||
|
import { lightSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
const githubLightColors: ColorsTheme = {
|
const githubLightColors: ColorsTheme = {
|
||||||
type: 'light',
|
type: 'light',
|
||||||
|
@ -144,4 +145,5 @@ export const GitHubLight: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
githubLightColors,
|
githubLightColors,
|
||||||
|
lightSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { lightTheme, Theme, type ColorsTheme } from './theme.js';
|
import { lightTheme, Theme, type ColorsTheme } from './theme.js';
|
||||||
|
import { lightSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
const googleCodeColors: ColorsTheme = {
|
const googleCodeColors: ColorsTheme = {
|
||||||
type: 'light',
|
type: 'light',
|
||||||
|
@ -141,4 +142,5 @@ export const GoogleCode: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
googleCodeColors,
|
googleCodeColors,
|
||||||
|
lightSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Theme, ColorsTheme } from './theme.js';
|
import { Theme, ColorsTheme } from './theme.js';
|
||||||
|
import { SemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
const noColorColorsTheme: ColorsTheme = {
|
const noColorColorsTheme: ColorsTheme = {
|
||||||
type: 'ansi',
|
type: 'ansi',
|
||||||
|
@ -23,6 +24,36 @@ const noColorColorsTheme: ColorsTheme = {
|
||||||
Gray: '',
|
Gray: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const noColorSemanticColors: SemanticColors = {
|
||||||
|
text: {
|
||||||
|
primary: '',
|
||||||
|
secondary: '',
|
||||||
|
link: '',
|
||||||
|
accent: '',
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
primary: '',
|
||||||
|
diff: {
|
||||||
|
added: '',
|
||||||
|
removed: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
default: '',
|
||||||
|
focused: '',
|
||||||
|
},
|
||||||
|
ui: {
|
||||||
|
comment: '',
|
||||||
|
symbol: '',
|
||||||
|
gradient: [],
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
error: '',
|
||||||
|
success: '',
|
||||||
|
warning: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const NoColorTheme: Theme = new Theme(
|
export const NoColorTheme: Theme = new Theme(
|
||||||
'NoColor',
|
'NoColor',
|
||||||
'dark',
|
'dark',
|
||||||
|
@ -90,4 +121,5 @@ export const NoColorTheme: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
noColorColorsTheme,
|
noColorColorsTheme,
|
||||||
|
noColorSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { lightTheme, darkTheme, ansiTheme } from './theme.js';
|
||||||
|
|
||||||
|
export interface SemanticColors {
|
||||||
|
text: {
|
||||||
|
primary: string;
|
||||||
|
secondary: string;
|
||||||
|
link: string;
|
||||||
|
accent: string;
|
||||||
|
};
|
||||||
|
background: {
|
||||||
|
primary: string;
|
||||||
|
diff: {
|
||||||
|
added: string;
|
||||||
|
removed: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
border: {
|
||||||
|
default: string;
|
||||||
|
focused: string;
|
||||||
|
};
|
||||||
|
ui: {
|
||||||
|
comment: string;
|
||||||
|
symbol: string;
|
||||||
|
gradient: string[] | undefined;
|
||||||
|
};
|
||||||
|
status: {
|
||||||
|
error: string;
|
||||||
|
success: string;
|
||||||
|
warning: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const lightSemanticColors: SemanticColors = {
|
||||||
|
text: {
|
||||||
|
primary: lightTheme.Foreground,
|
||||||
|
secondary: lightTheme.Gray,
|
||||||
|
link: lightTheme.AccentBlue,
|
||||||
|
accent: lightTheme.AccentPurple,
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
primary: lightTheme.Background,
|
||||||
|
diff: {
|
||||||
|
added: lightTheme.DiffAdded,
|
||||||
|
removed: lightTheme.DiffRemoved,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
default: lightTheme.Gray,
|
||||||
|
focused: lightTheme.AccentBlue,
|
||||||
|
},
|
||||||
|
ui: {
|
||||||
|
comment: lightTheme.Comment,
|
||||||
|
symbol: lightTheme.Gray,
|
||||||
|
gradient: lightTheme.GradientColors,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
error: lightTheme.AccentRed,
|
||||||
|
success: lightTheme.AccentGreen,
|
||||||
|
warning: lightTheme.AccentYellow,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const darkSemanticColors: SemanticColors = {
|
||||||
|
text: {
|
||||||
|
primary: darkTheme.Foreground,
|
||||||
|
secondary: darkTheme.Gray,
|
||||||
|
link: darkTheme.AccentBlue,
|
||||||
|
accent: darkTheme.AccentPurple,
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
primary: darkTheme.Background,
|
||||||
|
diff: {
|
||||||
|
added: darkTheme.DiffAdded,
|
||||||
|
removed: darkTheme.DiffRemoved,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
default: darkTheme.Gray,
|
||||||
|
focused: darkTheme.AccentBlue,
|
||||||
|
},
|
||||||
|
ui: {
|
||||||
|
comment: darkTheme.Comment,
|
||||||
|
symbol: darkTheme.Gray,
|
||||||
|
gradient: darkTheme.GradientColors,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
error: darkTheme.AccentRed,
|
||||||
|
success: darkTheme.AccentGreen,
|
||||||
|
warning: darkTheme.AccentYellow,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ansiSemanticColors: SemanticColors = {
|
||||||
|
text: {
|
||||||
|
primary: ansiTheme.Foreground,
|
||||||
|
secondary: ansiTheme.Gray,
|
||||||
|
link: ansiTheme.AccentBlue,
|
||||||
|
accent: ansiTheme.AccentPurple,
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
primary: ansiTheme.Background,
|
||||||
|
diff: {
|
||||||
|
added: ansiTheme.DiffAdded,
|
||||||
|
removed: ansiTheme.DiffRemoved,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
default: ansiTheme.Gray,
|
||||||
|
focused: ansiTheme.AccentBlue,
|
||||||
|
},
|
||||||
|
ui: {
|
||||||
|
comment: ansiTheme.Comment,
|
||||||
|
symbol: ansiTheme.Gray,
|
||||||
|
gradient: ansiTheme.GradientColors,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
error: ansiTheme.AccentRed,
|
||||||
|
success: ansiTheme.AccentGreen,
|
||||||
|
warning: ansiTheme.AccentYellow,
|
||||||
|
},
|
||||||
|
};
|
|
@ -9,6 +9,7 @@
|
||||||
* @author Ahmad Awais <https://twitter.com/mrahmadawais/>
|
* @author Ahmad Awais <https://twitter.com/mrahmadawais/>
|
||||||
*/
|
*/
|
||||||
import { type ColorsTheme, Theme } from './theme.js';
|
import { type ColorsTheme, Theme } from './theme.js';
|
||||||
|
import { darkSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
const shadesOfPurpleColors: ColorsTheme = {
|
const shadesOfPurpleColors: ColorsTheme = {
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
|
@ -347,4 +348,5 @@ export const ShadesOfPurple = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
shadesOfPurpleColors,
|
shadesOfPurpleColors,
|
||||||
|
darkSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
|
@ -44,15 +44,6 @@ describe('ThemeManager', () => {
|
||||||
expect(themeManager.isCustomTheme('MyCustomTheme')).toBe(true);
|
expect(themeManager.isCustomTheme('MyCustomTheme')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not load invalid custom themes', () => {
|
|
||||||
const invalidTheme = { ...validCustomTheme, Background: 'not-a-color' };
|
|
||||||
themeManager.loadCustomThemes({
|
|
||||||
InvalidTheme: invalidTheme as unknown as CustomTheme,
|
|
||||||
});
|
|
||||||
expect(themeManager.getCustomThemeNames()).not.toContain('InvalidTheme');
|
|
||||||
expect(themeManager.isCustomTheme('InvalidTheme')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set and get the active theme', () => {
|
it('should set and get the active theme', () => {
|
||||||
expect(themeManager.getActiveTheme().name).toBe(DEFAULT_THEME.name);
|
expect(themeManager.getActiveTheme().name).toBe(DEFAULT_THEME.name);
|
||||||
themeManager.setActiveTheme('Ayu');
|
themeManager.setActiveTheme('Ayu');
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
createCustomTheme,
|
createCustomTheme,
|
||||||
validateCustomTheme,
|
validateCustomTheme,
|
||||||
} from './theme.js';
|
} from './theme.js';
|
||||||
|
import { SemanticColors } from './semantic-tokens.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 { NoColorTheme } from './no-color.js';
|
||||||
|
@ -134,6 +135,14 @@ class ThemeManager {
|
||||||
return this.activeTheme;
|
return this.activeTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the semantic colors for the active theme.
|
||||||
|
* @returns The semantic colors.
|
||||||
|
*/
|
||||||
|
getSemanticColors(): SemanticColors {
|
||||||
|
return this.getActiveTheme().semanticColors;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a list of custom theme names.
|
* Gets a list of custom theme names.
|
||||||
* @returns Array of custom theme names.
|
* @returns Array of custom theme names.
|
||||||
|
|
|
@ -36,25 +36,6 @@ describe('validateCustomTheme', () => {
|
||||||
expect(result.error).toBeUndefined();
|
expect(result.error).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return isValid: false for a theme with a missing required field', () => {
|
|
||||||
const invalidTheme = {
|
|
||||||
...validTheme,
|
|
||||||
name: undefined as unknown as string,
|
|
||||||
};
|
|
||||||
const result = validateCustomTheme(invalidTheme);
|
|
||||||
expect(result.isValid).toBe(false);
|
|
||||||
expect(result.error).toBe('Missing required field: name');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return isValid: false for a theme with an invalid color format', () => {
|
|
||||||
const invalidTheme = { ...validTheme, Background: 'not-a-color' };
|
|
||||||
const result = validateCustomTheme(invalidTheme);
|
|
||||||
expect(result.isValid).toBe(false);
|
|
||||||
expect(result.error).toBe(
|
|
||||||
'Invalid color format for Background: not-a-color',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return isValid: false for a theme with an invalid name', () => {
|
it('should return isValid: false for a theme with an invalid name', () => {
|
||||||
const invalidTheme = { ...validTheme, name: ' ' };
|
const invalidTheme = { ...validTheme, name: ' ' };
|
||||||
const result = validateCustomTheme(invalidTheme);
|
const result = validateCustomTheme(invalidTheme);
|
||||||
|
@ -71,37 +52,6 @@ describe('validateCustomTheme', () => {
|
||||||
expect(result.error).toBeUndefined();
|
expect(result.error).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a warning if DiffAdded and DiffRemoved are missing', () => {
|
|
||||||
const legacyTheme: Partial<CustomTheme> = { ...validTheme };
|
|
||||||
delete legacyTheme.DiffAdded;
|
|
||||||
delete legacyTheme.DiffRemoved;
|
|
||||||
const result = validateCustomTheme(legacyTheme);
|
|
||||||
expect(result.isValid).toBe(true);
|
|
||||||
expect(result.warning).toBe('Missing field(s) DiffAdded, DiffRemoved');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a warning if only DiffRemoved is missing', () => {
|
|
||||||
const legacyTheme: Partial<CustomTheme> = { ...validTheme };
|
|
||||||
delete legacyTheme.DiffRemoved;
|
|
||||||
const result = validateCustomTheme(legacyTheme);
|
|
||||||
expect(result.isValid).toBe(true);
|
|
||||||
expect(result.warning).toBe('Missing field(s) DiffRemoved');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return isValid: false for a theme with an invalid DiffAdded color', () => {
|
|
||||||
const invalidTheme = { ...validTheme, DiffAdded: 'invalid' };
|
|
||||||
const result = validateCustomTheme(invalidTheme);
|
|
||||||
expect(result.isValid).toBe(false);
|
|
||||||
expect(result.error).toBe('Invalid color format for DiffAdded: invalid');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return isValid: false for a theme with an invalid DiffRemoved color', () => {
|
|
||||||
const invalidTheme = { ...validTheme, DiffRemoved: 'invalid' };
|
|
||||||
const result = validateCustomTheme(invalidTheme);
|
|
||||||
expect(result.isValid).toBe(false);
|
|
||||||
expect(result.error).toBe('Invalid color format for DiffRemoved: invalid');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return isValid: false for a theme with a very long name', () => {
|
it('should return isValid: false for a theme with a very long name', () => {
|
||||||
const invalidTheme = { ...validTheme, name: 'a'.repeat(51) };
|
const invalidTheme = { ...validTheme, name: 'a'.repeat(51) };
|
||||||
const result = validateCustomTheme(invalidTheme);
|
const result = validateCustomTheme(invalidTheme);
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { isValidColor, resolveColor } from './color-utils.js';
|
import { SemanticColors } from './semantic-tokens.js';
|
||||||
|
import { resolveColor } from './color-utils.js';
|
||||||
|
|
||||||
export type ThemeType = 'light' | 'dark' | 'ansi' | 'custom';
|
export type ThemeType = 'light' | 'dark' | 'ansi' | 'custom';
|
||||||
|
|
||||||
|
@ -27,9 +28,53 @@ export interface ColorsTheme {
|
||||||
GradientColors?: string[];
|
GradientColors?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomTheme extends ColorsTheme {
|
export interface CustomTheme {
|
||||||
type: 'custom';
|
type: 'custom';
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
text?: {
|
||||||
|
primary?: string;
|
||||||
|
secondary?: string;
|
||||||
|
link?: string;
|
||||||
|
accent?: string;
|
||||||
|
};
|
||||||
|
background?: {
|
||||||
|
primary?: string;
|
||||||
|
diff?: {
|
||||||
|
added?: string;
|
||||||
|
removed?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
border?: {
|
||||||
|
default?: string;
|
||||||
|
focused?: string;
|
||||||
|
};
|
||||||
|
ui?: {
|
||||||
|
comment?: string;
|
||||||
|
symbol?: string;
|
||||||
|
gradient?: string[];
|
||||||
|
};
|
||||||
|
status?: {
|
||||||
|
error?: string;
|
||||||
|
success?: string;
|
||||||
|
warning?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Legacy properties (all optional)
|
||||||
|
Background?: string;
|
||||||
|
Foreground?: string;
|
||||||
|
LightBlue?: string;
|
||||||
|
AccentBlue?: string;
|
||||||
|
AccentPurple?: string;
|
||||||
|
AccentCyan?: string;
|
||||||
|
AccentGreen?: string;
|
||||||
|
AccentYellow?: string;
|
||||||
|
AccentRed?: string;
|
||||||
|
DiffAdded?: string;
|
||||||
|
DiffRemoved?: string;
|
||||||
|
Comment?: string;
|
||||||
|
Gray?: string;
|
||||||
|
GradientColors?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lightTheme: ColorsTheme = {
|
export const lightTheme: ColorsTheme = {
|
||||||
|
@ -107,6 +152,7 @@ export class Theme {
|
||||||
readonly type: ThemeType,
|
readonly type: ThemeType,
|
||||||
rawMappings: Record<string, CSSProperties>,
|
rawMappings: Record<string, CSSProperties>,
|
||||||
readonly colors: ColorsTheme,
|
readonly colors: ColorsTheme,
|
||||||
|
readonly semanticColors: SemanticColors,
|
||||||
) {
|
) {
|
||||||
this._colorMap = Object.freeze(this._buildColorMap(rawMappings)); // Build and freeze the map
|
this._colorMap = Object.freeze(this._buildColorMap(rawMappings)); // Build and freeze the map
|
||||||
|
|
||||||
|
@ -174,107 +220,127 @@ export class Theme {
|
||||||
* @returns A new Theme instance.
|
* @returns A new Theme instance.
|
||||||
*/
|
*/
|
||||||
export function createCustomTheme(customTheme: CustomTheme): Theme {
|
export function createCustomTheme(customTheme: CustomTheme): Theme {
|
||||||
|
const colors: ColorsTheme = {
|
||||||
|
type: 'custom',
|
||||||
|
Background: customTheme.background?.primary ?? customTheme.Background ?? '',
|
||||||
|
Foreground: customTheme.text?.primary ?? customTheme.Foreground ?? '',
|
||||||
|
LightBlue: customTheme.text?.link ?? customTheme.LightBlue ?? '',
|
||||||
|
AccentBlue: customTheme.text?.link ?? customTheme.AccentBlue ?? '',
|
||||||
|
AccentPurple: customTheme.text?.accent ?? customTheme.AccentPurple ?? '',
|
||||||
|
AccentCyan: customTheme.text?.link ?? customTheme.AccentCyan ?? '',
|
||||||
|
AccentGreen: customTheme.status?.success ?? customTheme.AccentGreen ?? '',
|
||||||
|
AccentYellow: customTheme.status?.warning ?? customTheme.AccentYellow ?? '',
|
||||||
|
AccentRed: customTheme.status?.error ?? customTheme.AccentRed ?? '',
|
||||||
|
DiffAdded:
|
||||||
|
customTheme.background?.diff?.added ?? customTheme.DiffAdded ?? '',
|
||||||
|
DiffRemoved:
|
||||||
|
customTheme.background?.diff?.removed ?? customTheme.DiffRemoved ?? '',
|
||||||
|
Comment: customTheme.ui?.comment ?? customTheme.Comment ?? '',
|
||||||
|
Gray: customTheme.text?.secondary ?? customTheme.Gray ?? '',
|
||||||
|
GradientColors: customTheme.ui?.gradient ?? customTheme.GradientColors,
|
||||||
|
};
|
||||||
|
|
||||||
// Generate CSS properties mappings based on the custom theme colors
|
// Generate CSS properties mappings based on the custom theme colors
|
||||||
const rawMappings: Record<string, CSSProperties> = {
|
const rawMappings: Record<string, CSSProperties> = {
|
||||||
hljs: {
|
hljs: {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
overflowX: 'auto',
|
overflowX: 'auto',
|
||||||
padding: '0.5em',
|
padding: '0.5em',
|
||||||
background: customTheme.Background,
|
background: colors.Background,
|
||||||
color: customTheme.Foreground,
|
color: colors.Foreground,
|
||||||
},
|
},
|
||||||
'hljs-keyword': {
|
'hljs-keyword': {
|
||||||
color: customTheme.AccentBlue,
|
color: colors.AccentBlue,
|
||||||
},
|
},
|
||||||
'hljs-literal': {
|
'hljs-literal': {
|
||||||
color: customTheme.AccentBlue,
|
color: colors.AccentBlue,
|
||||||
},
|
},
|
||||||
'hljs-symbol': {
|
'hljs-symbol': {
|
||||||
color: customTheme.AccentBlue,
|
color: colors.AccentBlue,
|
||||||
},
|
},
|
||||||
'hljs-name': {
|
'hljs-name': {
|
||||||
color: customTheme.AccentBlue,
|
color: colors.AccentBlue,
|
||||||
},
|
},
|
||||||
'hljs-link': {
|
'hljs-link': {
|
||||||
color: customTheme.AccentBlue,
|
color: colors.AccentBlue,
|
||||||
textDecoration: 'underline',
|
textDecoration: 'underline',
|
||||||
},
|
},
|
||||||
'hljs-built_in': {
|
'hljs-built_in': {
|
||||||
color: customTheme.AccentCyan,
|
color: colors.AccentCyan,
|
||||||
},
|
},
|
||||||
'hljs-type': {
|
'hljs-type': {
|
||||||
color: customTheme.AccentCyan,
|
color: colors.AccentCyan,
|
||||||
},
|
},
|
||||||
'hljs-number': {
|
'hljs-number': {
|
||||||
color: customTheme.AccentGreen,
|
color: colors.AccentGreen,
|
||||||
},
|
},
|
||||||
'hljs-class': {
|
'hljs-class': {
|
||||||
color: customTheme.AccentGreen,
|
color: colors.AccentGreen,
|
||||||
},
|
},
|
||||||
'hljs-string': {
|
'hljs-string': {
|
||||||
color: customTheme.AccentYellow,
|
color: colors.AccentYellow,
|
||||||
},
|
},
|
||||||
'hljs-meta-string': {
|
'hljs-meta-string': {
|
||||||
color: customTheme.AccentYellow,
|
color: colors.AccentYellow,
|
||||||
},
|
},
|
||||||
'hljs-regexp': {
|
'hljs-regexp': {
|
||||||
color: customTheme.AccentRed,
|
color: colors.AccentRed,
|
||||||
},
|
},
|
||||||
'hljs-template-tag': {
|
'hljs-template-tag': {
|
||||||
color: customTheme.AccentRed,
|
color: colors.AccentRed,
|
||||||
},
|
},
|
||||||
'hljs-subst': {
|
'hljs-subst': {
|
||||||
color: customTheme.Foreground,
|
color: colors.Foreground,
|
||||||
},
|
},
|
||||||
'hljs-function': {
|
'hljs-function': {
|
||||||
color: customTheme.Foreground,
|
color: colors.Foreground,
|
||||||
},
|
},
|
||||||
'hljs-title': {
|
'hljs-title': {
|
||||||
color: customTheme.Foreground,
|
color: colors.Foreground,
|
||||||
},
|
},
|
||||||
'hljs-params': {
|
'hljs-params': {
|
||||||
color: customTheme.Foreground,
|
color: colors.Foreground,
|
||||||
},
|
},
|
||||||
'hljs-formula': {
|
'hljs-formula': {
|
||||||
color: customTheme.Foreground,
|
color: colors.Foreground,
|
||||||
},
|
},
|
||||||
'hljs-comment': {
|
'hljs-comment': {
|
||||||
color: customTheme.Comment,
|
color: colors.Comment,
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
},
|
},
|
||||||
'hljs-quote': {
|
'hljs-quote': {
|
||||||
color: customTheme.Comment,
|
color: colors.Comment,
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
},
|
},
|
||||||
'hljs-doctag': {
|
'hljs-doctag': {
|
||||||
color: customTheme.Comment,
|
color: colors.Comment,
|
||||||
},
|
},
|
||||||
'hljs-meta': {
|
'hljs-meta': {
|
||||||
color: customTheme.Gray,
|
color: colors.Gray,
|
||||||
},
|
},
|
||||||
'hljs-meta-keyword': {
|
'hljs-meta-keyword': {
|
||||||
color: customTheme.Gray,
|
color: colors.Gray,
|
||||||
},
|
},
|
||||||
'hljs-tag': {
|
'hljs-tag': {
|
||||||
color: customTheme.Gray,
|
color: colors.Gray,
|
||||||
},
|
},
|
||||||
'hljs-variable': {
|
'hljs-variable': {
|
||||||
color: customTheme.AccentPurple,
|
color: colors.AccentPurple,
|
||||||
},
|
},
|
||||||
'hljs-template-variable': {
|
'hljs-template-variable': {
|
||||||
color: customTheme.AccentPurple,
|
color: colors.AccentPurple,
|
||||||
},
|
},
|
||||||
'hljs-attr': {
|
'hljs-attr': {
|
||||||
color: customTheme.LightBlue,
|
color: colors.LightBlue,
|
||||||
},
|
},
|
||||||
'hljs-attribute': {
|
'hljs-attribute': {
|
||||||
color: customTheme.LightBlue,
|
color: colors.LightBlue,
|
||||||
},
|
},
|
||||||
'hljs-builtin-name': {
|
'hljs-builtin-name': {
|
||||||
color: customTheme.LightBlue,
|
color: colors.LightBlue,
|
||||||
},
|
},
|
||||||
'hljs-section': {
|
'hljs-section': {
|
||||||
color: customTheme.AccentYellow,
|
color: colors.AccentYellow,
|
||||||
},
|
},
|
||||||
'hljs-emphasis': {
|
'hljs-emphasis': {
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
|
@ -283,36 +349,72 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
},
|
},
|
||||||
'hljs-bullet': {
|
'hljs-bullet': {
|
||||||
color: customTheme.AccentYellow,
|
color: colors.AccentYellow,
|
||||||
},
|
},
|
||||||
'hljs-selector-tag': {
|
'hljs-selector-tag': {
|
||||||
color: customTheme.AccentYellow,
|
color: colors.AccentYellow,
|
||||||
},
|
},
|
||||||
'hljs-selector-id': {
|
'hljs-selector-id': {
|
||||||
color: customTheme.AccentYellow,
|
color: colors.AccentYellow,
|
||||||
},
|
},
|
||||||
'hljs-selector-class': {
|
'hljs-selector-class': {
|
||||||
color: customTheme.AccentYellow,
|
color: colors.AccentYellow,
|
||||||
},
|
},
|
||||||
'hljs-selector-attr': {
|
'hljs-selector-attr': {
|
||||||
color: customTheme.AccentYellow,
|
color: colors.AccentYellow,
|
||||||
},
|
},
|
||||||
'hljs-selector-pseudo': {
|
'hljs-selector-pseudo': {
|
||||||
color: customTheme.AccentYellow,
|
color: colors.AccentYellow,
|
||||||
},
|
},
|
||||||
'hljs-addition': {
|
'hljs-addition': {
|
||||||
backgroundColor: customTheme.AccentGreen,
|
backgroundColor: colors.AccentGreen,
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
'hljs-deletion': {
|
'hljs-deletion': {
|
||||||
backgroundColor: customTheme.AccentRed,
|
backgroundColor: colors.AccentRed,
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Theme(customTheme.name, 'custom', rawMappings, customTheme);
|
const semanticColors: SemanticColors = {
|
||||||
|
text: {
|
||||||
|
primary: colors.Foreground,
|
||||||
|
secondary: colors.Gray,
|
||||||
|
link: colors.AccentBlue,
|
||||||
|
accent: colors.AccentPurple,
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
primary: colors.Background,
|
||||||
|
diff: {
|
||||||
|
added: colors.DiffAdded,
|
||||||
|
removed: colors.DiffRemoved,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
default: colors.Gray,
|
||||||
|
focused: colors.AccentBlue,
|
||||||
|
},
|
||||||
|
ui: {
|
||||||
|
comment: colors.Comment,
|
||||||
|
symbol: colors.Gray,
|
||||||
|
gradient: colors.GradientColors,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
error: colors.AccentRed,
|
||||||
|
success: colors.AccentGreen,
|
||||||
|
warning: colors.AccentYellow,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Theme(
|
||||||
|
customTheme.name,
|
||||||
|
'custom',
|
||||||
|
rawMappings,
|
||||||
|
colors,
|
||||||
|
semanticColors,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -325,74 +427,7 @@ export function validateCustomTheme(customTheme: Partial<CustomTheme>): {
|
||||||
error?: string;
|
error?: string;
|
||||||
warning?: string;
|
warning?: string;
|
||||||
} {
|
} {
|
||||||
// Check required fields
|
// Since all fields are optional, we only need to validate the name.
|
||||||
const requiredFields: Array<keyof CustomTheme> = [
|
|
||||||
'name',
|
|
||||||
'Background',
|
|
||||||
'Foreground',
|
|
||||||
'LightBlue',
|
|
||||||
'AccentBlue',
|
|
||||||
'AccentPurple',
|
|
||||||
'AccentCyan',
|
|
||||||
'AccentGreen',
|
|
||||||
'AccentYellow',
|
|
||||||
'AccentRed',
|
|
||||||
// 'DiffAdded' and 'DiffRemoved' are not required as they were added after
|
|
||||||
// the theme format was defined.
|
|
||||||
'Comment',
|
|
||||||
'Gray',
|
|
||||||
];
|
|
||||||
|
|
||||||
const recommendedFields: Array<keyof CustomTheme> = [
|
|
||||||
'DiffAdded',
|
|
||||||
'DiffRemoved',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const field of requiredFields) {
|
|
||||||
if (!customTheme[field]) {
|
|
||||||
return {
|
|
||||||
isValid: false,
|
|
||||||
error: `Missing required field: ${field}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const missingFields: string[] = [];
|
|
||||||
|
|
||||||
for (const field of recommendedFields) {
|
|
||||||
if (!customTheme[field]) {
|
|
||||||
missingFields.push(field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate color format (basic hex validation)
|
|
||||||
const colorFields: Array<keyof CustomTheme> = [
|
|
||||||
'Background',
|
|
||||||
'Foreground',
|
|
||||||
'LightBlue',
|
|
||||||
'AccentBlue',
|
|
||||||
'AccentPurple',
|
|
||||||
'AccentCyan',
|
|
||||||
'AccentGreen',
|
|
||||||
'AccentYellow',
|
|
||||||
'AccentRed',
|
|
||||||
'DiffAdded',
|
|
||||||
'DiffRemoved',
|
|
||||||
'Comment',
|
|
||||||
'Gray',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const field of colorFields) {
|
|
||||||
const color = customTheme[field] as string | undefined;
|
|
||||||
if (color !== undefined && !isValidColor(color)) {
|
|
||||||
return {
|
|
||||||
isValid: false,
|
|
||||||
error: `Invalid color format for ${field}: ${color}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate theme name
|
|
||||||
if (customTheme.name && !isValidThemeName(customTheme.name)) {
|
if (customTheme.name && !isValidThemeName(customTheme.name)) {
|
||||||
return {
|
return {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
|
@ -402,10 +437,6 @@ export function validateCustomTheme(customTheme: Partial<CustomTheme>): {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isValid: true,
|
isValid: true,
|
||||||
warning:
|
|
||||||
missingFields.length > 0
|
|
||||||
? `Missing field(s) ${missingFields.join(', ')}`
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ColorsTheme, Theme } from './theme.js';
|
import { type ColorsTheme, Theme } from './theme.js';
|
||||||
|
import { lightSemanticColors } from './semantic-tokens.js';
|
||||||
|
|
||||||
const xcodeColors: ColorsTheme = {
|
const xcodeColors: ColorsTheme = {
|
||||||
type: 'light',
|
type: 'light',
|
||||||
|
@ -149,4 +150,5 @@ export const XCode: Theme = new Theme(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
xcodeColors,
|
xcodeColors,
|
||||||
|
lightSemanticColors,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue