feat(cli): Improve new file diff rendering with syntax highlighting

- Enhance the  component to provide better readability for newly created files.
- Instead of displaying a standard line-by-line diff for new files, extract the added content and render it with syntax highlighting based on the file extension.
- Refactor the existing diff rendering logic into a separate  function.
- Add a helper function  to map common file extensions to language names for syntax highlighting.

Fixes: https://b.corp.google.com/issues/414279447
Signed-off-by: Gemini, your friendly neighborhood code agent.
This commit is contained in:
Taylor Mullen 2025-04-27 23:19:08 -07:00 committed by N. Taylor Mullen
parent a6e9bcb52d
commit a9dc2772dd
1 changed files with 55 additions and 2 deletions

View File

@ -8,6 +8,7 @@ import React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../../colors.js'; import { Colors } from '../../colors.js';
import crypto from 'crypto'; import crypto from 'crypto';
import { colorizeCode } from '../../utils/CodeColorizer.js';
interface DiffLine { interface DiffLine {
type: 'add' | 'del' | 'context' | 'hunk' | 'other'; type: 'add' | 'del' | 'context' | 'hunk' | 'other';
@ -104,6 +105,42 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
const parsedLines = parseDiffWithLineNumbers(diffContent); const parsedLines = parseDiffWithLineNumbers(diffContent);
// Check if the diff represents a new file (only additions and header lines)
const isNewFile = parsedLines.every(
(line) =>
line.type === 'add' ||
line.type === 'hunk' ||
line.type === 'other' ||
line.content.startsWith('diff --git') ||
line.content.startsWith('new file mode'),
);
let renderedOutput;
if (isNewFile) {
// Extract only the added lines' content
const addedContent = parsedLines
.filter((line) => line.type === 'add')
.map((line) => line.content)
.join('\n');
// Attempt to infer language from filename, default to plain text if no filename
const fileExtension = filename?.split('.').pop() || null;
const language = fileExtension
? getLanguageFromExtension(fileExtension)
: null;
renderedOutput = colorizeCode(addedContent, language);
} else {
renderedOutput = renderDiffContent(parsedLines, filename, tabWidth);
}
return renderedOutput;
};
const renderDiffContent = (
parsedLines: DiffLine[],
filename?: string,
tabWidth = DEFAULT_TAB_WIDTH,
) => {
// 1. Normalize whitespace (replace tabs with spaces) *before* further processing // 1. Normalize whitespace (replace tabs with spaces) *before* further processing
const normalizedLines = parsedLines.map((line) => ({ const normalizedLines = parsedLines.map((line) => ({
...line, ...line,
@ -137,11 +174,11 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
if (!isFinite(baseIndentation)) { if (!isFinite(baseIndentation)) {
baseIndentation = 0; baseIndentation = 0;
} }
// --- End Modification ---
const key = filename const key = filename
? `diff-box-${filename}` ? `diff-box-${filename}`
: `diff-box-${crypto.createHash('sha1').update(diffContent).digest('hex')}`; : `diff-box-${crypto.createHash('sha1').update(JSON.stringify(parsedLines)).digest('hex')}`;
return ( return (
<Box flexDirection="column" key={key}> <Box flexDirection="column" key={key}>
{/* Iterate over the lines that should be displayed (already normalized) */} {/* Iterate over the lines that should be displayed (already normalized) */}
@ -193,3 +230,19 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
</Box> </Box>
); );
}; };
const getLanguageFromExtension = (extension: string): string | null => {
const languageMap: { [key: string]: string } = {
'.js': 'javascript',
'.ts': 'typescript',
'.py': 'python',
'.json': 'json',
'.css': 'css',
'.html': 'html',
'.sh': 'bash',
'.md': 'markdown',
'.yaml': 'yaml',
'.yml': 'yaml',
};
return languageMap[extension] || null; // Return null if extension not found
};