diff options
| author | Tian Jian Wang <[email protected]> | 2025-06-30 20:25:19 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-07-01 03:25:19 +0000 |
| commit | a4062cb44aab771822fcc4d0fb772bbcde256c1d (patch) | |
| tree | 87fea5fc45211ed743b508bc10b59df7fa98866f /packages/cli/src/ui/utils/TableRenderer.tsx | |
| parent | 1a30b9656fd3d293243d0124d77ff1036bcf29f8 (diff) | |
feat: Add markdown table rendering support (#1955)
Co-authored-by: heartyguy <[email protected]>
Co-authored-by: Allen Hutchison <[email protected]>
Diffstat (limited to 'packages/cli/src/ui/utils/TableRenderer.tsx')
| -rw-r--r-- | packages/cli/src/ui/utils/TableRenderer.tsx | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/packages/cli/src/ui/utils/TableRenderer.tsx b/packages/cli/src/ui/utils/TableRenderer.tsx new file mode 100644 index 00000000..745e5135 --- /dev/null +++ b/packages/cli/src/ui/utils/TableRenderer.tsx @@ -0,0 +1,114 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { Text, Box } from 'ink'; +import { Colors } from '../colors.js'; + +interface TableRendererProps { + headers: string[]; + rows: string[][]; + terminalWidth: number; +} + +/** + * Custom table renderer for markdown tables + * We implement our own instead of using ink-table due to module compatibility issues + */ +export const TableRenderer: React.FC<TableRendererProps> = ({ + headers, + rows, + terminalWidth, +}) => { + // Calculate column widths + const columnWidths = headers.map((header, index) => { + const headerWidth = header.length; + const maxRowWidth = Math.max( + ...rows.map((row) => (row[index] || '').length), + ); + return Math.max(headerWidth, maxRowWidth) + 2; // Add padding + }); + + // Ensure table fits within terminal width + const totalWidth = columnWidths.reduce((sum, width) => sum + width + 1, 1); + const scaleFactor = + totalWidth > terminalWidth ? terminalWidth / totalWidth : 1; + const adjustedWidths = columnWidths.map((width) => + Math.floor(width * scaleFactor), + ); + + const renderCell = (content: string, width: number, isHeader = false) => { + // The actual space for content inside the padding + const contentWidth = Math.max(0, width - 2); + + let cellContent = content; + if (content.length > contentWidth) { + if (contentWidth <= 3) { + // Not enough space for '...' + cellContent = content.substring(0, contentWidth); + } else { + cellContent = content.substring(0, contentWidth - 3) + '...'; + } + } + + // Pad the content to fill the cell + const padded = cellContent.padEnd(contentWidth, ' '); + + if (isHeader) { + return ( + <Text bold color={Colors.AccentCyan}> + {padded} + </Text> + ); + } + return <Text>{padded}</Text>; + }; + + const renderRow = (cells: string[], isHeader = false) => ( + <Box flexDirection="row"> + <Text>│ </Text> + {cells.map((cell, index) => ( + <React.Fragment key={index}> + {renderCell(cell, adjustedWidths[index] || 0, isHeader)} + <Text> │ </Text> + </React.Fragment> + ))} + </Box> + ); + + const renderSeparator = () => { + const separator = adjustedWidths + .map((width) => '─'.repeat(Math.max(0, (width || 0) - 2))) + .join('─┼─'); + return <Text>├─{separator}─┤</Text>; + }; + + const renderTopBorder = () => { + const border = adjustedWidths + .map((width) => '─'.repeat(Math.max(0, (width || 0) - 2))) + .join('─┬─'); + return <Text>┌─{border}─┐</Text>; + }; + + const renderBottomBorder = () => { + const border = adjustedWidths + .map((width) => '─'.repeat(Math.max(0, (width || 0) - 2))) + .join('─┴─'); + return <Text>└─{border}─┘</Text>; + }; + + return ( + <Box flexDirection="column" marginY={1}> + {renderTopBorder()} + {renderRow(headers, true)} + {renderSeparator()} + {rows.map((row, index) => ( + <React.Fragment key={index}>{renderRow(row)}</React.Fragment> + ))} + {renderBottomBorder()} + </Box> + ); +}; |
