summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/themes/theme-manager.ts
diff options
context:
space:
mode:
authorAli Al Jufairi <[email protected]>2025-07-20 16:51:18 +0900
committerGitHub <[email protected]>2025-07-20 07:51:18 +0000
commit76b935d598b895240b9bc2b182eb9f1e1b24be0d (patch)
treecc76fb76a8655f7ab9a064b6c2af750726dd2478 /packages/cli/src/ui/themes/theme-manager.ts
parentc0bfa388c571342265915f8de888a43190c82759 (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.ts163
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);
}
}