mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-21 17:27:54 +00:00
Feature custom themes logic (#2639)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
@@ -15,7 +15,13 @@ import { DefaultLight } from './default-light.js';
|
||||
import { DefaultDark } from './default.js';
|
||||
import { ShadesOfPurple } from './shades-of-purple.js';
|
||||
import { XCode } from './xcode.js';
|
||||
import { Theme, ThemeType } from './theme.js';
|
||||
import {
|
||||
Theme,
|
||||
ThemeType,
|
||||
CustomTheme,
|
||||
createCustomTheme,
|
||||
validateCustomTheme,
|
||||
} from './theme.js';
|
||||
import { ANSI } from './ansi.js';
|
||||
import { ANSILight } from './ansi-light.js';
|
||||
import { NoColorTheme } from './no-color.js';
|
||||
@@ -24,6 +30,7 @@ import process from 'node:process';
|
||||
export interface ThemeDisplay {
|
||||
name: string;
|
||||
type: ThemeType;
|
||||
isCustom?: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_THEME: Theme = DefaultDark;
|
||||
@@ -31,6 +38,7 @@ export const DEFAULT_THEME: Theme = DefaultDark;
|
||||
class ThemeManager {
|
||||
private readonly availableThemes: Theme[];
|
||||
private activeTheme: Theme;
|
||||
private customThemes: Map<string, Theme> = new Map();
|
||||
|
||||
constructor() {
|
||||
this.availableThemes = [
|
||||
@@ -51,19 +59,121 @@ class ThemeManager {
|
||||
this.activeTheme = DEFAULT_THEME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads custom themes from settings.
|
||||
* @param customThemesSettings Custom themes from settings.
|
||||
*/
|
||||
loadCustomThemes(customThemesSettings?: Record<string, CustomTheme>): void {
|
||||
this.customThemes.clear();
|
||||
|
||||
if (!customThemesSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [name, customThemeConfig] of Object.entries(
|
||||
customThemesSettings,
|
||||
)) {
|
||||
const validation = validateCustomTheme(customThemeConfig);
|
||||
if (validation.isValid) {
|
||||
try {
|
||||
const theme = createCustomTheme(customThemeConfig);
|
||||
this.customThemes.set(name, theme);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load custom theme "${name}":`, error);
|
||||
}
|
||||
} else {
|
||||
console.warn(`Invalid custom theme "${name}": ${validation.error}`);
|
||||
}
|
||||
}
|
||||
// If the current active theme is a custom theme, keep it if still valid
|
||||
if (
|
||||
this.activeTheme &&
|
||||
this.activeTheme.type === 'custom' &&
|
||||
this.customThemes.has(this.activeTheme.name)
|
||||
) {
|
||||
this.activeTheme = this.customThemes.get(this.activeTheme.name)!;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active theme.
|
||||
* @param themeName The name of the theme to set as active.
|
||||
* @returns True if the theme was successfully set, false otherwise.
|
||||
*/
|
||||
setActiveTheme(themeName: string | undefined): boolean {
|
||||
const theme = this.findThemeByName(themeName);
|
||||
if (!theme) {
|
||||
return false;
|
||||
}
|
||||
this.activeTheme = theme;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently active theme.
|
||||
* @returns The active theme.
|
||||
*/
|
||||
getActiveTheme(): Theme {
|
||||
if (process.env.NO_COLOR) {
|
||||
return NoColorTheme;
|
||||
}
|
||||
// Ensure the active theme is always valid (fallback to default if not)
|
||||
if (!this.activeTheme || !this.findThemeByName(this.activeTheme.name)) {
|
||||
this.activeTheme = DEFAULT_THEME;
|
||||
}
|
||||
return this.activeTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of custom theme names.
|
||||
* @returns Array of custom theme names.
|
||||
*/
|
||||
getCustomThemeNames(): string[] {
|
||||
return Array.from(this.customThemes.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a theme name is a custom theme.
|
||||
* @param themeName The theme name to check.
|
||||
* @returns True if the theme is custom.
|
||||
*/
|
||||
isCustomTheme(themeName: string): boolean {
|
||||
return this.customThemes.has(themeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of available theme names.
|
||||
*/
|
||||
getAvailableThemes(): ThemeDisplay[] {
|
||||
const sortedThemes = [...this.availableThemes].sort((a, b) => {
|
||||
const builtInThemes = this.availableThemes.map((theme) => ({
|
||||
name: theme.name,
|
||||
type: theme.type,
|
||||
isCustom: false,
|
||||
}));
|
||||
|
||||
const customThemes = Array.from(this.customThemes.values()).map(
|
||||
(theme) => ({
|
||||
name: theme.name,
|
||||
type: theme.type,
|
||||
isCustom: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const allThemes = [...builtInThemes, ...customThemes];
|
||||
|
||||
const sortedThemes = allThemes.sort((a, b) => {
|
||||
const typeOrder = (type: ThemeType): number => {
|
||||
switch (type) {
|
||||
case 'dark':
|
||||
return 1;
|
||||
case 'light':
|
||||
return 2;
|
||||
default:
|
||||
case 'ansi':
|
||||
return 3;
|
||||
case 'custom':
|
||||
return 4; // Custom themes at the end
|
||||
default:
|
||||
return 5;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,50 +184,33 @@ class ThemeManager {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
return sortedThemes.map((theme) => ({
|
||||
name: theme.name,
|
||||
type: theme.type,
|
||||
}));
|
||||
return sortedThemes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active theme.
|
||||
* @param themeName The name of the theme to activate.
|
||||
* @returns True if the theme was successfully set, false otherwise.
|
||||
* Gets a theme by name.
|
||||
* @param themeName The name of the theme to get.
|
||||
* @returns The theme if found, undefined otherwise.
|
||||
*/
|
||||
setActiveTheme(themeName: string | undefined): boolean {
|
||||
const foundTheme = this.findThemeByName(themeName);
|
||||
|
||||
if (foundTheme) {
|
||||
this.activeTheme = foundTheme;
|
||||
return true;
|
||||
} else {
|
||||
// If themeName is undefined, it means we want to set the default theme.
|
||||
// If findThemeByName returns undefined (e.g. default theme is also not found for some reason)
|
||||
// then this will return false.
|
||||
if (themeName === undefined) {
|
||||
this.activeTheme = DEFAULT_THEME;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
getTheme(themeName: string): Theme | undefined {
|
||||
return this.findThemeByName(themeName);
|
||||
}
|
||||
|
||||
findThemeByName(themeName: string | undefined): Theme | undefined {
|
||||
if (!themeName) {
|
||||
return DEFAULT_THEME;
|
||||
}
|
||||
return this.availableThemes.find((theme) => theme.name === themeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently active theme object.
|
||||
*/
|
||||
getActiveTheme(): Theme {
|
||||
if (process.env.NO_COLOR) {
|
||||
return NoColorTheme;
|
||||
// First check built-in themes
|
||||
const builtInTheme = this.availableThemes.find(
|
||||
(theme) => theme.name === themeName,
|
||||
);
|
||||
if (builtInTheme) {
|
||||
return builtInTheme;
|
||||
}
|
||||
return this.activeTheme;
|
||||
|
||||
// Then check custom themes
|
||||
return this.customThemes.get(themeName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user