From cfc697a96d2e716a75e1c3b7f0f34fce81abaf1e Mon Sep 17 00:00:00 2001 From: Taylor Mullen Date: Thu, 17 Apr 2025 18:06:21 -0400 Subject: Run `npm run format` - Also updated README.md accordingly. Part of https://b.corp.google.com/issues/411384603 --- .../src/ui/components/messages/DiffRenderer.tsx | 69 +++++++++++----- .../src/ui/components/messages/ErrorMessage.tsx | 30 +++---- .../src/ui/components/messages/GeminiMessage.tsx | 56 ++++++------- .../cli/src/ui/components/messages/InfoMessage.tsx | 30 +++---- .../messages/ToolConfirmationMessage.tsx | 61 ++++++++++----- .../ui/components/messages/ToolGroupMessage.tsx | 65 ++++++++-------- .../cli/src/ui/components/messages/ToolMessage.tsx | 91 +++++++++++++--------- .../cli/src/ui/components/messages/UserMessage.tsx | 28 +++---- 8 files changed, 255 insertions(+), 175 deletions(-) (limited to 'packages/cli/src/ui/components/messages') diff --git a/packages/cli/src/ui/components/messages/DiffRenderer.tsx b/packages/cli/src/ui/components/messages/DiffRenderer.tsx index 5cae9004..a45efe2a 100644 --- a/packages/cli/src/ui/components/messages/DiffRenderer.tsx +++ b/packages/cli/src/ui/components/messages/DiffRenderer.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Box, Text } from 'ink' +import { Box, Text } from 'ink'; interface DiffLine { type: 'add' | 'del' | 'context' | 'hunk' | 'other'; @@ -30,29 +30,53 @@ function parseDiffWithLineNumbers(diffContent: string): DiffLine[] { continue; } if (!inHunk) { - // Skip standard Git header lines more robustly - if (line.startsWith('--- ') || line.startsWith('+++ ') || line.startsWith('diff --git') || line.startsWith('index ') || line.startsWith('similarity index') || line.startsWith('rename from') || line.startsWith('rename to') || line.startsWith('new file mode') || line.startsWith('deleted file mode')) continue; + // Skip standard Git header lines more robustly + if ( + line.startsWith('--- ') || + line.startsWith('+++ ') || + line.startsWith('diff --git') || + line.startsWith('index ') || + line.startsWith('similarity index') || + line.startsWith('rename from') || + line.startsWith('rename to') || + line.startsWith('new file mode') || + line.startsWith('deleted file mode') + ) + continue; // If it's not a hunk or header, skip (or handle as 'other' if needed) continue; } if (line.startsWith('+')) { currentNewLine++; // Increment before pushing - result.push({ type: 'add', newLine: currentNewLine, content: line.substring(1) }); + result.push({ + type: 'add', + newLine: currentNewLine, + content: line.substring(1), + }); } else if (line.startsWith('-')) { currentOldLine++; // Increment before pushing - result.push({ type: 'del', oldLine: currentOldLine, content: line.substring(1) }); + result.push({ + type: 'del', + oldLine: currentOldLine, + content: line.substring(1), + }); } else if (line.startsWith(' ')) { currentOldLine++; // Increment before pushing currentNewLine++; - result.push({ type: 'context', oldLine: currentOldLine, newLine: currentNewLine, content: line.substring(1) }); - } else if (line.startsWith('\\')) { // Handle "\ No newline at end of file" + result.push({ + type: 'context', + oldLine: currentOldLine, + newLine: currentNewLine, + content: line.substring(1), + }); + } else if (line.startsWith('\\')) { + // Handle "\ No newline at end of file" result.push({ type: 'other', content: line }); } } return result; } - interface DiffRendererProps { diffContent: string; filename?: string; @@ -61,7 +85,10 @@ interface DiffRendererProps { const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization -const DiffRenderer: React.FC = ({ diffContent, tabWidth = DEFAULT_TAB_WIDTH }) => { +const DiffRenderer: React.FC = ({ + diffContent, + tabWidth = DEFAULT_TAB_WIDTH, +}) => { if (!diffContent || typeof diffContent !== 'string') { return No diff content.; } @@ -69,14 +96,15 @@ const DiffRenderer: React.FC = ({ diffContent, tabWidth = DEF const parsedLines = parseDiffWithLineNumbers(diffContent); // 1. Normalize whitespace (replace tabs with spaces) *before* further processing - const normalizedLines = parsedLines.map(line => ({ + const normalizedLines = parsedLines.map((line) => ({ ...line, - content: line.content.replace(/\t/g, ' '.repeat(tabWidth)) + content: line.content.replace(/\t/g, ' '.repeat(tabWidth)), })); // Filter out non-displayable lines (hunks, potentially 'other') using the normalized list - const displayableLines = normalizedLines.filter(l => l.type !== 'hunk' && l.type !== 'other'); - + const displayableLines = normalizedLines.filter( + (l) => l.type !== 'hunk' && l.type !== 'other', + ); if (displayableLines.length === 0) { return ( @@ -93,7 +121,7 @@ const DiffRenderer: React.FC = ({ diffContent, tabWidth = DEF if (line.content.trim() === '') continue; const firstCharIndex = line.content.search(/\S/); // Find index of first non-whitespace char - const currentIndent = (firstCharIndex === -1) ? 0 : firstCharIndex; // Indent is 0 if no non-whitespace found + const currentIndent = firstCharIndex === -1 ? 0 : firstCharIndex; // Indent is 0 if no non-whitespace found baseIndentation = Math.min(baseIndentation, currentIndent); } // If baseIndentation remained Infinity (e.g., no displayable lines with content), default to 0 @@ -102,7 +130,6 @@ const DiffRenderer: React.FC = ({ diffContent, tabWidth = DEF } // --- End Modification --- - return ( {/* Iterate over the lines that should be displayed (already normalized) */} @@ -139,9 +166,13 @@ const DiffRenderer: React.FC = ({ diffContent, tabWidth = DEF return ( // Using your original rendering structure - {gutterNumStr} - {prefixSymbol} - {displayContent} + {gutterNumStr} + + {prefixSymbol}{' '} + + + {displayContent} + ); })} @@ -149,4 +180,4 @@ const DiffRenderer: React.FC = ({ diffContent, tabWidth = DEF ); }; -export default DiffRenderer; \ No newline at end of file +export default DiffRenderer; diff --git a/packages/cli/src/ui/components/messages/ErrorMessage.tsx b/packages/cli/src/ui/components/messages/ErrorMessage.tsx index 7ed8f4f2..fb7f9fa5 100644 --- a/packages/cli/src/ui/components/messages/ErrorMessage.tsx +++ b/packages/cli/src/ui/components/messages/ErrorMessage.tsx @@ -2,23 +2,25 @@ import React from 'react'; import { Text, Box } from 'ink'; interface ErrorMessageProps { - text: string; + text: string; } const ErrorMessage: React.FC = ({ text }) => { - const prefix = '✕ '; - const prefixWidth = prefix.length; + const prefix = '✕ '; + const prefixWidth = prefix.length; - return ( - - - {prefix} - - - {text} - - - ); + return ( + + + {prefix} + + + + {text} + + + + ); }; -export default ErrorMessage; \ No newline at end of file +export default ErrorMessage; diff --git a/packages/cli/src/ui/components/messages/GeminiMessage.tsx b/packages/cli/src/ui/components/messages/GeminiMessage.tsx index fe09eb33..ccccbfc6 100644 --- a/packages/cli/src/ui/components/messages/GeminiMessage.tsx +++ b/packages/cli/src/ui/components/messages/GeminiMessage.tsx @@ -3,42 +3,42 @@ import { Text, Box } from 'ink'; import { MarkdownRenderer } from '../../utils/MarkdownRenderer.js'; interface GeminiMessageProps { - text: string; + text: string; } const GeminiMessage: React.FC = ({ text }) => { - const prefix = '✦ '; - const prefixWidth = prefix.length; + const prefix = '✦ '; + const prefixWidth = prefix.length; - // Handle potentially null or undefined text gracefully - const safeText = text || ''; + // Handle potentially null or undefined text gracefully + const safeText = text || ''; - // Use the static render method from the MarkdownRenderer class - // Pass safeText which is guaranteed to be a string - const renderedBlocks = MarkdownRenderer.render(safeText); - - // If the original text was actually empty/null, render the minimal state - if (!safeText && renderedBlocks.length === 0) { - return ( - - - {prefix} - - - - ); - } + // Use the static render method from the MarkdownRenderer class + // Pass safeText which is guaranteed to be a string + const renderedBlocks = MarkdownRenderer.render(safeText); + // If the original text was actually empty/null, render the minimal state + if (!safeText && renderedBlocks.length === 0) { return ( - - - {prefix} - - - {renderedBlocks} - + + + {prefix} + + ); + } + + return ( + + + {prefix} + + + {renderedBlocks} + + + ); }; -export default GeminiMessage; \ No newline at end of file +export default GeminiMessage; diff --git a/packages/cli/src/ui/components/messages/InfoMessage.tsx b/packages/cli/src/ui/components/messages/InfoMessage.tsx index 8f5841b2..e6f24859 100644 --- a/packages/cli/src/ui/components/messages/InfoMessage.tsx +++ b/packages/cli/src/ui/components/messages/InfoMessage.tsx @@ -2,23 +2,25 @@ import React from 'react'; import { Text, Box } from 'ink'; interface InfoMessageProps { - text: string; + text: string; } const InfoMessage: React.FC = ({ text }) => { - const prefix = 'ℹ '; - const prefixWidth = prefix.length; + const prefix = 'ℹ '; + const prefixWidth = prefix.length; - return ( - - - {prefix} - - - {text} - - - ); + return ( + + + {prefix} + + + + {text} + + + + ); }; -export default InfoMessage; \ No newline at end of file +export default InfoMessage; diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx index a37d2f94..59c2cc42 100644 --- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx @@ -1,7 +1,12 @@ import React from 'react'; import { Box, Text, useInput } from 'ink'; import SelectInput from 'ink-select-input'; -import { ToolCallConfirmationDetails, ToolEditConfirmationDetails, ToolConfirmationOutcome, ToolExecuteConfirmationDetails } from '../../types.js'; // Adjust path as needed +import { + ToolCallConfirmationDetails, + ToolEditConfirmationDetails, + ToolConfirmationOutcome, + ToolExecuteConfirmationDetails, +} from '../../types.js'; // Adjust path as needed import { PartListUnion } from '@google/genai'; import DiffRenderer from './DiffRenderer.js'; import { UI_WIDTH } from '../../constants.js'; @@ -11,7 +16,9 @@ export interface ToolConfirmationMessageProps { onSubmit: (value: PartListUnion) => void; } -function isEditDetails(props: ToolCallConfirmationDetails): props is ToolEditConfirmationDetails { +function isEditDetails( + props: ToolCallConfirmationDetails, +): props is ToolEditConfirmationDetails { return (props as ToolEditConfirmationDetails).fileName !== undefined; } @@ -20,7 +27,9 @@ interface InternalOption { value: ToolConfirmationOutcome; } -const ToolConfirmationMessage: React.FC = ({ confirmationDetails }) => { +const ToolConfirmationMessage: React.FC = ({ + confirmationDetails, +}) => { const { onConfirm } = confirmationDetails; useInput((_, key) => { @@ -39,41 +48,53 @@ const ToolConfirmationMessage: React.FC = ({ confi const options: InternalOption[] = []; if (isEditDetails(confirmationDetails)) { - title = "Edit"; // Title for the outer box + title = 'Edit'; // Title for the outer box // Body content is now the DiffRenderer, passing filename to it // The bordered box is removed from here and handled within DiffRenderer - bodyContent = ( - - ); + bodyContent = ; question = `Apply this change?`; options.push( - { label: '1. Yes, apply change', value: ToolConfirmationOutcome.ProceedOnce }, - { label: "2. Yes, always apply file edits", value: ToolConfirmationOutcome.ProceedAlways }, - { label: '3. No (esc)', value: ToolConfirmationOutcome.Cancel } + { + label: '1. Yes, apply change', + value: ToolConfirmationOutcome.ProceedOnce, + }, + { + label: '2. Yes, always apply file edits', + value: ToolConfirmationOutcome.ProceedAlways, + }, + { label: '3. No (esc)', value: ToolConfirmationOutcome.Cancel }, ); - } else { - const executionProps = confirmationDetails as ToolExecuteConfirmationDetails; - title = "Execute Command"; // Title for the outer box + const executionProps = + confirmationDetails as ToolExecuteConfirmationDetails; + title = 'Execute Command'; // Title for the outer box // For execution, we still need context display and description const commandDisplay = {executionProps.command}; // Combine command and description into bodyContent for layout consistency bodyContent = ( - - {commandDisplay} + + + {commandDisplay} + ); question = `Allow execution?`; const alwaysLabel = `2. Yes, always allow '${executionProps.rootCommand}' commands`; options.push( - { label: '1. Yes, allow once', value: ToolConfirmationOutcome.ProceedOnce }, - { label: alwaysLabel, value: ToolConfirmationOutcome.ProceedAlways }, - { label: '3. No (esc)', value: ToolConfirmationOutcome.Cancel } + { + label: '1. Yes, allow once', + value: ToolConfirmationOutcome.ProceedOnce, + }, + { + label: alwaysLabel, + value: ToolConfirmationOutcome.ProceedAlways, + }, + { label: '3. No (esc)', value: ToolConfirmationOutcome.Cancel }, ); } @@ -82,7 +103,7 @@ const ToolConfirmationMessage: React.FC = ({ confi {/* Body Content (Diff Renderer or Command Info) */} {/* No separate context display here anymore for edits */} - {bodyContent} + {bodyContent} {/* Confirmation Question */} @@ -98,4 +119,4 @@ const ToolConfirmationMessage: React.FC = ({ confi ); }; -export default ToolConfirmationMessage; \ No newline at end of file +export default ToolConfirmationMessage; diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx index 6ef3c5fc..7317345b 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx @@ -6,42 +6,45 @@ import { PartListUnion } from '@google/genai'; import ToolConfirmationMessage from './ToolConfirmationMessage.js'; interface ToolGroupMessageProps { - toolCalls: IndividualToolCallDisplay[]; - onSubmit: (value: PartListUnion) => void; + toolCalls: IndividualToolCallDisplay[]; + onSubmit: (value: PartListUnion) => void; } // Main component renders the border and maps the tools using ToolMessage -const ToolGroupMessage: React.FC = ({ toolCalls, onSubmit }) => { - const hasPending = toolCalls.some(t => t.status === ToolCallStatus.Pending); - const borderColor = hasPending ? "yellow" : "blue"; +const ToolGroupMessage: React.FC = ({ + toolCalls, + onSubmit, +}) => { + const hasPending = toolCalls.some((t) => t.status === ToolCallStatus.Pending); + const borderColor = hasPending ? 'yellow' : 'blue'; - return ( - - {toolCalls.map((tool) => { - return ( - - - {tool.status === ToolCallStatus.Confirming && tool.confirmationDetails && ( - - )} - - ); - })} - {/* Optional: Add padding below the last item if needed, + return ( + + {toolCalls.map((tool) => { + return ( + + + {tool.status === ToolCallStatus.Confirming && + tool.confirmationDetails && ( + + )} + + ); + })} + {/* Optional: Add padding below the last item if needed, though ToolMessage already has some vertical space implicitly */} - {/* {tools.length > 0 && } */} - - ); + {/* {tools.length > 0 && } */} + + ); }; export default ToolGroupMessage; diff --git a/packages/cli/src/ui/components/messages/ToolMessage.tsx b/packages/cli/src/ui/components/messages/ToolMessage.tsx index f8db54c4..cd18dae2 100644 --- a/packages/cli/src/ui/components/messages/ToolMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolMessage.tsx @@ -7,47 +7,68 @@ import DiffRenderer from './DiffRenderer.js'; import { MarkdownRenderer } from '../../utils/MarkdownRenderer.js'; interface ToolMessageProps { - name: string; - description: string; - resultDisplay: ToolResultDisplay | undefined; - status: ToolCallStatus; + name: string; + description: string; + resultDisplay: ToolResultDisplay | undefined; + status: ToolCallStatus; } -const ToolMessage: React.FC = ({ name, description, resultDisplay, status }) => { - const statusIndicatorWidth = 3; - const hasResult = (status === ToolCallStatus.Invoked || status === ToolCallStatus.Canceled) && resultDisplay && resultDisplay.toString().trim().length > 0; +const ToolMessage: React.FC = ({ + name, + description, + resultDisplay, + status, +}) => { + const statusIndicatorWidth = 3; + const hasResult = + (status === ToolCallStatus.Invoked || status === ToolCallStatus.Canceled) && + resultDisplay && + resultDisplay.toString().trim().length > 0; - return ( - - {/* Row for Status Indicator and Tool Info */} - - {/* Status Indicator */} - - {status === ToolCallStatus.Pending && } - {status === ToolCallStatus.Invoked && } - {status === ToolCallStatus.Confirming && ?} - {status === ToolCallStatus.Canceled && -} - - - - - {name} {description} - - - + return ( + + {/* Row for Status Indicator and Tool Info */} + + {/* Status Indicator */} + + {status === ToolCallStatus.Pending && } + {status === ToolCallStatus.Invoked && } + {status === ToolCallStatus.Confirming && ?} + {status === ToolCallStatus.Canceled && ( + + - + + )} + + + + {name} {description} + + + - {hasResult && ( - - - - {/* Use default text color (white) or gray instead of dimColor */} - {typeof resultDisplay === 'string' && {MarkdownRenderer.render(resultDisplay)}} - {typeof resultDisplay === 'object' && } - - + {hasResult && ( + + + + {/* Use default text color (white) or gray instead of dimColor */} + {typeof resultDisplay === 'string' && ( + + {MarkdownRenderer.render(resultDisplay)} + + )} + {typeof resultDisplay === 'object' && ( + )} + - ); + )} + + ); }; export default ToolMessage; diff --git a/packages/cli/src/ui/components/messages/UserMessage.tsx b/packages/cli/src/ui/components/messages/UserMessage.tsx index 0dd451f6..08c0070f 100644 --- a/packages/cli/src/ui/components/messages/UserMessage.tsx +++ b/packages/cli/src/ui/components/messages/UserMessage.tsx @@ -2,23 +2,23 @@ import React from 'react'; import { Text, Box } from 'ink'; interface UserMessageProps { - text: string; + text: string; } const UserMessage: React.FC = ({ text }) => { - const prefix = '> '; - const prefixWidth = prefix.length; + const prefix = '> '; + const prefixWidth = prefix.length; - return ( - - - {prefix} - - - {text} - - - ); + return ( + + + {prefix} + + + {text} + + + ); }; -export default UserMessage; \ No newline at end of file +export default UserMessage; -- cgit v1.2.3