From a9dc2772dda8d6c814a8f151aeb1e4e796a797d7 Mon Sep 17 00:00:00 2001 From: Taylor Mullen Date: Sun, 27 Apr 2025 23:19:08 -0700 Subject: [PATCH] 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. --- .../ui/components/messages/DiffRenderer.tsx | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/ui/components/messages/DiffRenderer.tsx b/packages/cli/src/ui/components/messages/DiffRenderer.tsx index 4d196e6d..a9afeca3 100644 --- a/packages/cli/src/ui/components/messages/DiffRenderer.tsx +++ b/packages/cli/src/ui/components/messages/DiffRenderer.tsx @@ -8,6 +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'; interface DiffLine { type: 'add' | 'del' | 'context' | 'hunk' | 'other'; @@ -104,6 +105,42 @@ export const DiffRenderer: React.FC = ({ 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 const normalizedLines = parsedLines.map((line) => ({ ...line, @@ -137,11 +174,11 @@ export const DiffRenderer: React.FC = ({ if (!isFinite(baseIndentation)) { baseIndentation = 0; } - // --- End Modification --- const key = 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 ( {/* Iterate over the lines that should be displayed (already normalized) */} @@ -193,3 +230,19 @@ export const DiffRenderer: React.FC = ({ ); }; + +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 +};