summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/components
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/components')
-rw-r--r--packages/cli/src/ui/components/AuthDialog.test.tsx76
-rw-r--r--packages/cli/src/ui/components/ThemeDialog.tsx125
-rw-r--r--packages/cli/src/ui/components/messages/DiffRenderer.test.tsx3
-rw-r--r--packages/cli/src/ui/components/messages/DiffRenderer.tsx9
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