summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/utils/TableRenderer.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/utils/TableRenderer.tsx')
-rw-r--r--packages/cli/src/ui/utils/TableRenderer.tsx143
1 files changed, 94 insertions, 49 deletions
diff --git a/packages/cli/src/ui/utils/TableRenderer.tsx b/packages/cli/src/ui/utils/TableRenderer.tsx
index 745e5135..2ec19549 100644
--- a/packages/cli/src/ui/utils/TableRenderer.tsx
+++ b/packages/cli/src/ui/utils/TableRenderer.tsx
@@ -7,6 +7,7 @@
import React from 'react';
import { Text, Box } from 'ink';
import { Colors } from '../colors.js';
+import { RenderInline, getPlainTextLength } from './InlineMarkdownRenderer.js';
interface TableRendererProps {
headers: string[];
@@ -23,11 +24,11 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
rows,
terminalWidth,
}) => {
- // Calculate column widths
+ // Calculate column widths using actual display width after markdown processing
const columnWidths = headers.map((header, index) => {
- const headerWidth = header.length;
+ const headerWidth = getPlainTextLength(header);
const maxRowWidth = Math.max(
- ...rows.map((row) => (row[index] || '').length),
+ ...rows.map((row) => getPlainTextLength(row[index] || '')),
);
return Math.max(headerWidth, maxRowWidth) + 2; // Add padding
});
@@ -40,75 +41,119 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
Math.floor(width * scaleFactor),
);
- const renderCell = (content: string, width: number, isHeader = false) => {
- // The actual space for content inside the padding
+ // Helper function to render a cell with proper width
+ const renderCell = (
+ content: string,
+ width: number,
+ isHeader = false,
+ ): React.ReactNode => {
const contentWidth = Math.max(0, width - 2);
+ const displayWidth = getPlainTextLength(content);
let cellContent = content;
- if (content.length > contentWidth) {
+ if (displayWidth > contentWidth) {
if (contentWidth <= 3) {
- // Not enough space for '...'
- cellContent = content.substring(0, contentWidth);
+ // Just truncate by character count
+ cellContent = content.substring(
+ 0,
+ Math.min(content.length, contentWidth),
+ );
} else {
- cellContent = content.substring(0, contentWidth - 3) + '...';
+ // Truncate preserving markdown formatting using binary search
+ let left = 0;
+ let right = content.length;
+ let bestTruncated = content;
+
+ // Binary search to find the optimal truncation point
+ while (left <= right) {
+ const mid = Math.floor((left + right) / 2);
+ const candidate = content.substring(0, mid);
+ const candidateWidth = getPlainTextLength(candidate);
+
+ if (candidateWidth <= contentWidth - 3) {
+ bestTruncated = candidate;
+ left = mid + 1;
+ } else {
+ right = mid - 1;
+ }
+ }
+
+ cellContent = bestTruncated + '...';
}
}
- // Pad the content to fill the cell
- const padded = cellContent.padEnd(contentWidth, ' ');
+ // Calculate exact padding needed
+ const actualDisplayWidth = getPlainTextLength(cellContent);
+ const paddingNeeded = Math.max(0, contentWidth - actualDisplayWidth);
- if (isHeader) {
- return (
- <Text bold color={Colors.AccentCyan}>
- {padded}
- </Text>
- );
- }
- return <Text>{padded}</Text>;
+ return (
+ <Text>
+ {isHeader ? (
+ <Text bold color={Colors.AccentCyan}>
+ <RenderInline text={cellContent} />
+ </Text>
+ ) : (
+ <RenderInline text={cellContent} />
+ )}
+ {' '.repeat(paddingNeeded)}
+ </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>
- );
+ // Helper function to render border
+ const renderBorder = (type: 'top' | 'middle' | 'bottom'): React.ReactNode => {
+ const chars = {
+ top: { left: '┌', middle: '┬', right: '┐', horizontal: '─' },
+ middle: { left: '├', middle: '┼', right: '┤', horizontal: '─' },
+ bottom: { left: '└', middle: '┴', right: '┘', horizontal: '─' },
+ };
- const renderSeparator = () => {
- const separator = adjustedWidths
- .map((width) => '─'.repeat(Math.max(0, (width || 0) - 2)))
- .join('─┼─');
- return <Text>├─{separator}─┤</Text>;
- };
+ const char = chars[type];
+ const borderParts = adjustedWidths.map((w) => char.horizontal.repeat(w));
+ const border = char.left + borderParts.join(char.middle) + char.right;
- const renderTopBorder = () => {
- const border = adjustedWidths
- .map((width) => '─'.repeat(Math.max(0, (width || 0) - 2)))
- .join('─┬─');
- return <Text>┌─{border}─┐</Text>;
+ return <Text>{border}</Text>;
};
- const renderBottomBorder = () => {
- const border = adjustedWidths
- .map((width) => '─'.repeat(Math.max(0, (width || 0) - 2)))
- .join('─┴─');
- return <Text>└─{border}─┘</Text>;
+ // Helper function to render a table row
+ const renderRow = (cells: string[], isHeader = false): React.ReactNode => {
+ const renderedCells = cells.map((cell, index) => {
+ const width = adjustedWidths[index] || 0;
+ return renderCell(cell || '', width, isHeader);
+ });
+
+ return (
+ <Text>
+ │{' '}
+ {renderedCells.map((cell, index) => (
+ <React.Fragment key={index}>
+ {cell}
+ {index < renderedCells.length - 1 ? ' │ ' : ''}
+ </React.Fragment>
+ ))}{' '}
+ │
+ </Text>
+ );
};
return (
<Box flexDirection="column" marginY={1}>
- {renderTopBorder()}
+ {/* Top border */}
+ {renderBorder('top')}
+
+ {/* Header row */}
{renderRow(headers, true)}
- {renderSeparator()}
+
+ {/* Middle border */}
+ {renderBorder('middle')}
+
+ {/* Data rows */}
{rows.map((row, index) => (
<React.Fragment key={index}>{renderRow(row)}</React.Fragment>
))}
- {renderBottomBorder()}
+
+ {/* Bottom border */}
+ {renderBorder('bottom')}
</Box>
);
};