Add a theme preview and update the theme when highlight changes. (#151)

This commit is contained in:
Jacob Richman 2025-04-24 11:36:34 -07:00 committed by GitHub
parent d8c0587346
commit 5790a5d7cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 70 additions and 5 deletions

View File

@ -39,8 +39,12 @@ export const App = ({ config, cliVersion }: AppProps) => {
const { elapsedTime, currentLoadingPhrase } = const { elapsedTime, currentLoadingPhrase } =
useLoadingIndicator(streamingState); useLoadingIndicator(streamingState);
const { isThemeDialogOpen, openThemeDialog, handleThemeSelect } = const {
useThemeCommand(); isThemeDialogOpen,
openThemeDialog,
handleThemeSelect,
handleThemeHighlight,
} = useThemeCommand();
useStartupWarnings(setStartupWarnings); useStartupWarnings(setStartupWarnings);
useInitializationErrorEffect(initError, history, setHistory); useInitializationErrorEffect(initError, history, setHistory);
@ -134,7 +138,10 @@ export const App = ({ config, cliVersion }: AppProps) => {
)} )}
{isThemeDialogOpen ? ( {isThemeDialogOpen ? (
<ThemeDialog onSelect={handleThemeSelect} /> <ThemeDialog
onSelect={handleThemeSelect}
onHighlight={handleThemeHighlight}
/>
) : ( ) : (
<> <>
<Box flexDirection="column"> <Box flexDirection="column">

View File

@ -9,13 +9,21 @@ import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { Colors } from '../colors.js';
import { themeManager } from '../themes/theme-manager.js'; import { themeManager } from '../themes/theme-manager.js';
import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
import { DiffRenderer } from './messages/DiffRenderer.js';
import { colorizeCode } from '../utils/CodeColorizer.js';
interface ThemeDialogProps { interface ThemeDialogProps {
/** Callback function when a theme is selected */ /** Callback function when a theme is selected */
onSelect: (themeName: string) => void; onSelect: (themeName: string) => void;
/** Callback function when a theme is highlighted */
onHighlight: (themeName: string) => void;
} }
export function ThemeDialog({ onSelect }: ThemeDialogProps): React.JSX.Element { export function ThemeDialog({
onSelect,
onHighlight,
}: ThemeDialogProps): React.JSX.Element {
const themeItems = themeManager.getAvailableThemes().map((theme) => ({ const themeItems = themeManager.getAvailableThemes().map((theme) => ({
label: theme.active ? `${theme.name} (Active)` : theme.name, label: theme.active ? `${theme.name} (Active)` : theme.name,
value: theme.name, value: theme.name,
@ -38,12 +46,40 @@ export function ThemeDialog({ onSelect }: ThemeDialogProps): React.JSX.Element {
items={themeItems} items={themeItems}
initialIndex={initialIndex} initialIndex={initialIndex}
onSelect={onSelect} onSelect={onSelect}
onHighlight={onHighlight}
/> />
<Box marginTop={1}> <Box marginTop={1}>
<Text color={Colors.SubtleComment}> <Text color={Colors.SubtleComment}>
(Use / arrows and Enter to select) (Use / arrows and Enter to select)
</Text> </Text>
</Box> </Box>
<Box marginTop={1} flexDirection="column">
<Text bold>Preview</Text>
<Box
borderStyle="single"
borderColor={Colors.SubtleComment}
padding={1}
flexDirection="column"
>
{colorizeCode(
`# Source code
print("Hello, World!")
`,
'python',
)}
<Box marginTop={1} />
<DiffRenderer
diffContent={`--- a/old_file.txt
+++ b/new_file.txt
@@ -1,4 +1,5 @@
This is a context line.
-This line was deleted.
+This line was added.
`}
/>
</Box>
</Box>
</Box> </Box>
); );
} }

View File

@ -34,6 +34,9 @@ export interface RadioButtonSelectProps<T> {
/** Function called when an item is selected. Receives the `value` of the selected item. */ /** Function called when an item is selected. Receives the `value` of the selected item. */
onSelect: (value: T) => void; onSelect: (value: T) => void;
/** Function called when an item is highlighted. Receives the `value` of the selected item. */
onHighlight?: (value: T) => void;
} }
/** /**
@ -73,10 +76,16 @@ export function RadioButtonSelect<T>({
items, items,
initialIndex, initialIndex,
onSelect, onSelect,
onHighlight,
}: RadioButtonSelectProps<T>): React.JSX.Element { }: RadioButtonSelectProps<T>): React.JSX.Element {
const handleSelect = (item: RadioSelectItem<T>) => { const handleSelect = (item: RadioSelectItem<T>) => {
onSelect(item.value); onSelect(item.value);
}; };
const handleHighlight = (item: RadioSelectItem<T>) => {
if (onHighlight) {
onHighlight(item.value);
}
};
initialIndex = initialIndex ?? 0; initialIndex = initialIndex ?? 0;
return ( return (
<SelectInput <SelectInput
@ -85,6 +94,7 @@ export function RadioButtonSelect<T>({
items={items} items={items}
initialIndex={initialIndex} initialIndex={initialIndex}
onSelect={handleSelect} onSelect={handleSelect}
onHighlight={handleHighlight}
/> />
); );
} }

View File

@ -11,6 +11,7 @@ interface UseThemeCommandReturn {
isThemeDialogOpen: boolean; isThemeDialogOpen: boolean;
openThemeDialog: () => void; openThemeDialog: () => void;
handleThemeSelect: (themeName: string) => void; handleThemeSelect: (themeName: string) => void;
handleThemeHighlight: (themeName: string) => void;
} }
export const useThemeCommand = (): UseThemeCommandReturn => { export const useThemeCommand = (): UseThemeCommandReturn => {
@ -21,12 +22,22 @@ export const useThemeCommand = (): UseThemeCommandReturn => {
setIsThemeDialogOpen(true); setIsThemeDialogOpen(true);
}, []); }, []);
const handleThemeSelect = useCallback((themeName: string) => { function applyTheme(themeName: string) {
try { try {
themeManager.setActiveTheme(themeName); themeManager.setActiveTheme(themeName);
setForceRender((v) => v + 1); // Trigger potential re-render setForceRender((v) => v + 1); // Trigger potential re-render
} catch (error) { } catch (error) {
console.error(`Error setting theme: ${error}`); console.error(`Error setting theme: ${error}`);
}
}
const handleThemeHighlight = useCallback((themeName: string) => {
applyTheme(themeName);
}, []);
const handleThemeSelect = useCallback((themeName: string) => {
try {
applyTheme(themeName);
} finally { } finally {
setIsThemeDialogOpen(false); // Close the dialog setIsThemeDialogOpen(false); // Close the dialog
} }
@ -36,5 +47,6 @@ export const useThemeCommand = (): UseThemeCommandReturn => {
isThemeDialogOpen, isThemeDialogOpen,
openThemeDialog, openThemeDialog,
handleThemeSelect, handleThemeSelect,
handleThemeHighlight,
}; };
}; };