/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { useState, useCallback } from 'react'; import { themeManager } from '../themes/theme-manager.js'; import type { LoadedSettings, SettingScope } from '../../config/settings.js'; // Import LoadedSettings, AppSettings, MergedSetting import { type HistoryItem, MessageType } from '../types.js'; import process from 'node:process'; import { t } from '../../i18n/index.js'; interface UseThemeCommandReturn { isThemeDialogOpen: boolean; openThemeDialog: () => void; handleThemeSelect: ( themeName: string | undefined, scope: SettingScope, ) => void; // Added scope handleThemeHighlight: (themeName: string | undefined) => void; } export const useThemeCommand = ( loadedSettings: LoadedSettings, setThemeError: (error: string | null) => void, addItem: (item: Omit, timestamp: number) => void, initialThemeError: string | null, ): UseThemeCommandReturn => { const [isThemeDialogOpen, setIsThemeDialogOpen] = useState(!!initialThemeError); const [themeBeforeDialogOpen, setThemeBeforeDialogOpen] = useState< string | undefined >(themeManager.getActiveTheme().name); const openThemeDialog = useCallback(() => { if (process.env['NO_COLOR']) { addItem( { type: MessageType.INFO, text: t( 'Theme configuration unavailable due to NO_COLOR env variable.', ), }, Date.now(), ); return; } // The theme may temporarily change while navigating the list; keep the // original value to restore it if user cancels with Esc/Ctrl+C. setThemeBeforeDialogOpen(themeManager.getActiveTheme().name); setIsThemeDialogOpen(true); }, [addItem]); const applyTheme = useCallback( (themeName: string | undefined) => { if (!themeManager.setActiveTheme(themeName)) { // If theme is not found, open the theme selection dialog and set error message setIsThemeDialogOpen(true); setThemeError( t('Theme "{{themeName}}" not found.', { themeName: themeName ?? '', }), ); } else { setThemeError(null); // Clear any previous theme error on success } }, [setThemeError], ); const handleThemeHighlight = useCallback( (themeName: string | undefined) => { applyTheme(themeName); }, [applyTheme], ); const handleThemeSelect = useCallback( (themeName: string | undefined, scope: SettingScope) => { // Undefined means "cancel": close dialog and restore original theme. if (themeName === undefined) { applyTheme(themeBeforeDialogOpen); setThemeError(null); setIsThemeDialogOpen(false); return; } try { // Merge user and workspace custom themes (workspace takes precedence) const mergedCustomThemes = { ...(loadedSettings.user.settings.ui?.customThemes || {}), ...(loadedSettings.workspace.settings.ui?.customThemes || {}), }; // Only allow selecting themes available in the merged custom themes or built-in themes const isBuiltIn = themeManager.findThemeByName(themeName); const isCustom = themeName && mergedCustomThemes[themeName]; if (!isBuiltIn && !isCustom) { setThemeError( t('Theme "{{themeName}}" not found in selected scope.', { themeName: themeName ?? '', }), ); setIsThemeDialogOpen(true); return; } loadedSettings.setValue(scope, 'ui.theme', themeName); // Update the merged settings if (loadedSettings.merged.ui?.customThemes) { themeManager.loadCustomThemes(loadedSettings.merged.ui?.customThemes); } applyTheme(loadedSettings.merged.ui?.theme); // Apply the current theme setThemeError(null); } finally { setIsThemeDialogOpen(false); // Close the dialog } }, [applyTheme, loadedSettings, setThemeError, themeBeforeDialogOpen], ); return { isThemeDialogOpen, openThemeDialog, handleThemeSelect, handleThemeHighlight, }; };