diff options
Diffstat (limited to 'packages/cli/src/ui/components')
4 files changed, 150 insertions, 63 deletions
diff --git a/packages/cli/src/ui/components/AuthDialog.test.tsx b/packages/cli/src/ui/components/AuthDialog.test.tsx index b737b2f7..a8893215 100644 --- a/packages/cli/src/ui/components/AuthDialog.test.tsx +++ b/packages/cli/src/ui/components/AuthDialog.test.tsx @@ -31,7 +31,7 @@ describe('AuthDialog', () => { const settings: LoadedSettings = new LoadedSettings( { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, path: '', }, { @@ -41,7 +41,7 @@ describe('AuthDialog', () => { path: '', }, { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, path: '', }, [], @@ -68,11 +68,17 @@ describe('AuthDialog', () => { { settings: { selectedAuthType: undefined, + customThemes: {}, + mcpServers: {}, }, path: '', }, { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, + path: '', + }, + { + settings: { customThemes: {}, mcpServers: {} }, path: '', }, [], @@ -95,11 +101,17 @@ describe('AuthDialog', () => { { settings: { selectedAuthType: undefined, + customThemes: {}, + mcpServers: {}, }, path: '', }, { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, + path: '', + }, + { + settings: { customThemes: {}, mcpServers: {} }, path: '', }, [], @@ -122,11 +134,17 @@ describe('AuthDialog', () => { { settings: { selectedAuthType: undefined, + customThemes: {}, + mcpServers: {}, }, path: '', }, { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, + path: '', + }, + { + settings: { customThemes: {}, mcpServers: {} }, path: '', }, [], @@ -150,11 +168,17 @@ describe('AuthDialog', () => { { settings: { selectedAuthType: undefined, + customThemes: {}, + mcpServers: {}, }, path: '', }, { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, + path: '', + }, + { + settings: { customThemes: {}, mcpServers: {} }, path: '', }, [], @@ -173,11 +197,17 @@ describe('AuthDialog', () => { { settings: { selectedAuthType: undefined, + customThemes: {}, + mcpServers: {}, }, path: '', }, { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, + path: '', + }, + { + settings: { customThemes: {}, mcpServers: {} }, path: '', }, [], @@ -198,11 +228,17 @@ describe('AuthDialog', () => { { settings: { selectedAuthType: undefined, + customThemes: {}, + mcpServers: {}, }, path: '', }, { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, + path: '', + }, + { + settings: { customThemes: {}, mcpServers: {} }, path: '', }, [], @@ -225,17 +261,19 @@ describe('AuthDialog', () => { const onSelect = vi.fn(); const settings: LoadedSettings = new LoadedSettings( { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, path: '', }, { settings: { selectedAuthType: undefined, + customThemes: {}, + mcpServers: {}, }, path: '', }, { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, path: '', }, [], @@ -262,11 +300,19 @@ describe('AuthDialog', () => { const onSelect = vi.fn(); const settings: LoadedSettings = new LoadedSettings( { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, + path: '', + }, + { + settings: { + selectedAuthType: undefined, + customThemes: {}, + mcpServers: {}, + }, path: '', }, { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, path: '', }, [], @@ -296,17 +342,19 @@ describe('AuthDialog', () => { const onSelect = vi.fn(); const settings: LoadedSettings = new LoadedSettings( { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, path: '', }, { settings: { selectedAuthType: AuthType.USE_GEMINI, + customThemes: {}, + mcpServers: {}, }, path: '', }, { - settings: {}, + settings: { customThemes: {}, mcpServers: {} }, path: '', }, [], diff --git a/packages/cli/src/ui/components/ThemeDialog.tsx b/packages/cli/src/ui/components/ThemeDialog.tsx index be8c52a1..41c39b63 100644 --- a/packages/cli/src/ui/components/ThemeDialog.tsx +++ b/packages/cli/src/ui/components/ThemeDialog.tsx @@ -36,22 +36,45 @@ export function ThemeDialog({ SettingScope.User, ); - const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); + // Track the currently highlighted theme name + const [highlightedThemeName, setHighlightedThemeName] = useState< + string | undefined + >(settings.merged.theme || DEFAULT_THEME.name); + // Generate theme items filtered by selected scope + const customThemes = + selectedScope === SettingScope.User + ? settings.user.settings.customThemes || {} + : settings.merged.customThemes || {}; + const builtInThemes = themeManager + .getAvailableThemes() + .filter((theme) => theme.type !== 'custom'); + const customThemeNames = Object.keys(customThemes); + const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); // Generate theme items - const themeItems = themeManager.getAvailableThemes().map((theme) => ({ - label: theme.name, - value: theme.name, - themeNameDisplay: theme.name, - themeTypeDisplay: capitalize(theme.type), - })); + const themeItems = [ + ...builtInThemes.map((theme) => ({ + label: theme.name, + value: theme.name, + themeNameDisplay: theme.name, + themeTypeDisplay: capitalize(theme.type), + })), + ...customThemeNames.map((name) => ({ + label: name, + value: name, + themeNameDisplay: name, + themeTypeDisplay: 'Custom', + })), + ]; const [selectInputKey, setSelectInputKey] = useState(Date.now()); - // Determine which radio button should be initially selected in the theme list - // This should reflect the theme *saved* for the selected scope, or the default + // Find the index of the selected theme, but only if it exists in the list + const selectedThemeName = settings.merged.theme || DEFAULT_THEME.name; const initialThemeIndex = themeItems.findIndex( - (item) => item.value === (settings.merged.theme || DEFAULT_THEME.name), + (item) => item.value === selectedThemeName, ); + // If not found, fallback to the first theme + const safeInitialThemeIndex = initialThemeIndex >= 0 ? initialThemeIndex : 0; const scopeItems = [ { label: 'User Settings', value: SettingScope.User }, @@ -66,6 +89,11 @@ export function ThemeDialog({ [onSelect, selectedScope], ); + const handleThemeHighlight = (themeName: string) => { + setHighlightedThemeName(themeName); + onHighlight(themeName); + }; + const handleScopeHighlight = useCallback((scope: SettingScope) => { setSelectedScope(scope); setSelectInputKey(Date.now()); @@ -182,7 +210,6 @@ export function ThemeDialog({ // The code block is slightly longer than the diff, so give it more space. const codeBlockHeight = Math.ceil(availableHeightForPanes * 0.6); const diffHeight = Math.floor(availableHeightForPanes * 0.4); - const themeType = capitalize(themeManager.getActiveTheme().type); return ( <Box borderStyle="round" @@ -204,9 +231,9 @@ export function ThemeDialog({ <RadioButtonSelect key={selectInputKey} items={themeItems} - initialIndex={initialThemeIndex} + initialIndex={safeInitialThemeIndex} onSelect={handleThemeSelect} - onHighlight={onHighlight} + onHighlight={handleThemeHighlight} isFocused={currenFocusedSection === 'theme'} maxItemsToShow={8} showScrollArrows={true} @@ -233,40 +260,44 @@ export function ThemeDialog({ {/* Right Column: Preview */} <Box flexDirection="column" width="55%" paddingLeft={2}> - <Text bold>{themeType} Theme Preview</Text> - <Box - borderStyle="single" - borderColor={Colors.Gray} - paddingTop={includePadding ? 1 : 0} - paddingBottom={includePadding ? 1 : 0} - paddingLeft={1} - paddingRight={1} - flexDirection="column" - > - {colorizeCode( - `# python function -def fibonacci(n): - a, b = 0, 1 - for _ in range(n): - a, b = b, a + b - return a`, - 'python', - codeBlockHeight, - colorizeCodeWidth, - )} - <Box marginTop={1} /> - <DiffRenderer - diffContent={`--- a/util.py -+++ b/util.py -@@ -1,3 +1,3 @@ - def greet(name): -- print("Hello, " + name) -+ print(f"Hello, {name}!") -`} - availableTerminalHeight={diffHeight} - terminalWidth={colorizeCodeWidth} - /> - </Box> + <Text bold>Preview</Text> + {/* Get the Theme object for the highlighted theme, fallback to default if not found */} + {(() => { + const previewTheme = + themeManager.getTheme( + highlightedThemeName || DEFAULT_THEME.name, + ) || DEFAULT_THEME; + return ( + <Box + borderStyle="single" + borderColor={Colors.Gray} + paddingTop={includePadding ? 1 : 0} + paddingBottom={includePadding ? 1 : 0} + paddingLeft={1} + paddingRight={1} + flexDirection="column" + > + {colorizeCode( + `# function +-def fibonacci(n): +- a, b = 0, 1 +- for _ in range(n): +- a, b = b, a + b +- return a`, + 'python', + codeBlockHeight, + colorizeCodeWidth, + )} + <Box marginTop={1} /> + <DiffRenderer + diffContent={`--- a/old_file.txt\n+++ b/new_file.txt\n@@ -1,6 +1,7 @@\n # function\n-def fibonacci(n):\n- a, b = 0, 1\n- for _ in range(n):\n- a, b = b, a + b\n- return a\n+def fibonacci(n):\n+ a, b = 0, 1\n+ for _ in range(n):\n+ a, b = b, a + b\n+ return a\n+\n+print(fibonacci(10))\n`} + availableTerminalHeight={diffHeight} + terminalWidth={colorizeCodeWidth} + theme={previewTheme} + /> + </Box> + ); + })()} </Box> </Box> <Box marginTop={1}> diff --git a/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx b/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx index a6f906a6..e299f2af 100644 --- a/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx +++ b/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx @@ -44,6 +44,7 @@ index 0000000..e69de29 'python', undefined, 80, + undefined, ); }); @@ -71,6 +72,7 @@ index 0000000..e69de29 null, undefined, 80, + undefined, ); }); @@ -94,6 +96,7 @@ index 0000000..e69de29 null, undefined, 80, + undefined, ); }); diff --git a/packages/cli/src/ui/components/messages/DiffRenderer.tsx b/packages/cli/src/ui/components/messages/DiffRenderer.tsx index 25fb293e..db402517 100644 --- a/packages/cli/src/ui/components/messages/DiffRenderer.tsx +++ b/packages/cli/src/ui/components/messages/DiffRenderer.tsx @@ -93,6 +93,7 @@ interface DiffRendererProps { tabWidth?: number; availableTerminalHeight?: number; terminalWidth: number; + theme?: import('../../themes/theme.js').Theme; } const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization @@ -103,6 +104,7 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({ tabWidth = DEFAULT_TAB_WIDTH, availableTerminalHeight, terminalWidth, + theme, }) => { if (!diffContent || typeof diffContent !== 'string') { return <Text color={Colors.AccentYellow}>No diff content.</Text>; @@ -146,6 +148,7 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({ language, availableTerminalHeight, terminalWidth, + theme, ); } else { renderedOutput = renderDiffContent( @@ -154,6 +157,7 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({ tabWidth, availableTerminalHeight, terminalWidth, + theme, ); } @@ -166,6 +170,7 @@ const renderDiffContent = ( tabWidth = DEFAULT_TAB_WIDTH, availableTerminalHeight: number | undefined, terminalWidth: number, + theme?: import('../../themes/theme.js').Theme, ) => { // 1. Normalize whitespace (replace tabs with spaces) *before* further processing const normalizedLines = parsedLines.map((line) => ({ @@ -246,13 +251,13 @@ const renderDiffContent = ( switch (line.type) { case 'add': gutterNumStr = (line.newLine ?? '').toString(); - color = 'green'; + color = theme?.colors?.AccentGreen || 'green'; prefixSymbol = '+'; lastLineNumber = line.newLine ?? null; break; case 'del': gutterNumStr = (line.oldLine ?? '').toString(); - color = 'red'; + color = theme?.colors?.AccentRed || 'red'; prefixSymbol = '-'; // For deletions, update lastLineNumber based on oldLine if it's advancing. // This helps manage gaps correctly if there are multiple consecutive deletions |
