Add a theme preview and update the theme when highlight changes. (#151)
This commit is contained in:
parent
d8c0587346
commit
5790a5d7cf
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue