Update diff colors (#4747)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
Miguel Solorio 2025-07-23 15:39:22 -07:00 committed by GitHub
parent e21b5c95aa
commit 2e28bb90a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 155 additions and 61 deletions

View File

@ -38,6 +38,12 @@ export const Colors: ColorsTheme = {
get AccentRed() {
return themeManager.getActiveTheme().colors.AccentRed;
},
get DiffAdded() {
return themeManager.getActiveTheme().colors.DiffAdded;
},
get DiffRemoved() {
return themeManager.getActiveTheme().colors.DiffRemoved;
},
get Comment() {
return themeManager.getActiveTheme().colors.Comment;
},

View File

@ -279,18 +279,23 @@ export function ThemeDialog({
>
{colorizeCode(
`# function
-def fibonacci(n):
- a, b = 0, 1
- for _ in range(n):
- a, b = b, a + b
- return a`,
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`}
diffContent={`--- a/util.py
+++ b/util.py
@@ -1,2 +1,2 @@
- print("Hello, " + name)
+ print(f"Hello, {name}!")
`}
availableTerminalHeight={diffHeight}
terminalWidth={colorizeCodeWidth}
theme={previewTheme}

View File

@ -277,9 +277,9 @@ index 123..789 100644
terminalWidth: 30,
height: 6,
expected: `... first 10 lines hidden ...
'test';
21 + const anotherNew =
'test';
;
21 + const anotherNew = 'test'
;
22 console.log('end of
second hunk');`,
},

View File

@ -8,7 +8,7 @@ import React from 'react';
import { Box, Text } from 'ink';
import { Colors } from '../../colors.js';
import crypto from 'crypto';
import { colorizeCode } from '../../utils/CodeColorizer.js';
import { colorizeCode, colorizeLine } from '../../utils/CodeColorizer.js';
import { MaxSizedBox } from '../shared/MaxSizedBox.js';
interface DiffLine {
@ -157,7 +157,6 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
tabWidth,
availableTerminalHeight,
terminalWidth,
theme,
);
}
@ -170,7 +169,6 @@ 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) => ({
@ -191,6 +189,18 @@ const renderDiffContent = (
);
}
const maxLineNumber = Math.max(
0,
...displayableLines.map((l) => l.oldLine ?? 0),
...displayableLines.map((l) => l.newLine ?? 0),
);
const gutterWidth = Math.max(1, maxLineNumber.toString().length);
const fileExtension = filename?.split('.').pop() || null;
const language = fileExtension
? getLanguageFromExtension(fileExtension)
: null;
// Calculate the minimum indentation across all displayable lines
let baseIndentation = Infinity; // Start high to find the minimum
for (const line of displayableLines) {
@ -237,27 +247,25 @@ const renderDiffContent = (
) {
acc.push(
<Box key={`gap-${index}`}>
<Text wrap="truncate">{'═'.repeat(terminalWidth)}</Text>
<Text wrap="truncate" color={Colors.Gray}>
{'═'.repeat(terminalWidth)}
</Text>
</Box>,
);
}
const lineKey = `diff-line-${index}`;
let gutterNumStr = '';
let color: string | undefined = undefined;
let prefixSymbol = ' ';
let dim = false;
switch (line.type) {
case 'add':
gutterNumStr = (line.newLine ?? '').toString();
color = theme?.colors?.AccentGreen || 'green';
prefixSymbol = '+';
lastLineNumber = line.newLine ?? null;
break;
case 'del':
gutterNumStr = (line.oldLine ?? '').toString();
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
@ -268,7 +276,6 @@ const renderDiffContent = (
break;
case 'context':
gutterNumStr = (line.newLine ?? '').toString();
dim = true;
prefixSymbol = ' ';
lastLineNumber = line.newLine ?? null;
break;
@ -280,13 +287,26 @@ const renderDiffContent = (
acc.push(
<Box key={lineKey} flexDirection="row">
<Text color={Colors.Gray}>{gutterNumStr.padEnd(4)} </Text>
<Text color={color} dimColor={dim}>
{prefixSymbol}{' '}
<Text color={Colors.Gray}>
{gutterNumStr.padStart(gutterWidth)}{' '}
</Text>
<Text color={color} dimColor={dim} wrap="wrap">
{displayContent}
{line.type === 'context' ? (
<>
<Text>{prefixSymbol} </Text>
<Text wrap="wrap">
{colorizeLine(displayContent, language)}
</Text>
</>
) : (
<Text
backgroundColor={
line.type === 'add' ? Colors.DiffAdded : Colors.DiffRemoved
}
wrap="wrap"
>
{prefixSymbol} {colorizeLine(displayContent, language)}
</Text>
)}
</Box>,
);
return acc;

View File

@ -17,6 +17,8 @@ const ansiLightColors: ColorsTheme = {
AccentGreen: 'green',
AccentYellow: 'orange',
AccentRed: 'red',
DiffAdded: '#E5F2E5',
DiffRemoved: '#FFE5E5',
Comment: 'gray',
Gray: 'gray',
GradientColors: ['blue', 'green'],

View File

@ -17,6 +17,8 @@ const ansiColors: ColorsTheme = {
AccentGreen: 'green',
AccentYellow: 'yellow',
AccentRed: 'red',
DiffAdded: '#003300',
DiffRemoved: '#4D0000',
Comment: 'gray',
Gray: 'gray',
GradientColors: ['cyan', 'green'],

View File

@ -17,6 +17,8 @@ const atomOneDarkColors: ColorsTheme = {
AccentGreen: '#98c379',
AccentYellow: '#e6c07b',
AccentRed: '#e06c75',
DiffAdded: '#39544E',
DiffRemoved: '#562B2F',
Comment: '#5c6370',
Gray: '#5c6370',
GradientColors: ['#61aeee', '#98c379'],

View File

@ -17,8 +17,10 @@ const ayuLightColors: ColorsTheme = {
AccentGreen: '#86b300',
AccentYellow: '#f2ae49',
AccentRed: '#f07171',
DiffAdded: '#C6EAD8',
DiffRemoved: '#FFCCCC',
Comment: '#ABADB1',
Gray: '#CCCFD3',
Gray: '#a6aaaf',
GradientColors: ['#399ee6', '#86b300'],
};

View File

@ -17,8 +17,10 @@ const ayuDarkColors: ColorsTheme = {
AccentGreen: '#AAD94C',
AccentYellow: '#FFB454',
AccentRed: '#F26D78',
DiffAdded: '#293022',
DiffRemoved: '#3D1215',
Comment: '#646A71',
Gray: '##3D4149',
Gray: '#3D4149',
GradientColors: ['#FFB454', '#F26D78'],
};

View File

@ -17,6 +17,8 @@ const draculaColors: ColorsTheme = {
AccentGreen: '#50fa7b',
AccentYellow: '#f1fa8c',
AccentRed: '#ff5555',
DiffAdded: '#11431d',
DiffRemoved: '#6e1818',
Comment: '#6272a4',
Gray: '#6272a4',
GradientColors: ['#ff79c6', '#8be9fd'],

View File

@ -17,6 +17,8 @@ const githubDarkColors: ColorsTheme = {
AccentGreen: '#85E89D',
AccentYellow: '#FFAB70',
AccentRed: '#F97583',
DiffAdded: '#3C4636',
DiffRemoved: '#502125',
Comment: '#6A737D',
Gray: '#6A737D',
GradientColors: ['#79B8FF', '#85E89D'],

View File

@ -17,6 +17,8 @@ const githubLightColors: ColorsTheme = {
AccentGreen: '#008080',
AccentYellow: '#990073',
AccentRed: '#d14',
DiffAdded: '#C6EAD8',
DiffRemoved: '#FFCCCC',
Comment: '#998',
Gray: '#999',
GradientColors: ['#458', '#008080'],

View File

@ -17,6 +17,8 @@ const googleCodeColors: ColorsTheme = {
AccentGreen: '#080',
AccentYellow: '#660',
AccentRed: '#800',
DiffAdded: '#C6EAD8',
DiffRemoved: '#FEDEDE',
Comment: '#5f6368',
Gray: lightTheme.Gray,
GradientColors: ['#066', '#606'],

View File

@ -17,6 +17,8 @@ const noColorColorsTheme: ColorsTheme = {
AccentGreen: '',
AccentYellow: '',
AccentRed: '',
DiffAdded: '',
DiffRemoved: '',
Comment: '',
Gray: '',
};

View File

@ -22,6 +22,8 @@ const shadesOfPurpleColors: ColorsTheme = {
AccentGreen: '#A5FF90', // Strings and many others
AccentYellow: '#fad000', // Title, main yellow
AccentRed: '#ff628c', // Error/deletion accent
DiffAdded: '#383E45',
DiffRemoved: '#572244',
Comment: '#B362FF', // Comment color (same as AccentPurple)
Gray: '#726c86', // Gray color
GradientColors: ['#4d21fc', '#847ace', '#ff628c'],

View File

@ -23,10 +23,12 @@ const validCustomTheme: CustomTheme = {
AccentPurple: '#8B5CF6',
AccentCyan: '#06B6D4',
AccentGreen: '#3CA84B',
AccentYellow: '#D5A40A',
AccentRed: '#DD4C4C',
Comment: '#008000',
Gray: '#B7BECC',
AccentYellow: 'yellow',
AccentRed: 'red',
DiffAdded: 'green',
DiffRemoved: 'red',
Comment: 'gray',
Gray: 'gray',
};
describe('ThemeManager', () => {

View File

@ -20,6 +20,8 @@ export interface ColorsTheme {
AccentGreen: string;
AccentYellow: string;
AccentRed: string;
DiffAdded: string;
DiffRemoved: string;
Comment: string;
Gray: string;
GradientColors?: string[];
@ -41,8 +43,10 @@ export const lightTheme: ColorsTheme = {
AccentGreen: '#3CA84B',
AccentYellow: '#D5A40A',
AccentRed: '#DD4C4C',
DiffAdded: '#C6EAD8',
DiffRemoved: '#FFCCCC',
Comment: '#008000',
Gray: '#B7BECC',
Gray: '#97a0b0',
GradientColors: ['#4796E4', '#847ACE', '#C3677F'],
};
@ -57,6 +61,8 @@ export const darkTheme: ColorsTheme = {
AccentGreen: '#A6E3A1',
AccentYellow: '#F9E2AF',
AccentRed: '#F38BA8',
DiffAdded: '#28350B',
DiffRemoved: '#430000',
Comment: '#6C7086',
Gray: '#6C7086',
GradientColors: ['#4796E4', '#847ACE', '#C3677F'],
@ -73,6 +79,8 @@ export const ansiTheme: ColorsTheme = {
AccentGreen: 'green',
AccentYellow: 'yellow',
AccentRed: 'red',
DiffAdded: 'green',
DiffRemoved: 'red',
Comment: 'gray',
Gray: 'gray',
};
@ -328,6 +336,8 @@ export function validateCustomTheme(customTheme: Partial<CustomTheme>): {
'AccentGreen',
'AccentYellow',
'AccentRed',
'DiffAdded',
'DiffRemoved',
'Comment',
'Gray',
];
@ -352,6 +362,8 @@ export function validateCustomTheme(customTheme: Partial<CustomTheme>): {
'AccentGreen',
'AccentYellow',
'AccentRed',
'DiffAdded',
'DiffRemoved',
'Comment',
'Gray',
];

View File

@ -17,6 +17,8 @@ const xcodeColors: ColorsTheme = {
AccentGreen: '#007400',
AccentYellow: '#836C28',
AccentRed: '#c41a16',
DiffAdded: '#C6EAD8',
DiffRemoved: '#FEDEDE',
Comment: '#007400',
Gray: '#c0c0c0',
GradientColors: ['#1c00cf', '#007400'],

View File

@ -88,6 +88,34 @@ function renderHastNode(
return null;
}
function highlightAndRenderLine(
line: string,
language: string | null,
theme: Theme,
): React.ReactNode {
try {
const getHighlightedLine = () =>
!language || !lowlight.registered(language)
? lowlight.highlightAuto(line)
: lowlight.highlight(language, line);
const renderedNode = renderHastNode(getHighlightedLine(), theme, undefined);
return renderedNode !== null ? renderedNode : line;
} catch (_error) {
return line;
}
}
export function colorizeLine(
line: string,
language: string | null,
theme?: Theme,
): React.ReactNode {
const activeTheme = theme || themeManager.getActiveTheme();
return highlightAndRenderLine(line, language, activeTheme);
}
/**
* Renders syntax-highlighted code for Ink applications using a selected theme.
*
@ -123,11 +151,6 @@ export function colorizeCode(
}
}
const getHighlightedLines = (line: string) =>
!language || !lowlight.registered(language)
? lowlight.highlightAuto(line)
: lowlight.highlight(language, line);
return (
<MaxSizedBox
maxHeight={availableHeight}
@ -136,17 +159,19 @@ export function colorizeCode(
overflowDirection="top"
>
{lines.map((line, index) => {
const renderedNode = renderHastNode(
getHighlightedLines(line),
const contentToRender = highlightAndRenderLine(
line,
language,
activeTheme,
undefined,
);
const contentToRender = renderedNode !== null ? renderedNode : line;
return (
<Box key={index}>
<Text color={activeTheme.colors.Gray}>
{`${String(index + 1 + hiddenLinesCount).padStart(padWidth, ' ')} `}
{`${String(index + 1 + hiddenLinesCount).padStart(
padWidth,
' ',
)} `}
</Text>
<Text color={activeTheme.defaultColor} wrap="wrap">
{contentToRender}