diff options
| author | Ali Al Jufairi <[email protected]> | 2025-07-20 16:51:18 +0900 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-07-20 07:51:18 +0000 |
| commit | 76b935d598b895240b9bc2b182eb9f1e1b24be0d (patch) | |
| tree | cc76fb76a8655f7ab9a064b6c2af750726dd2478 /packages/cli/src/ui/themes/theme-manager.ts | |
| parent | c0bfa388c571342265915f8de888a43190c82759 (diff) | |
Feature custom themes logic (#2639)
Co-authored-by: Jacob Richman <[email protected]>
Diffstat (limited to 'packages/cli/src/ui/themes/theme-manager.ts')
| -rw-r--r-- | packages/cli/src/ui/themes/theme-manager.ts | 163 |
1 files changed, 128 insertions, 35 deletions
diff --git a/packages/cli/src/ui/themes/theme-manager.ts b/packages/cli/src/ui/themes/theme-manager.ts index 73876e0c..f121a9ec 100644 --- a/packages/cli/src/ui/themes/theme-manager.ts +++ b/packages/cli/src/ui/themes/theme-manager.ts @@ -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 = [ @@ -52,18 +60,120 @@ class ThemeManager { } /** + * 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); } } |
