mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-27 21:44:25 -07:00
feat(cli): support independent light and dark mode themes
This commit is contained in:
@@ -9,7 +9,7 @@ import { useCallback, useState } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { themeManager, DEFAULT_THEME } from '../themes/theme-manager.js';
|
||||
import { pickDefaultThemeName, type Theme } from '../themes/theme.js';
|
||||
import { type Theme } from '../themes/theme.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import { DiffRenderer } from './messages/DiffRenderer.js';
|
||||
import { colorizeCode } from '../utils/CodeColorizer.js';
|
||||
@@ -44,7 +44,10 @@ interface ThemeDialogProps {
|
||||
terminalWidth: number;
|
||||
}
|
||||
|
||||
import { resolveColor } from '../themes/color-utils.js';
|
||||
import { resolveColor , getThemeTypeFromBackgroundColor } from '../themes/color-utils.js';
|
||||
|
||||
import { DefaultLight } from '../themes/default-light.js';
|
||||
import { DefaultDark } from '../themes/default.js';
|
||||
|
||||
function generateThemeItem(
|
||||
name: string,
|
||||
@@ -52,10 +55,6 @@ function generateThemeItem(
|
||||
fullTheme: Theme | undefined,
|
||||
terminalBackgroundColor: string | undefined,
|
||||
) {
|
||||
const isCompatible = fullTheme
|
||||
? themeManager.isThemeCompatible(fullTheme, terminalBackgroundColor)
|
||||
: true;
|
||||
|
||||
const themeBackground = fullTheme
|
||||
? resolveColor(fullTheme.colors.Background)
|
||||
: undefined;
|
||||
@@ -70,10 +69,9 @@ function generateThemeItem(
|
||||
value: name,
|
||||
themeNameDisplay: name,
|
||||
themeTypeDisplay: typeDisplay,
|
||||
themeWarning: isCompatible ? '' : ' (Incompatible)',
|
||||
themeMatch: isBackgroundMatch ? ' (Matches terminal)' : '',
|
||||
key: name,
|
||||
isCompatible,
|
||||
type: fullTheme?.type,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -91,50 +89,44 @@ export function ThemeDialog({
|
||||
SettingScope.User,
|
||||
);
|
||||
|
||||
// Track the currently highlighted theme name
|
||||
const [highlightedThemeName, setHighlightedThemeName] = useState<string>(
|
||||
() => {
|
||||
// If a theme is already set, use it.
|
||||
if (settings.merged.ui.theme) {
|
||||
return settings.merged.ui.theme;
|
||||
}
|
||||
const initialTab =
|
||||
terminalBackgroundColor &&
|
||||
getThemeTypeFromBackgroundColor(terminalBackgroundColor) === 'dark'
|
||||
? 'dark'
|
||||
: 'light';
|
||||
const [activeTab, setActiveTab] = useState<'light' | 'dark'>(initialTab);
|
||||
|
||||
// Otherwise, try to pick a theme that matches the terminal background.
|
||||
return pickDefaultThemeName(
|
||||
terminalBackgroundColor,
|
||||
themeManager.getAllThemes(),
|
||||
DEFAULT_THEME.name,
|
||||
'Default Light',
|
||||
);
|
||||
},
|
||||
);
|
||||
const [highlightedThemeNameLight, setHighlightedThemeNameLight] =
|
||||
useState<string>(() => settings.merged.ui.themeLight || DefaultLight.name);
|
||||
const [highlightedThemeNameDark, setHighlightedThemeNameDark] =
|
||||
useState<string>(() => settings.merged.ui.themeDark || DefaultDark.name);
|
||||
|
||||
const highlightedThemeName =
|
||||
activeTab === 'light'
|
||||
? highlightedThemeNameLight
|
||||
: highlightedThemeNameDark;
|
||||
|
||||
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
|
||||
|
||||
// Generate theme items
|
||||
const themeItems = themeManager
|
||||
.getAvailableThemes()
|
||||
.map((theme) => {
|
||||
const fullTheme = themeManager.getTheme(theme.name);
|
||||
const capitalizedType = capitalize(theme.type);
|
||||
const typeDisplay = theme.name.endsWith(capitalizedType)
|
||||
? ''
|
||||
: capitalizedType;
|
||||
const allThemeItems = themeManager.getAvailableThemes().map((theme) => {
|
||||
const fullTheme = themeManager.getTheme(theme.name);
|
||||
const capitalizedType = capitalize(theme.type);
|
||||
const typeDisplay = theme.name.endsWith(capitalizedType)
|
||||
? ''
|
||||
: capitalizedType;
|
||||
|
||||
return generateThemeItem(
|
||||
theme.name,
|
||||
typeDisplay,
|
||||
fullTheme,
|
||||
terminalBackgroundColor,
|
||||
);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// Show compatible themes first
|
||||
if (a.isCompatible && !b.isCompatible) return -1;
|
||||
if (!a.isCompatible && b.isCompatible) return 1;
|
||||
// Then sort by name
|
||||
return a.label.localeCompare(b.label);
|
||||
});
|
||||
return generateThemeItem(
|
||||
theme.name,
|
||||
typeDisplay,
|
||||
fullTheme,
|
||||
terminalBackgroundColor,
|
||||
);
|
||||
});
|
||||
|
||||
const themeItems = allThemeItems
|
||||
.filter((item) => item.type === activeTab || !item.type) // Filter by tab, allow untyped
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
// Find the index of the selected theme, but only if it exists in the list
|
||||
const initialThemeIndex = themeItems.findIndex(
|
||||
@@ -145,13 +137,18 @@ export function ThemeDialog({
|
||||
|
||||
const handleThemeSelect = useCallback(
|
||||
async (themeName: string) => {
|
||||
await onSelect(themeName, selectedScope);
|
||||
// @ts-expect-error adding extra argument for the updated hook
|
||||
await onSelect(themeName, selectedScope, activeTab);
|
||||
},
|
||||
[onSelect, selectedScope],
|
||||
[onSelect, selectedScope, activeTab],
|
||||
);
|
||||
|
||||
const handleThemeHighlight = (themeName: string) => {
|
||||
setHighlightedThemeName(themeName);
|
||||
if (activeTab === 'light') {
|
||||
setHighlightedThemeNameLight(themeName);
|
||||
} else {
|
||||
setHighlightedThemeNameDark(themeName);
|
||||
}
|
||||
onHighlight(themeName);
|
||||
};
|
||||
|
||||
@@ -161,9 +158,10 @@ export function ThemeDialog({
|
||||
|
||||
const handleScopeSelect = useCallback(
|
||||
async (scope: LoadableSettingScope) => {
|
||||
await onSelect(highlightedThemeName, scope);
|
||||
// @ts-expect-error adding extra argument
|
||||
await onSelect(highlightedThemeName, scope, activeTab);
|
||||
},
|
||||
[onSelect, highlightedThemeName],
|
||||
[onSelect, highlightedThemeName, activeTab],
|
||||
);
|
||||
|
||||
const [mode, setMode] = useState<'theme' | 'scope'>('theme');
|
||||
@@ -178,6 +176,18 @@ export function ThemeDialog({
|
||||
onCancel();
|
||||
return true;
|
||||
}
|
||||
if (mode === 'theme') {
|
||||
if (key.name === 'left' && activeTab === 'dark') {
|
||||
setActiveTab('light');
|
||||
onHighlight(highlightedThemeNameLight);
|
||||
return true;
|
||||
}
|
||||
if (key.name === 'right' && activeTab === 'light') {
|
||||
setActiveTab('dark');
|
||||
onHighlight(highlightedThemeNameDark);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
{ isActive: true },
|
||||
@@ -185,7 +195,7 @@ export function ThemeDialog({
|
||||
|
||||
// Generate scope message for theme setting
|
||||
const otherScopeModifiedMessage = getScopeMessageForSetting(
|
||||
'ui.theme',
|
||||
activeTab === 'light' ? 'ui.themeLight' : 'ui.themeDark',
|
||||
selectedScope,
|
||||
settings,
|
||||
);
|
||||
@@ -273,6 +283,38 @@ export function ThemeDialog({
|
||||
{otherScopeModifiedMessage}
|
||||
</Text>
|
||||
</Text>
|
||||
<Box
|
||||
flexDirection="row"
|
||||
paddingLeft={2}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
>
|
||||
<Text
|
||||
color={
|
||||
activeTab === 'light'
|
||||
? theme.text.primary
|
||||
: theme.text.secondary
|
||||
}
|
||||
underline={activeTab === 'light'}
|
||||
bold={activeTab === 'light'}
|
||||
>
|
||||
{' '}
|
||||
Light{' '}
|
||||
</Text>
|
||||
<Text> | </Text>
|
||||
<Text
|
||||
color={
|
||||
activeTab === 'dark'
|
||||
? theme.text.primary
|
||||
: theme.text.secondary
|
||||
}
|
||||
underline={activeTab === 'dark'}
|
||||
bold={activeTab === 'dark'}
|
||||
>
|
||||
{' '}
|
||||
Dark{' '}
|
||||
</Text>
|
||||
</Box>
|
||||
<RadioButtonSelect
|
||||
items={themeItems}
|
||||
initialIndex={safeInitialThemeIndex}
|
||||
@@ -283,9 +325,8 @@ export function ThemeDialog({
|
||||
showScrollArrows={true}
|
||||
showNumbers={mode === 'theme'}
|
||||
renderItem={(item, { titleColor }) => {
|
||||
// We know item has themeWarning because we put it there, but we need to cast or access safely
|
||||
// We know item has themeMatch because we put it there, but we need to cast or access safely
|
||||
const itemWithExtras = item as typeof item & {
|
||||
themeWarning?: string;
|
||||
themeMatch?: string;
|
||||
};
|
||||
|
||||
@@ -312,11 +353,6 @@ export function ThemeDialog({
|
||||
{itemWithExtras.themeMatch}
|
||||
</Text>
|
||||
)}
|
||||
{itemWithExtras.themeWarning && (
|
||||
<Text color={theme.status.warning}>
|
||||
{itemWithExtras.themeWarning}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
@@ -335,48 +371,65 @@ export function ThemeDialog({
|
||||
<Text bold color={theme.text.primary}>
|
||||
Preview
|
||||
</Text>
|
||||
<Box
|
||||
borderStyle="single"
|
||||
borderColor={theme.border.default}
|
||||
paddingTop={includePadding ? 1 : 0}
|
||||
paddingBottom={includePadding ? 1 : 0}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
flexDirection="column"
|
||||
>
|
||||
{colorizeCode({
|
||||
code: `# function
|
||||
{/* Get the Theme object for the highlighted theme, fall back to default if not found */}
|
||||
{(() => {
|
||||
const previewTheme =
|
||||
themeManager.getTheme(
|
||||
highlightedThemeName || DEFAULT_THEME.name,
|
||||
) || DEFAULT_THEME;
|
||||
|
||||
const effectiveBackground =
|
||||
activeTab === 'light' ? '#ffffff' : '#000000';
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
borderStyle="single"
|
||||
borderColor={theme.border.default}
|
||||
paddingTop={includePadding ? 1 : 0}
|
||||
paddingBottom={includePadding ? 1 : 0}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
flexDirection="column"
|
||||
backgroundColor={effectiveBackground}
|
||||
>
|
||||
{colorizeCode({
|
||||
code: `# function
|
||||
def fibonacci(n):
|
||||
a, b = 0, 1
|
||||
for _ in range(n):
|
||||
a, b = b, a + b
|
||||
return a`,
|
||||
language: 'python',
|
||||
availableHeight:
|
||||
isAlternateBuffer === false ? codeBlockHeight : undefined,
|
||||
maxWidth: colorizeCodeWidth,
|
||||
settings,
|
||||
})}
|
||||
<Box marginTop={1} />
|
||||
<DiffRenderer
|
||||
diffContent={`--- a/util.py
|
||||
language: 'python',
|
||||
availableHeight:
|
||||
isAlternateBuffer === false ? codeBlockHeight : undefined,
|
||||
maxWidth: colorizeCodeWidth,
|
||||
settings,
|
||||
theme: previewTheme,
|
||||
})}
|
||||
<Box marginTop={1} />
|
||||
<DiffRenderer
|
||||
diffContent={`--- a/util.py
|
||||
+++ b/util.py
|
||||
@@ -1,2 +1,2 @@
|
||||
- print("Hello, " + name)
|
||||
+ print(f"Hello, {name}!")
|
||||
`}
|
||||
availableTerminalHeight={
|
||||
isAlternateBuffer === false ? diffHeight : undefined
|
||||
}
|
||||
terminalWidth={colorizeCodeWidth}
|
||||
theme={previewTheme}
|
||||
/>
|
||||
</Box>
|
||||
{isDevelopment && (
|
||||
<Box marginTop={1}>
|
||||
<ColorsDisplay activeTheme={previewTheme} />
|
||||
</Box>
|
||||
)}
|
||||
availableTerminalHeight={
|
||||
isAlternateBuffer === false ? diffHeight : undefined
|
||||
}
|
||||
terminalWidth={colorizeCodeWidth}
|
||||
theme={previewTheme}
|
||||
/>
|
||||
</Box>
|
||||
{isDevelopment && (
|
||||
<Box marginTop={1}>
|
||||
<ColorsDisplay activeTheme={previewTheme} />
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
|
||||
@@ -4,20 +4,19 @@ exports[`Initial Theme Selection > should default to a dark theme when terminal
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ > Select Theme Preview │
|
||||
│ ▲ ┌─────────────────────────────────────────────────┐ │
|
||||
│ 1. ANSI Dark │ │ │
|
||||
│ 2. Atom One Dark │ 1 # function │ │
|
||||
│ 3. Ayu Dark │ 2 def fibonacci(n): │ │
|
||||
│ ● 4. Default Dark │ 3 a, b = 0, 1 │ │
|
||||
│ 5. Dracula Dark │ 4 for _ in range(n): │ │
|
||||
│ 6. GitHub Dark │ 5 a, b = b, a + b │ │
|
||||
│ 7. Holiday Dark │ 6 return a │ │
|
||||
│ 8. Shades Of Purple Dark │ │ │
|
||||
│ 9. Solarized Dark │ 1 - print("Hello, " + name) │ │
|
||||
│ 10. ANSI Light │ 1 + print(f"Hello, {name}!") │ │
|
||||
│ 11. Ayu Light │ │ │
|
||||
│ 12. Default Light └─────────────────────────────────────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ Light | Dark │ │ │
|
||||
│ │ 1 # function │ │
|
||||
│ 1. ANSI Dark │ 2 def fibonacci(n): │ │
|
||||
│ 2. Atom One Dark │ 3 a, b = 0, 1 │ │
|
||||
│ 3. Ayu Dark │ 4 for _ in range(n): │ │
|
||||
│ ● 4. Default Dark │ 5 a, b = b, a + b │ │
|
||||
│ 5. Dracula Dark │ 6 return a │ │
|
||||
│ 6. GitHub Dark │ │ │
|
||||
│ 7. Holiday Dark │ 1 - print("Hello, " + name) │ │
|
||||
│ 8. Shades Of Purple Dark │ 1 + print(f"Hello, {name}!") │ │
|
||||
│ 9. Solarized Dark │ │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ (Use Enter to select, Tab to configure scope, Esc to close) │
|
||||
│ │
|
||||
@@ -29,20 +28,19 @@ exports[`Initial Theme Selection > should default to a light theme when terminal
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ > Select Theme Preview │
|
||||
│ ▲ ┌─────────────────────────────────────────────────┐ │
|
||||
│ 1. ANSI Light │ │ │
|
||||
│ 2. Ayu Light │ 1 # function │ │
|
||||
│ ● 3. Default Light │ 2 def fibonacci(n): │ │
|
||||
│ 4. GitHub Light │ 3 a, b = 0, 1 │ │
|
||||
│ 5. Google Code Light │ 4 for _ in range(n): │ │
|
||||
│ 6. Solarized Light │ 5 a, b = b, a + b │ │
|
||||
│ 7. Xcode Light │ 6 return a │ │
|
||||
│ 8. ANSI Dark (Incompatible) │ │ │
|
||||
│ 9. Atom One Dark (Incompatible) │ 1 - print("Hello, " + name) │ │
|
||||
│ 10. Ayu Dark (Incompatible) │ 1 + print(f"Hello, {name}!") │ │
|
||||
│ 11. Default Dark (Incompatible) │ │ │
|
||||
│ 12. Dracula Dark (Incompatible) └─────────────────────────────────────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ Light | Dark │ │ │
|
||||
│ │ 1 # function │ │
|
||||
│ 1. ANSI Light │ 2 def fibonacci(n): │ │
|
||||
│ 2. Ayu Light │ 3 a, b = 0, 1 │ │
|
||||
│ ● 3. Default Light │ 4 for _ in range(n): │ │
|
||||
│ 4. GitHub Light │ 5 a, b = b, a + b │ │
|
||||
│ 5. Google Code Light │ 6 return a │ │
|
||||
│ 6. Solarized Light │ │ │
|
||||
│ 7. Xcode Light │ 1 - print("Hello, " + name) │ │
|
||||
│ │ 1 + print(f"Hello, {name}!") │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ (Use Enter to select, Tab to configure scope, Esc to close) │
|
||||
│ │
|
||||
@@ -54,20 +52,19 @@ exports[`Initial Theme Selection > should use the theme from settings even if te
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ > Select Theme Preview │
|
||||
│ ▲ ┌─────────────────────────────────────────────────┐ │
|
||||
│ ● 1. ANSI Dark │ │ │
|
||||
│ 2. Atom One Dark │ 1 # function │ │
|
||||
│ 3. Ayu Dark │ 2 def fibonacci(n): │ │
|
||||
│ 4. Default Dark │ 3 a, b = 0, 1 │ │
|
||||
│ 5. Dracula Dark │ 4 for _ in range(n): │ │
|
||||
│ 6. GitHub Dark │ 5 a, b = b, a + b │ │
|
||||
│ 7. Holiday Dark │ 6 return a │ │
|
||||
│ 8. Shades Of Purple Dark │ │ │
|
||||
│ 9. Solarized Dark │ 1 - print("Hello, " + name) │ │
|
||||
│ 10. ANSI Light │ 1 + print(f"Hello, {name}!") │ │
|
||||
│ 11. Ayu Light │ │ │
|
||||
│ 12. Default Light └─────────────────────────────────────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ Light | Dark │ │ │
|
||||
│ │ 1 # function │ │
|
||||
│ 1. ANSI Dark │ 2 def fibonacci(n): │ │
|
||||
│ 2. Atom One Dark │ 3 a, b = 0, 1 │ │
|
||||
│ 3. Ayu Dark │ 4 for _ in range(n): │ │
|
||||
│ ● 4. Default Dark │ 5 a, b = b, a + b │ │
|
||||
│ 5. Dracula Dark │ 6 return a │ │
|
||||
│ 6. GitHub Dark │ │ │
|
||||
│ 7. Holiday Dark │ 1 - print("Hello, " + name) │ │
|
||||
│ 8. Shades Of Purple Dark │ 1 + print(f"Hello, {name}!") │ │
|
||||
│ 9. Solarized Dark │ │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ (Use Enter to select, Tab to configure scope, Esc to close) │
|
||||
│ │
|
||||
@@ -93,20 +90,19 @@ exports[`ThemeDialog Snapshots > should render correctly in theme selection mode
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ > Select Theme Preview │
|
||||
│ ▲ ┌─────────────────────────────────────────────────┐ │
|
||||
│ ● 1. ANSI Dark (Matches terminal) │ │ │
|
||||
│ 2. Atom One Dark │ 1 # function │ │
|
||||
│ 3. Ayu Dark │ 2 def fibonacci(n): │ │
|
||||
│ 4. Default Dark │ 3 a, b = 0, 1 │ │
|
||||
│ 5. Dracula Dark │ 4 for _ in range(n): │ │
|
||||
│ 6. GitHub Dark │ 5 a, b = b, a + b │ │
|
||||
│ 7. Holiday Dark │ 6 return a │ │
|
||||
│ 8. Shades Of Purple Dark │ │ │
|
||||
│ 9. Solarized Dark │ 1 - print("Hello, " + name) │ │
|
||||
│ 10. ANSI Light │ 1 + print(f"Hello, {name}!") │ │
|
||||
│ 11. Ayu Light │ │ │
|
||||
│ 12. Default Light └─────────────────────────────────────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ Light | Dark │ │ │
|
||||
│ │ 1 # function │ │
|
||||
│ 1. ANSI Dark (Matches terminal) │ 2 def fibonacci(n): │ │
|
||||
│ 2. Atom One Dark │ 3 a, b = 0, 1 │ │
|
||||
│ 3. Ayu Dark │ 4 for _ in range(n): │ │
|
||||
│ ● 4. Default Dark │ 5 a, b = b, a + b │ │
|
||||
│ 5. Dracula Dark │ 6 return a │ │
|
||||
│ 6. GitHub Dark │ │ │
|
||||
│ 7. Holiday Dark │ 1 - print("Hello, " + name) │ │
|
||||
│ 8. Shades Of Purple Dark │ 1 + print(f"Hello, {name}!") │ │
|
||||
│ 9. Solarized Dark │ │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ (Use Enter to select, Tab to configure scope, Esc to close) │
|
||||
│ │
|
||||
|
||||
@@ -11,6 +11,7 @@ import crypto from 'node:crypto';
|
||||
import { colorizeCode, colorizeLine } from '../../utils/CodeColorizer.js';
|
||||
import { MaxSizedBox } from '../shared/MaxSizedBox.js';
|
||||
import { theme as semanticTheme } from '../../semantic-colors.js';
|
||||
import { themeManager } from '../../themes/theme-manager.js';
|
||||
import type { Theme } from '../../themes/theme.js';
|
||||
import { useSettings } from '../../contexts/SettingsContext.js';
|
||||
|
||||
@@ -177,6 +178,7 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
|
||||
tabWidth,
|
||||
availableTerminalHeight,
|
||||
terminalWidth,
|
||||
theme,
|
||||
);
|
||||
}
|
||||
}, [
|
||||
@@ -201,7 +203,11 @@ const renderDiffContent = (
|
||||
tabWidth = DEFAULT_TAB_WIDTH,
|
||||
availableTerminalHeight: number | undefined,
|
||||
terminalWidth: number,
|
||||
theme?: Theme,
|
||||
) => {
|
||||
const activeTheme = theme || themeManager.getActiveTheme();
|
||||
const semanticColors = activeTheme.semanticColors;
|
||||
|
||||
// 1. Normalize whitespace (replace tabs with spaces) *before* further processing
|
||||
const normalizedLines = parsedLines.map((line) => ({
|
||||
...line,
|
||||
@@ -217,7 +223,7 @@ const renderDiffContent = (
|
||||
return (
|
||||
<Box
|
||||
borderStyle="round"
|
||||
borderColor={semanticTheme.border.default}
|
||||
borderColor={semanticColors.border.default}
|
||||
padding={1}
|
||||
>
|
||||
<Text dimColor>No changes detected.</Text>
|
||||
@@ -284,7 +290,7 @@ const renderDiffContent = (
|
||||
borderRight={false}
|
||||
borderBottom={false}
|
||||
width={terminalWidth}
|
||||
borderColor={semanticTheme.text.secondary}
|
||||
borderColor={semanticColors.text.secondary}
|
||||
></Box>
|
||||
</Box>,
|
||||
);
|
||||
@@ -323,10 +329,16 @@ const renderDiffContent = (
|
||||
|
||||
const backgroundColor =
|
||||
line.type === 'add'
|
||||
? semanticTheme.background.diff.added
|
||||
? semanticColors.background.diff.added
|
||||
: line.type === 'del'
|
||||
? semanticTheme.background.diff.removed
|
||||
? semanticColors.background.diff.removed
|
||||
: undefined;
|
||||
|
||||
const effectiveDefaultColor =
|
||||
activeTheme.defaultColor !== ''
|
||||
? activeTheme.defaultColor
|
||||
: activeTheme.colors.Foreground;
|
||||
|
||||
acc.push(
|
||||
<Box key={lineKey} flexDirection="row">
|
||||
<Box
|
||||
@@ -336,32 +348,35 @@ const renderDiffContent = (
|
||||
backgroundColor={backgroundColor}
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Text color={semanticTheme.text.secondary}>{gutterNumStr}</Text>
|
||||
<Text color={semanticColors.text.secondary}>{gutterNumStr}</Text>
|
||||
</Box>
|
||||
{line.type === 'context' ? (
|
||||
<>
|
||||
<Text color={effectiveDefaultColor}>
|
||||
<Text>{prefixSymbol} </Text>
|
||||
<Text wrap="wrap">{colorizeLine(displayContent, language)}</Text>
|
||||
</>
|
||||
<Text wrap="wrap">
|
||||
{colorizeLine(displayContent, language, activeTheme)}
|
||||
</Text>
|
||||
</Text>
|
||||
) : (
|
||||
<Text
|
||||
backgroundColor={
|
||||
line.type === 'add'
|
||||
? semanticTheme.background.diff.added
|
||||
: semanticTheme.background.diff.removed
|
||||
? semanticColors.background.diff.added
|
||||
: semanticColors.background.diff.removed
|
||||
}
|
||||
color={effectiveDefaultColor}
|
||||
wrap="wrap"
|
||||
>
|
||||
<Text
|
||||
color={
|
||||
line.type === 'add'
|
||||
? semanticTheme.status.success
|
||||
: semanticTheme.status.error
|
||||
? semanticColors.status.success
|
||||
: semanticColors.status.error
|
||||
}
|
||||
>
|
||||
{prefixSymbol}
|
||||
</Text>{' '}
|
||||
{colorizeLine(displayContent, language)}
|
||||
{colorizeLine(displayContent, language, activeTheme)}
|
||||
</Text>
|
||||
)}
|
||||
</Box>,
|
||||
|
||||
@@ -14,8 +14,8 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
|
||||
'test';
|
||||
21 + const anotherNew =
|
||||
'test';
|
||||
22 console.log('end of second
|
||||
hunk');
|
||||
22 console.log('end of
|
||||
second hunk');
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -107,8 +107,8 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
|
||||
'test';
|
||||
21 + const anotherNew =
|
||||
'test';
|
||||
22 console.log('end of second
|
||||
hunk');
|
||||
22 console.log('end of
|
||||
second hunk');
|
||||
"
|
||||
`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user