Add theming support.
- Added a number of common themes to our support matrix: - AtomOneDark - Dracula - VS - GitHub - GoogleCode - XCode - ... Admittedly these all were randomly picked, we could probably curate these better... - Added a new `ThemeDialog` UI that can be accessed via `/theme`. It shows your currentlyt available themes and allows you to change them freely. It does **not**: - Save the theme between sessions - Allow you to hit escape - Show a preview prior to selection. - These themes are from reacts highlight js library. Fixes https://b.corp.google.com/issues/412797985
This commit is contained in:
parent
e163e02499
commit
4c2a5045a0
|
@ -4,17 +4,19 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { StreamingState, type HistoryItem } from './types.js';
|
||||
import { useGeminiStream } from './hooks/useGeminiStream.js';
|
||||
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
|
||||
import { useInputHistory } from './hooks/useInputHistory.js';
|
||||
import { useThemeCommand } from './hooks/useThemeCommand.js';
|
||||
import { Header } from './components/Header.js';
|
||||
import { HistoryDisplay } from './components/HistoryDisplay.js';
|
||||
import { LoadingIndicator } from './components/LoadingIndicator.js';
|
||||
import { InputPrompt } from './components/InputPrompt.js';
|
||||
import { Footer } from './components/Footer.js';
|
||||
import { ThemeDialog } from './components/ThemeDialog.js';
|
||||
import { ITermDetectionWarning } from './utils/itermDetection.js';
|
||||
import {
|
||||
useStartupWarnings,
|
||||
|
@ -22,13 +24,13 @@ import {
|
|||
} from './hooks/useAppEffects.js';
|
||||
import { shortenPath, type Config } from '@gemini-code/server';
|
||||
import { Colors } from './colors.js';
|
||||
import { Tips } from './components/Tips.js';
|
||||
|
||||
interface AppProps {
|
||||
config: Config;
|
||||
}
|
||||
|
||||
export const App = ({ config }: AppProps) => {
|
||||
// Destructured prop
|
||||
const [history, setHistory] = useState<HistoryItem[]>([]);
|
||||
const [startupWarnings, setStartupWarnings] = useState<string[]>([]);
|
||||
const { streamingState, submitQuery, initError, debugMessage } =
|
||||
|
@ -36,9 +38,24 @@ export const App = ({ config }: AppProps) => {
|
|||
const { elapsedTime, currentLoadingPhrase } =
|
||||
useLoadingIndicator(streamingState);
|
||||
|
||||
const { isThemeDialogOpen, openThemeDialog, handleThemeSelect } =
|
||||
useThemeCommand();
|
||||
|
||||
useStartupWarnings(setStartupWarnings);
|
||||
useInitializationErrorEffect(initError, history, setHistory);
|
||||
|
||||
const handleFinalSubmit = useCallback(
|
||||
(submittedValue: string) => {
|
||||
const trimmedValue = submittedValue.trim();
|
||||
if (trimmedValue === '/theme') {
|
||||
openThemeDialog();
|
||||
} else if (trimmedValue.length > 0) {
|
||||
submitQuery(submittedValue);
|
||||
}
|
||||
},
|
||||
[openThemeDialog, submitQuery],
|
||||
);
|
||||
|
||||
const userMessages = useMemo(
|
||||
() =>
|
||||
history
|
||||
|
@ -56,13 +73,16 @@ export const App = ({ config }: AppProps) => {
|
|||
|
||||
const { query, handleSubmit: handleHistorySubmit } = useInputHistory({
|
||||
userMessages,
|
||||
onSubmit: submitQuery,
|
||||
onSubmit: handleFinalSubmit,
|
||||
isActive: isInputActive,
|
||||
});
|
||||
|
||||
// --- Render Logic ---
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" marginBottom={1} width="100%">
|
||||
<Header />
|
||||
<Tips />
|
||||
|
||||
{startupWarnings.length > 0 && (
|
||||
<Box
|
||||
|
@ -112,6 +132,10 @@ export const App = ({ config }: AppProps) => {
|
|||
</Box>
|
||||
)}
|
||||
|
||||
{isThemeDialogOpen ? (
|
||||
<ThemeDialog onSelect={handleThemeSelect} />
|
||||
) : (
|
||||
<>
|
||||
<Box flexDirection="column">
|
||||
<HistoryDisplay history={history} onSubmit={submitQuery} />
|
||||
<LoadingIndicator
|
||||
|
@ -133,6 +157,8 @@ export const App = ({ config }: AppProps) => {
|
|||
<InputPrompt onSubmit={handleHistorySubmit} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Footer
|
||||
config={config}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import Gradient from 'ink-gradient';
|
||||
import { Tips } from './Tips.js';
|
||||
|
||||
const gradientColors = ['#4796E4', '#847ACE', '#C3677F'];
|
||||
|
||||
|
@ -32,6 +31,5 @@ export const Header: React.FC = () => (
|
|||
`}</Text>
|
||||
</Gradient>
|
||||
</Box>
|
||||
<Tips />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Colors } from '../colors.js';
|
||||
import { themeManager } from '../themes/theme-manager.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
|
||||
interface ThemeDialogProps {
|
||||
/** Callback function when a theme is selected */
|
||||
onSelect: (themeName: string) => void;
|
||||
}
|
||||
|
||||
export function ThemeDialog({ onSelect }: ThemeDialogProps): React.JSX.Element {
|
||||
const themeItems = themeManager.getAvailableThemes().map((theme) => ({
|
||||
label: theme.active ? `${theme.name} (Active)` : theme.name,
|
||||
value: theme.name,
|
||||
}));
|
||||
const initialIndex = themeItems.findIndex(
|
||||
(item) => item.value === themeManager.getActiveTheme().name,
|
||||
);
|
||||
return (
|
||||
<Box
|
||||
borderStyle="round"
|
||||
borderColor={Colors.AccentCyan}
|
||||
flexDirection="column"
|
||||
padding={1}
|
||||
width="50%"
|
||||
>
|
||||
<Box marginBottom={1}>
|
||||
<Text bold>Select Theme</Text>
|
||||
</Box>
|
||||
<RadioButtonSelect
|
||||
items={themeItems}
|
||||
initialIndex={initialIndex}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
<Box marginTop={1}>
|
||||
<Text color={Colors.SubtleComment}>
|
||||
(Use ↑/↓ arrows and Enter to select)
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { themeManager } from '../themes/theme-manager.js';
|
||||
|
||||
interface UseThemeCommandReturn {
|
||||
isThemeDialogOpen: boolean;
|
||||
openThemeDialog: () => void;
|
||||
handleThemeSelect: (themeName: string) => void;
|
||||
}
|
||||
|
||||
export const useThemeCommand = (): UseThemeCommandReturn => {
|
||||
const [isThemeDialogOpen, setIsThemeDialogOpen] = useState(false);
|
||||
const [, setForceRender] = useState(0);
|
||||
|
||||
const openThemeDialog = useCallback(() => {
|
||||
setIsThemeDialogOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleThemeSelect = useCallback((themeName: string) => {
|
||||
try {
|
||||
themeManager.setActiveTheme(themeName);
|
||||
setForceRender((v) => v + 1); // Trigger potential re-render
|
||||
} catch (error) {
|
||||
console.error(`Error setting theme: ${error}`);
|
||||
} finally {
|
||||
setIsThemeDialogOpen(false); // Close the dialog
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
isThemeDialogOpen,
|
||||
openThemeDialog,
|
||||
handleThemeSelect,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Theme } from './theme.js';
|
||||
|
||||
export const AtomOneDark: Theme = new Theme('Atom One Dark', {
|
||||
hljs: {
|
||||
display: 'block',
|
||||
overflowX: 'auto',
|
||||
padding: '0.5em',
|
||||
color: '#abb2bf',
|
||||
background: '#282c34',
|
||||
},
|
||||
'hljs-comment': {
|
||||
color: '#5c6370',
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
'hljs-quote': {
|
||||
color: '#5c6370',
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
'hljs-doctag': {
|
||||
color: '#c678dd',
|
||||
},
|
||||
'hljs-keyword': {
|
||||
color: '#c678dd',
|
||||
},
|
||||
'hljs-formula': {
|
||||
color: '#c678dd',
|
||||
},
|
||||
'hljs-section': {
|
||||
color: '#e06c75',
|
||||
},
|
||||
'hljs-name': {
|
||||
color: '#e06c75',
|
||||
},
|
||||
'hljs-selector-tag': {
|
||||
color: '#e06c75',
|
||||
},
|
||||
'hljs-deletion': {
|
||||
color: '#e06c75',
|
||||
},
|
||||
'hljs-subst': {
|
||||
color: '#e06c75',
|
||||
},
|
||||
'hljs-literal': {
|
||||
color: '#56b6c2',
|
||||
},
|
||||
'hljs-string': {
|
||||
color: '#98c379',
|
||||
},
|
||||
'hljs-regexp': {
|
||||
color: '#98c379',
|
||||
},
|
||||
'hljs-addition': {
|
||||
color: '#98c379',
|
||||
},
|
||||
'hljs-attribute': {
|
||||
color: '#98c379',
|
||||
},
|
||||
'hljs-meta-string': {
|
||||
color: '#98c379',
|
||||
},
|
||||
'hljs-built_in': {
|
||||
color: '#e6c07b',
|
||||
},
|
||||
'hljs-class .hljs-title': {
|
||||
color: '#e6c07b',
|
||||
},
|
||||
'hljs-attr': {
|
||||
color: '#d19a66',
|
||||
},
|
||||
'hljs-variable': {
|
||||
color: '#d19a66',
|
||||
},
|
||||
'hljs-template-variable': {
|
||||
color: '#d19a66',
|
||||
},
|
||||
'hljs-type': {
|
||||
color: '#d19a66',
|
||||
},
|
||||
'hljs-selector-class': {
|
||||
color: '#d19a66',
|
||||
},
|
||||
'hljs-selector-attr': {
|
||||
color: '#d19a66',
|
||||
},
|
||||
'hljs-selector-pseudo': {
|
||||
color: '#d19a66',
|
||||
},
|
||||
'hljs-number': {
|
||||
color: '#d19a66',
|
||||
},
|
||||
'hljs-symbol': {
|
||||
color: '#61aeee',
|
||||
},
|
||||
'hljs-bullet': {
|
||||
color: '#61aeee',
|
||||
},
|
||||
'hljs-link': {
|
||||
color: '#61aeee',
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
'hljs-meta': {
|
||||
color: '#61aeee',
|
||||
},
|
||||
'hljs-selector-id': {
|
||||
color: '#61aeee',
|
||||
},
|
||||
'hljs-title': {
|
||||
color: '#61aeee',
|
||||
},
|
||||
'hljs-emphasis': {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
'hljs-strong': {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Theme } from './theme.js';
|
||||
|
||||
export const Dracula: Theme = new Theme('Dracula', {
|
||||
hljs: {
|
||||
display: 'block',
|
||||
overflowX: 'auto',
|
||||
padding: '0.5em',
|
||||
background: '#282a36',
|
||||
color: '#f8f8f2',
|
||||
},
|
||||
'hljs-keyword': {
|
||||
color: '#8be9fd',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-selector-tag': {
|
||||
color: '#8be9fd',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-literal': {
|
||||
color: '#8be9fd',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-section': {
|
||||
color: '#8be9fd',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-link': {
|
||||
color: '#8be9fd',
|
||||
},
|
||||
'hljs-function .hljs-keyword': {
|
||||
color: '#ff79c6',
|
||||
},
|
||||
'hljs-subst': {
|
||||
color: '#f8f8f2',
|
||||
},
|
||||
'hljs-string': {
|
||||
color: '#f1fa8c',
|
||||
},
|
||||
'hljs-title': {
|
||||
color: '#f1fa8c',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-name': {
|
||||
color: '#f1fa8c',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-type': {
|
||||
color: '#f1fa8c',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-attribute': {
|
||||
color: '#f1fa8c',
|
||||
},
|
||||
'hljs-symbol': {
|
||||
color: '#f1fa8c',
|
||||
},
|
||||
'hljs-bullet': {
|
||||
color: '#f1fa8c',
|
||||
},
|
||||
'hljs-addition': {
|
||||
color: '#f1fa8c',
|
||||
},
|
||||
'hljs-variable': {
|
||||
color: '#f1fa8c',
|
||||
},
|
||||
'hljs-template-tag': {
|
||||
color: '#f1fa8c',
|
||||
},
|
||||
'hljs-template-variable': {
|
||||
color: '#f1fa8c',
|
||||
},
|
||||
'hljs-comment': {
|
||||
color: '#6272a4',
|
||||
},
|
||||
'hljs-quote': {
|
||||
color: '#6272a4',
|
||||
},
|
||||
'hljs-deletion': {
|
||||
color: '#6272a4',
|
||||
},
|
||||
'hljs-meta': {
|
||||
color: '#6272a4',
|
||||
},
|
||||
'hljs-doctag': {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-strong': {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-emphasis': {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Theme } from './theme.js';
|
||||
|
||||
export const GitHub: Theme = new Theme('GitHub', {
|
||||
hljs: {
|
||||
display: 'block',
|
||||
overflowX: 'auto',
|
||||
padding: '0.5em',
|
||||
color: '#333',
|
||||
background: '#f8f8f8',
|
||||
},
|
||||
'hljs-comment': {
|
||||
color: '#998',
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
'hljs-quote': {
|
||||
color: '#998',
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
'hljs-keyword': {
|
||||
color: '#333',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-selector-tag': {
|
||||
color: '#333',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-subst': {
|
||||
color: '#333',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'hljs-number': {
|
||||
color: '#008080',
|
||||
},
|
||||
'hljs-literal': {
|
||||
color: '#008080',
|
||||
},
|
||||
'hljs-variable': {
|
||||
color: '#008080',
|
||||
},
|
||||
'hljs-template-variable': {
|
||||
color: '#008080',
|
||||
},
|
||||
'hljs-tag .hljs-attr': {
|
||||
color: '#008080',
|
||||
},
|
||||
'hljs-string': {
|
||||
color: '#d14',
|
||||
},
|
||||
'hljs-doctag': {
|
||||
color: '#d14',
|
||||
},
|
||||
'hljs-title': {
|
||||
color: '#900',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-section': {
|
||||
color: '#900',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-selector-id': {
|
||||
color: '#900',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-type': {
|
||||
color: '#458',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-class .hljs-title': {
|
||||
color: '#458',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-tag': {
|
||||
color: '#000080',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'hljs-name': {
|
||||
color: '#000080',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'hljs-attribute': {
|
||||
color: '#000080',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'hljs-regexp': {
|
||||
color: '#009926',
|
||||
},
|
||||
'hljs-link': {
|
||||
color: '#009926',
|
||||
},
|
||||
'hljs-symbol': {
|
||||
color: '#990073',
|
||||
},
|
||||
'hljs-bullet': {
|
||||
color: '#990073',
|
||||
},
|
||||
'hljs-built_in': {
|
||||
color: '#0086b3',
|
||||
},
|
||||
'hljs-builtin-name': {
|
||||
color: '#0086b3',
|
||||
},
|
||||
'hljs-meta': {
|
||||
color: '#999',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-deletion': {
|
||||
background: '#fdd',
|
||||
},
|
||||
'hljs-addition': {
|
||||
background: '#dfd',
|
||||
},
|
||||
'hljs-emphasis': {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
'hljs-strong': {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Theme } from './theme.js';
|
||||
|
||||
export const GoogleCode: Theme = new Theme('Google Code', {
|
||||
hljs: {
|
||||
display: 'block',
|
||||
overflowX: 'auto',
|
||||
padding: '0.5em',
|
||||
background: 'white',
|
||||
color: 'black',
|
||||
},
|
||||
'hljs-comment': {
|
||||
color: '#800',
|
||||
},
|
||||
'hljs-quote': {
|
||||
color: '#800',
|
||||
},
|
||||
'hljs-keyword': {
|
||||
color: '#008',
|
||||
},
|
||||
'hljs-selector-tag': {
|
||||
color: '#008',
|
||||
},
|
||||
'hljs-section': {
|
||||
color: '#008',
|
||||
},
|
||||
'hljs-title': {
|
||||
color: '#606',
|
||||
},
|
||||
'hljs-name': {
|
||||
color: '#008',
|
||||
},
|
||||
'hljs-variable': {
|
||||
color: '#660',
|
||||
},
|
||||
'hljs-template-variable': {
|
||||
color: '#660',
|
||||
},
|
||||
'hljs-string': {
|
||||
color: '#080',
|
||||
},
|
||||
'hljs-selector-attr': {
|
||||
color: '#080',
|
||||
},
|
||||
'hljs-selector-pseudo': {
|
||||
color: '#080',
|
||||
},
|
||||
'hljs-regexp': {
|
||||
color: '#080',
|
||||
},
|
||||
'hljs-literal': {
|
||||
color: '#066',
|
||||
},
|
||||
'hljs-symbol': {
|
||||
color: '#066',
|
||||
},
|
||||
'hljs-bullet': {
|
||||
color: '#066',
|
||||
},
|
||||
'hljs-meta': {
|
||||
color: '#066',
|
||||
},
|
||||
'hljs-number': {
|
||||
color: '#066',
|
||||
},
|
||||
'hljs-link': {
|
||||
color: '#066',
|
||||
},
|
||||
'hljs-doctag': {
|
||||
color: '#606',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-type': {
|
||||
color: '#606',
|
||||
},
|
||||
'hljs-attr': {
|
||||
color: '#606',
|
||||
},
|
||||
'hljs-built_in': {
|
||||
color: '#606',
|
||||
},
|
||||
'hljs-builtin-name': {
|
||||
color: '#606',
|
||||
},
|
||||
'hljs-params': {
|
||||
color: '#606',
|
||||
},
|
||||
'hljs-attribute': {
|
||||
color: '#000',
|
||||
},
|
||||
'hljs-subst': {
|
||||
color: '#000',
|
||||
},
|
||||
'hljs-formula': {
|
||||
backgroundColor: '#eee',
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
'hljs-selector-id': {
|
||||
color: '#9B703F',
|
||||
},
|
||||
'hljs-selector-class': {
|
||||
color: '#9B703F',
|
||||
},
|
||||
'hljs-addition': {
|
||||
backgroundColor: '#baeeba',
|
||||
},
|
||||
'hljs-deletion': {
|
||||
backgroundColor: '#ffc8bd',
|
||||
},
|
||||
'hljs-strong': {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-emphasis': {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
});
|
|
@ -4,19 +4,64 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { AtomOneDark } from './atom-one-dark.js';
|
||||
import { Dracula } from './dracula.js';
|
||||
import { GitHub } from './github.js';
|
||||
import { GoogleCode } from './googlecode.js';
|
||||
import { VS } from './vs.js';
|
||||
import { VS2015 } from './vs2015.js';
|
||||
import { XCode } from './xcode.js';
|
||||
import { Theme } from './theme.js';
|
||||
|
||||
export interface ThemeDisplay {
|
||||
name: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
class ThemeManager {
|
||||
private static readonly DEFAULT_THEME: Theme = VS2015;
|
||||
private readonly availableThemes: Theme[];
|
||||
private activeTheme: Theme;
|
||||
|
||||
constructor() {
|
||||
this.availableThemes = [VS2015];
|
||||
this.availableThemes = [
|
||||
AtomOneDark,
|
||||
Dracula,
|
||||
VS,
|
||||
VS2015,
|
||||
GitHub,
|
||||
GoogleCode,
|
||||
XCode,
|
||||
];
|
||||
this.activeTheme = ThemeManager.DEFAULT_THEME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of available theme names.
|
||||
*/
|
||||
getAvailableThemes(): ThemeDisplay[] {
|
||||
return this.availableThemes.map((theme) => ({
|
||||
name: theme.name,
|
||||
active: theme === this.activeTheme,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active theme.
|
||||
* @param themeName The name of the theme to activate.
|
||||
*/
|
||||
setActiveTheme(themeName: string): void {
|
||||
const foundTheme = this.availableThemes.find(
|
||||
(theme) => theme.name === themeName,
|
||||
);
|
||||
|
||||
if (foundTheme) {
|
||||
this.activeTheme = foundTheme;
|
||||
} else {
|
||||
throw new Error(`Theme "${themeName}" not found.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently active theme object.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Theme } from './theme.js';
|
||||
|
||||
export const VS: Theme = new Theme('VS', {
|
||||
hljs: {
|
||||
display: 'block',
|
||||
overflowX: 'auto',
|
||||
padding: '0.5em',
|
||||
background: 'white',
|
||||
color: 'black',
|
||||
},
|
||||
'hljs-comment': {
|
||||
color: '#008000',
|
||||
},
|
||||
'hljs-quote': {
|
||||
color: '#008000',
|
||||
},
|
||||
'hljs-variable': {
|
||||
color: '#008000',
|
||||
},
|
||||
'hljs-keyword': {
|
||||
color: '#00f',
|
||||
},
|
||||
'hljs-selector-tag': {
|
||||
color: '#00f',
|
||||
},
|
||||
'hljs-built_in': {
|
||||
color: '#00f',
|
||||
},
|
||||
'hljs-name': {
|
||||
color: '#00f',
|
||||
},
|
||||
'hljs-tag': {
|
||||
color: '#00f',
|
||||
},
|
||||
'hljs-string': {
|
||||
color: '#a31515',
|
||||
},
|
||||
'hljs-title': {
|
||||
color: '#a31515',
|
||||
},
|
||||
'hljs-section': {
|
||||
color: '#a31515',
|
||||
},
|
||||
'hljs-attribute': {
|
||||
color: '#a31515',
|
||||
},
|
||||
'hljs-literal': {
|
||||
color: '#a31515',
|
||||
},
|
||||
'hljs-template-tag': {
|
||||
color: '#a31515',
|
||||
},
|
||||
'hljs-template-variable': {
|
||||
color: '#a31515',
|
||||
},
|
||||
'hljs-type': {
|
||||
color: '#a31515',
|
||||
},
|
||||
'hljs-addition': {
|
||||
color: '#a31515',
|
||||
},
|
||||
'hljs-deletion': {
|
||||
color: '#2b91af',
|
||||
},
|
||||
'hljs-selector-attr': {
|
||||
color: '#2b91af',
|
||||
},
|
||||
'hljs-selector-pseudo': {
|
||||
color: '#2b91af',
|
||||
},
|
||||
'hljs-meta': {
|
||||
color: '#2b91af',
|
||||
},
|
||||
'hljs-doctag': {
|
||||
color: '#808080',
|
||||
},
|
||||
'hljs-attr': {
|
||||
color: '#f00',
|
||||
},
|
||||
'hljs-symbol': {
|
||||
color: '#00b0e8',
|
||||
},
|
||||
'hljs-bullet': {
|
||||
color: '#00b0e8',
|
||||
},
|
||||
'hljs-link': {
|
||||
color: '#00b0e8',
|
||||
},
|
||||
'hljs-emphasis': {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
'hljs-strong': {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Theme } from './theme.js';
|
||||
|
||||
export const XCode: Theme = new Theme('XCode', {
|
||||
hljs: {
|
||||
display: 'block',
|
||||
overflowX: 'auto',
|
||||
padding: '0.5em',
|
||||
background: '#fff',
|
||||
color: 'black',
|
||||
},
|
||||
'xml .hljs-meta': {
|
||||
color: '#c0c0c0',
|
||||
},
|
||||
'hljs-comment': {
|
||||
color: '#007400',
|
||||
},
|
||||
'hljs-quote': {
|
||||
color: '#007400',
|
||||
},
|
||||
'hljs-tag': {
|
||||
color: '#aa0d91',
|
||||
},
|
||||
'hljs-attribute': {
|
||||
color: '#aa0d91',
|
||||
},
|
||||
'hljs-keyword': {
|
||||
color: '#aa0d91',
|
||||
},
|
||||
'hljs-selector-tag': {
|
||||
color: '#aa0d91',
|
||||
},
|
||||
'hljs-literal': {
|
||||
color: '#aa0d91',
|
||||
},
|
||||
'hljs-name': {
|
||||
color: '#aa0d91',
|
||||
},
|
||||
'hljs-variable': {
|
||||
color: '#3F6E74',
|
||||
},
|
||||
'hljs-template-variable': {
|
||||
color: '#3F6E74',
|
||||
},
|
||||
'hljs-code': {
|
||||
color: '#c41a16',
|
||||
},
|
||||
'hljs-string': {
|
||||
color: '#c41a16',
|
||||
},
|
||||
'hljs-meta-string': {
|
||||
color: '#c41a16',
|
||||
},
|
||||
'hljs-regexp': {
|
||||
color: '#0E0EFF',
|
||||
},
|
||||
'hljs-link': {
|
||||
color: '#0E0EFF',
|
||||
},
|
||||
'hljs-title': {
|
||||
color: '#1c00cf',
|
||||
},
|
||||
'hljs-symbol': {
|
||||
color: '#1c00cf',
|
||||
},
|
||||
'hljs-bullet': {
|
||||
color: '#1c00cf',
|
||||
},
|
||||
'hljs-number': {
|
||||
color: '#1c00cf',
|
||||
},
|
||||
'hljs-section': {
|
||||
color: '#643820',
|
||||
},
|
||||
'hljs-meta': {
|
||||
color: '#643820',
|
||||
},
|
||||
'hljs-class .hljs-title': {
|
||||
color: '#5c2699',
|
||||
},
|
||||
'hljs-type': {
|
||||
color: '#5c2699',
|
||||
},
|
||||
'hljs-built_in': {
|
||||
color: '#5c2699',
|
||||
},
|
||||
'hljs-builtin-name': {
|
||||
color: '#5c2699',
|
||||
},
|
||||
'hljs-params': {
|
||||
color: '#5c2699',
|
||||
},
|
||||
'hljs-attr': {
|
||||
color: '#836C28',
|
||||
},
|
||||
'hljs-subst': {
|
||||
color: '#000',
|
||||
},
|
||||
'hljs-formula': {
|
||||
backgroundColor: '#eee',
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
'hljs-addition': {
|
||||
backgroundColor: '#baeeba',
|
||||
},
|
||||
'hljs-deletion': {
|
||||
backgroundColor: '#ffc8bd',
|
||||
},
|
||||
'hljs-selector-id': {
|
||||
color: '#9b703f',
|
||||
},
|
||||
'hljs-selector-class': {
|
||||
color: '#9b703f',
|
||||
},
|
||||
'hljs-doctag': {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-strong': {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-emphasis': {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue