mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)
* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-2): 格式化 commands (79 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-5): 格式化 components其余 + hooks + tools (232 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md - README.md: 大幅重写,更详细版本历史和配置示例 - Run.ps1: 新增 Windows 启动脚本 - TODO.md: 新增包完成清单 - V6.md: 删除(架构重构规划已不适用) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复以前的问题 * fix: 修复 login 面板的问题 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,38 +1,39 @@
|
||||
import type { Token, Tokens } from 'marked';
|
||||
import React from 'react';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
||||
import { stringWidth } from '../ink/stringWidth.js';
|
||||
import { wrapAnsi } from '../ink/wrapAnsi.js';
|
||||
import { Ansi, useTheme } from '../ink.js';
|
||||
import type { CliHighlight } from '../utils/cliHighlight.js';
|
||||
import { formatToken, padAligned } from '../utils/markdown.js';
|
||||
import type { Token, Tokens } from 'marked'
|
||||
import React from 'react'
|
||||
import stripAnsi from 'strip-ansi'
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize.js'
|
||||
import { stringWidth } from '../ink/stringWidth.js'
|
||||
import { wrapAnsi } from '../ink/wrapAnsi.js'
|
||||
import { Ansi, useTheme } from '../ink.js'
|
||||
import type { CliHighlight } from '../utils/cliHighlight.js'
|
||||
import { formatToken, padAligned } from '../utils/markdown.js'
|
||||
|
||||
/** Accounts for parent indentation (e.g. message dot prefix) and terminal
|
||||
* resize races. Without enough margin the table overflows its layout box
|
||||
* and Ink's clip truncates differently on alternating frames, causing an
|
||||
* infinite flicker loop in scrollback. */
|
||||
const SAFETY_MARGIN = 4;
|
||||
const SAFETY_MARGIN = 4
|
||||
|
||||
/** Minimum column width to prevent degenerate layouts */
|
||||
const MIN_COLUMN_WIDTH = 3;
|
||||
const MIN_COLUMN_WIDTH = 3
|
||||
|
||||
/**
|
||||
* Maximum number of lines per row before switching to vertical format.
|
||||
* When wrapping would make rows taller than this, vertical (key-value)
|
||||
* format provides better readability.
|
||||
*/
|
||||
const MAX_ROW_LINES = 4;
|
||||
const MAX_ROW_LINES = 4
|
||||
|
||||
/** ANSI escape codes for text formatting */
|
||||
const ANSI_BOLD_START = '\x1b[1m';
|
||||
const ANSI_BOLD_END = '\x1b[22m';
|
||||
const ANSI_BOLD_START = '\x1b[1m'
|
||||
const ANSI_BOLD_END = '\x1b[22m'
|
||||
|
||||
type Props = {
|
||||
token: Tokens.Table;
|
||||
highlight: CliHighlight | null;
|
||||
token: Tokens.Table
|
||||
highlight: CliHighlight | null
|
||||
/** Override terminal width (useful for testing) */
|
||||
forceWidth?: number;
|
||||
};
|
||||
forceWidth?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap text to fit within a given width, returning array of lines.
|
||||
@@ -41,24 +42,26 @@ type Props = {
|
||||
* @param hard - If true, break words that exceed width (needed when columns
|
||||
* are narrower than the longest word). Default false.
|
||||
*/
|
||||
function wrapText(text: string, width: number, options?: {
|
||||
hard?: boolean;
|
||||
}): string[] {
|
||||
if (width <= 0) return [text];
|
||||
function wrapText(
|
||||
text: string,
|
||||
width: number,
|
||||
options?: { hard?: boolean },
|
||||
): string[] {
|
||||
if (width <= 0) return [text]
|
||||
// Strip trailing whitespace/newlines before wrapping.
|
||||
// formatToken() adds EOL to paragraphs and other token types,
|
||||
// which would otherwise create extra blank lines in table cells.
|
||||
const trimmedText = text.trimEnd();
|
||||
const trimmedText = text.trimEnd()
|
||||
const wrapped = wrapAnsi(trimmedText, width, {
|
||||
hard: options?.hard ?? false,
|
||||
trim: false,
|
||||
wordWrap: true
|
||||
});
|
||||
wordWrap: true,
|
||||
})
|
||||
// Filter out empty lines that result from trailing newlines or
|
||||
// multiple consecutive newlines in the source content.
|
||||
const lines = wrapped.split('\n').filter(line => line.length > 0);
|
||||
const lines = wrapped.split('\n').filter(line => line.length > 0)
|
||||
// Ensure we always return at least one line (empty string for empty cells)
|
||||
return lines.length > 0 ? lines : [''];
|
||||
return lines.length > 0 ? lines : ['']
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,154 +75,171 @@ function wrapText(text: string, width: number, options?: {
|
||||
export function MarkdownTable({
|
||||
token,
|
||||
highlight,
|
||||
forceWidth
|
||||
forceWidth,
|
||||
}: Props): React.ReactNode {
|
||||
const [theme] = useTheme();
|
||||
const {
|
||||
columns: actualTerminalWidth
|
||||
} = useTerminalSize();
|
||||
const terminalWidth = forceWidth ?? actualTerminalWidth;
|
||||
const [theme] = useTheme()
|
||||
const { columns: actualTerminalWidth } = useTerminalSize()
|
||||
const terminalWidth = forceWidth ?? actualTerminalWidth
|
||||
|
||||
// Format cell content to ANSI string
|
||||
function formatCell(tokens: Token[] | undefined): string {
|
||||
return tokens?.map(_ => formatToken(_, theme, 0, null, null, highlight)).join('') ?? '';
|
||||
return (
|
||||
tokens
|
||||
?.map(_ => formatToken(_, theme, 0, null, null, highlight))
|
||||
.join('') ?? ''
|
||||
)
|
||||
}
|
||||
|
||||
// Get plain text (stripped of ANSI codes)
|
||||
function getPlainText(tokens_0: Token[] | undefined): string {
|
||||
return stripAnsi(formatCell(tokens_0));
|
||||
function getPlainText(tokens: Token[] | undefined): string {
|
||||
return stripAnsi(formatCell(tokens))
|
||||
}
|
||||
|
||||
// Get the longest word width in a cell (minimum width to avoid breaking words)
|
||||
function getMinWidth(tokens_1: Token[] | undefined): number {
|
||||
const text = getPlainText(tokens_1);
|
||||
const words = text.split(/\s+/).filter(w => w.length > 0);
|
||||
if (words.length === 0) return MIN_COLUMN_WIDTH;
|
||||
return Math.max(...words.map(w_0 => stringWidth(w_0)), MIN_COLUMN_WIDTH);
|
||||
function getMinWidth(tokens: Token[] | undefined): number {
|
||||
const text = getPlainText(tokens)
|
||||
const words = text.split(/\s+/).filter(w => w.length > 0)
|
||||
if (words.length === 0) return MIN_COLUMN_WIDTH
|
||||
return Math.max(...words.map(w => stringWidth(w)), MIN_COLUMN_WIDTH)
|
||||
}
|
||||
|
||||
// Get ideal width (full content without wrapping)
|
||||
function getIdealWidth(tokens_2: Token[] | undefined): number {
|
||||
return Math.max(stringWidth(getPlainText(tokens_2)), MIN_COLUMN_WIDTH);
|
||||
function getIdealWidth(tokens: Token[] | undefined): number {
|
||||
return Math.max(stringWidth(getPlainText(tokens)), MIN_COLUMN_WIDTH)
|
||||
}
|
||||
|
||||
// Calculate column widths
|
||||
// Step 1: Get minimum (longest word) and ideal (full content) widths
|
||||
const minWidths = token.header.map((header, colIndex) => {
|
||||
let maxMinWidth = getMinWidth(header.tokens);
|
||||
let maxMinWidth = getMinWidth(header.tokens)
|
||||
for (const row of token.rows) {
|
||||
maxMinWidth = Math.max(maxMinWidth, getMinWidth(row[colIndex]?.tokens));
|
||||
maxMinWidth = Math.max(maxMinWidth, getMinWidth(row[colIndex]?.tokens))
|
||||
}
|
||||
return maxMinWidth;
|
||||
});
|
||||
const idealWidths = token.header.map((header_0, colIndex_0) => {
|
||||
let maxIdeal = getIdealWidth(header_0.tokens);
|
||||
for (const row_0 of token.rows) {
|
||||
maxIdeal = Math.max(maxIdeal, getIdealWidth(row_0[colIndex_0]?.tokens));
|
||||
return maxMinWidth
|
||||
})
|
||||
|
||||
const idealWidths = token.header.map((header, colIndex) => {
|
||||
let maxIdeal = getIdealWidth(header.tokens)
|
||||
for (const row of token.rows) {
|
||||
maxIdeal = Math.max(maxIdeal, getIdealWidth(row[colIndex]?.tokens))
|
||||
}
|
||||
return maxIdeal;
|
||||
});
|
||||
return maxIdeal
|
||||
})
|
||||
|
||||
// Step 2: Calculate available space
|
||||
// Border overhead: │ content │ content │ = 1 + (width + 3) per column
|
||||
const numCols = token.header.length;
|
||||
const borderOverhead = 1 + numCols * 3; // │ + (2 padding + 1 border) per col
|
||||
const numCols = token.header.length
|
||||
const borderOverhead = 1 + numCols * 3 // │ + (2 padding + 1 border) per col
|
||||
// Account for SAFETY_MARGIN to avoid triggering the fallback safety check
|
||||
const availableWidth = Math.max(terminalWidth - borderOverhead - SAFETY_MARGIN, numCols * MIN_COLUMN_WIDTH);
|
||||
const availableWidth = Math.max(
|
||||
terminalWidth - borderOverhead - SAFETY_MARGIN,
|
||||
numCols * MIN_COLUMN_WIDTH,
|
||||
)
|
||||
|
||||
// Step 3: Calculate column widths that fit available space
|
||||
const totalMin = minWidths.reduce((sum, w_1) => sum + w_1, 0);
|
||||
const totalIdeal = idealWidths.reduce((sum_0, w_2) => sum_0 + w_2, 0);
|
||||
const totalMin = minWidths.reduce((sum, w) => sum + w, 0)
|
||||
const totalIdeal = idealWidths.reduce((sum, w) => sum + w, 0)
|
||||
|
||||
// Track whether columns are narrower than longest words (needs hard wrap)
|
||||
let needsHardWrap = false;
|
||||
let columnWidths: number[];
|
||||
let needsHardWrap = false
|
||||
|
||||
let columnWidths: number[]
|
||||
if (totalIdeal <= availableWidth) {
|
||||
// Everything fits - use ideal widths
|
||||
columnWidths = idealWidths;
|
||||
columnWidths = idealWidths
|
||||
} else if (totalMin <= availableWidth) {
|
||||
// Need to shrink - give each column its min, distribute remaining space
|
||||
const extraSpace = availableWidth - totalMin;
|
||||
const overflows = idealWidths.map((ideal, i) => ideal - minWidths[i]!);
|
||||
const totalOverflow = overflows.reduce((sum_1, o) => sum_1 + o, 0);
|
||||
columnWidths = minWidths.map((min, i_0) => {
|
||||
if (totalOverflow === 0) return min;
|
||||
const extra = Math.floor(overflows[i_0]! / totalOverflow * extraSpace);
|
||||
return min + extra;
|
||||
});
|
||||
const extraSpace = availableWidth - totalMin
|
||||
const overflows = idealWidths.map((ideal, i) => ideal - minWidths[i]!)
|
||||
const totalOverflow = overflows.reduce((sum, o) => sum + o, 0)
|
||||
|
||||
columnWidths = minWidths.map((min, i) => {
|
||||
if (totalOverflow === 0) return min
|
||||
const extra = Math.floor((overflows[i]! / totalOverflow) * extraSpace)
|
||||
return min + extra
|
||||
})
|
||||
} else {
|
||||
// Table wider than terminal at minimum widths
|
||||
// Shrink columns proportionally to fit, allowing word breaks
|
||||
needsHardWrap = true;
|
||||
const scaleFactor = availableWidth / totalMin;
|
||||
columnWidths = minWidths.map(w_3 => Math.max(Math.floor(w_3 * scaleFactor), MIN_COLUMN_WIDTH));
|
||||
needsHardWrap = true
|
||||
const scaleFactor = availableWidth / totalMin
|
||||
columnWidths = minWidths.map(w =>
|
||||
Math.max(Math.floor(w * scaleFactor), MIN_COLUMN_WIDTH),
|
||||
)
|
||||
}
|
||||
|
||||
// Step 4: Calculate max row lines to determine if vertical format is needed
|
||||
function calculateMaxRowLines(): number {
|
||||
let maxLines = 1;
|
||||
let maxLines = 1
|
||||
// Check header
|
||||
for (let i_1 = 0; i_1 < token.header.length; i_1++) {
|
||||
const content = formatCell(token.header[i_1]!.tokens);
|
||||
const wrapped = wrapText(content, columnWidths[i_1]!, {
|
||||
hard: needsHardWrap
|
||||
});
|
||||
maxLines = Math.max(maxLines, wrapped.length);
|
||||
for (let i = 0; i < token.header.length; i++) {
|
||||
const content = formatCell(token.header[i]!.tokens)
|
||||
const wrapped = wrapText(content, columnWidths[i]!, {
|
||||
hard: needsHardWrap,
|
||||
})
|
||||
maxLines = Math.max(maxLines, wrapped.length)
|
||||
}
|
||||
// Check rows
|
||||
for (const row_1 of token.rows) {
|
||||
for (let i_2 = 0; i_2 < row_1.length; i_2++) {
|
||||
const content_0 = formatCell(row_1[i_2]?.tokens);
|
||||
const wrapped_0 = wrapText(content_0, columnWidths[i_2]!, {
|
||||
hard: needsHardWrap
|
||||
});
|
||||
maxLines = Math.max(maxLines, wrapped_0.length);
|
||||
for (const row of token.rows) {
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
const content = formatCell(row[i]?.tokens)
|
||||
const wrapped = wrapText(content, columnWidths[i]!, {
|
||||
hard: needsHardWrap,
|
||||
})
|
||||
maxLines = Math.max(maxLines, wrapped.length)
|
||||
}
|
||||
}
|
||||
return maxLines;
|
||||
return maxLines
|
||||
}
|
||||
|
||||
// Use vertical format if wrapping would make rows too tall
|
||||
const maxRowLines = calculateMaxRowLines();
|
||||
const useVerticalFormat = maxRowLines > MAX_ROW_LINES;
|
||||
const maxRowLines = calculateMaxRowLines()
|
||||
const useVerticalFormat = maxRowLines > MAX_ROW_LINES
|
||||
|
||||
// Render a single row with potential multi-line cells
|
||||
// Returns an array of strings, one per line of the row
|
||||
function renderRowLines(cells: Array<{
|
||||
tokens?: Token[];
|
||||
}>, isHeader: boolean): string[] {
|
||||
function renderRowLines(
|
||||
cells: Array<{ tokens?: Token[] }>,
|
||||
isHeader: boolean,
|
||||
): string[] {
|
||||
// Get wrapped lines for each cell (preserving ANSI formatting)
|
||||
const cellLines = cells.map((cell, colIndex_1) => {
|
||||
const formattedText = formatCell(cell.tokens);
|
||||
const width = columnWidths[colIndex_1]!;
|
||||
return wrapText(formattedText, width, {
|
||||
hard: needsHardWrap
|
||||
});
|
||||
});
|
||||
const cellLines = cells.map((cell, colIndex) => {
|
||||
const formattedText = formatCell(cell.tokens)
|
||||
const width = columnWidths[colIndex]!
|
||||
return wrapText(formattedText, width, { hard: needsHardWrap })
|
||||
})
|
||||
|
||||
// Find max number of lines in this row
|
||||
const maxLines_0 = Math.max(...cellLines.map(lines => lines.length), 1);
|
||||
const maxLines = Math.max(...cellLines.map(lines => lines.length), 1)
|
||||
|
||||
// Calculate vertical offset for each cell (to center vertically)
|
||||
const verticalOffsets = cellLines.map(lines_0 => Math.floor((maxLines_0 - lines_0.length) / 2));
|
||||
const verticalOffsets = cellLines.map(lines =>
|
||||
Math.floor((maxLines - lines.length) / 2),
|
||||
)
|
||||
|
||||
// Build each line of the row as a single string
|
||||
const result: string[] = [];
|
||||
for (let lineIdx = 0; lineIdx < maxLines_0; lineIdx++) {
|
||||
let line = '│';
|
||||
for (let colIndex_2 = 0; colIndex_2 < cells.length; colIndex_2++) {
|
||||
const lines_1 = cellLines[colIndex_2]!;
|
||||
const offset = verticalOffsets[colIndex_2]!;
|
||||
const contentLineIdx = lineIdx - offset;
|
||||
const lineText = contentLineIdx >= 0 && contentLineIdx < lines_1.length ? lines_1[contentLineIdx]! : '';
|
||||
const width_0 = columnWidths[colIndex_2]!;
|
||||
const result: string[] = []
|
||||
for (let lineIdx = 0; lineIdx < maxLines; lineIdx++) {
|
||||
let line = '│'
|
||||
for (let colIndex = 0; colIndex < cells.length; colIndex++) {
|
||||
const lines = cellLines[colIndex]!
|
||||
const offset = verticalOffsets[colIndex]!
|
||||
const contentLineIdx = lineIdx - offset
|
||||
const lineText =
|
||||
contentLineIdx >= 0 && contentLineIdx < lines.length
|
||||
? lines[contentLineIdx]!
|
||||
: ''
|
||||
const width = columnWidths[colIndex]!
|
||||
// Headers always centered; data uses table alignment
|
||||
const align = isHeader ? 'center' : token.align?.[colIndex_2] ?? 'left';
|
||||
line += ' ' + padAligned(lineText, stringWidth(lineText), width_0, align) + ' │';
|
||||
const align = isHeader ? 'center' : (token.align?.[colIndex] ?? 'left')
|
||||
|
||||
line +=
|
||||
' ' + padAligned(lineText, stringWidth(lineText), width, align) + ' │'
|
||||
}
|
||||
result.push(line);
|
||||
result.push(line)
|
||||
}
|
||||
return result;
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Render horizontal border as a single string
|
||||
@@ -227,95 +247,110 @@ export function MarkdownTable({
|
||||
const [left, mid, cross, right] = {
|
||||
top: ['┌', '─', '┬', '┐'],
|
||||
middle: ['├', '─', '┼', '┤'],
|
||||
bottom: ['└', '─', '┴', '┘']
|
||||
}[type] as [string, string, string, string];
|
||||
let line_0 = left;
|
||||
columnWidths.forEach((width_1, colIndex_3) => {
|
||||
line_0 += mid.repeat(width_1 + 2);
|
||||
line_0 += colIndex_3 < columnWidths.length - 1 ? cross : right;
|
||||
});
|
||||
return line_0;
|
||||
bottom: ['└', '─', '┴', '┘'],
|
||||
}[type] as [string, string, string, string]
|
||||
|
||||
let line = left
|
||||
columnWidths.forEach((width, colIndex) => {
|
||||
line += mid.repeat(width + 2)
|
||||
line += colIndex < columnWidths.length - 1 ? cross : right
|
||||
})
|
||||
return line
|
||||
}
|
||||
|
||||
// Render vertical format (key-value pairs) for extra-narrow terminals
|
||||
function renderVerticalFormat(): string {
|
||||
const lines_2: string[] = [];
|
||||
const headers = token.header.map(h => getPlainText(h.tokens));
|
||||
const separatorWidth = Math.min(terminalWidth - 1, 40);
|
||||
const separator = '─'.repeat(separatorWidth);
|
||||
const lines: string[] = []
|
||||
const headers = token.header.map(h => getPlainText(h.tokens))
|
||||
const separatorWidth = Math.min(terminalWidth - 1, 40)
|
||||
const separator = '─'.repeat(separatorWidth)
|
||||
// Small indent for wrapped lines (just 2 spaces)
|
||||
const wrapIndent = ' ';
|
||||
token.rows.forEach((row_2, rowIndex) => {
|
||||
const wrapIndent = ' '
|
||||
|
||||
token.rows.forEach((row, rowIndex) => {
|
||||
if (rowIndex > 0) {
|
||||
lines_2.push(separator);
|
||||
lines.push(separator)
|
||||
}
|
||||
row_2.forEach((cell_0, colIndex_4) => {
|
||||
const label = headers[colIndex_4] || `Column ${colIndex_4 + 1}`;
|
||||
|
||||
row.forEach((cell, colIndex) => {
|
||||
const label = headers[colIndex] || `Column ${colIndex + 1}`
|
||||
// Clean value: trim, remove extra internal whitespace/newlines
|
||||
const rawValue = formatCell(cell_0.tokens).trimEnd();
|
||||
const value = rawValue.replace(/\n+/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
const rawValue = formatCell(cell.tokens).trimEnd()
|
||||
const value = rawValue.replace(/\n+/g, ' ').replace(/\s+/g, ' ').trim()
|
||||
|
||||
// Wrap value to fit terminal, accounting for label on first line
|
||||
const firstLineWidth = terminalWidth - stringWidth(label) - 3;
|
||||
const subsequentLineWidth = terminalWidth - wrapIndent.length - 1;
|
||||
const firstLineWidth = terminalWidth - stringWidth(label) - 3
|
||||
const subsequentLineWidth = terminalWidth - wrapIndent.length - 1
|
||||
|
||||
// Two-pass wrap: first line is narrower (label takes space),
|
||||
// continuation lines get the full width minus indent.
|
||||
const firstPassLines = wrapText(value, Math.max(firstLineWidth, 10));
|
||||
const firstLine = firstPassLines[0] || '';
|
||||
let wrappedValue: string[];
|
||||
if (firstPassLines.length <= 1 || subsequentLineWidth <= firstLineWidth) {
|
||||
wrappedValue = firstPassLines;
|
||||
const firstPassLines = wrapText(value, Math.max(firstLineWidth, 10))
|
||||
const firstLine = firstPassLines[0] || ''
|
||||
|
||||
let wrappedValue: string[]
|
||||
if (
|
||||
firstPassLines.length <= 1 ||
|
||||
subsequentLineWidth <= firstLineWidth
|
||||
) {
|
||||
wrappedValue = firstPassLines
|
||||
} else {
|
||||
// Re-join remaining text and re-wrap to the wider continuation width
|
||||
const remainingText = firstPassLines.slice(1).map(l => l.trim()).join(' ');
|
||||
const rewrapped = wrapText(remainingText, subsequentLineWidth);
|
||||
wrappedValue = [firstLine, ...rewrapped];
|
||||
const remainingText = firstPassLines
|
||||
.slice(1)
|
||||
.map(l => l.trim())
|
||||
.join(' ')
|
||||
const rewrapped = wrapText(remainingText, subsequentLineWidth)
|
||||
wrappedValue = [firstLine, ...rewrapped]
|
||||
}
|
||||
|
||||
// First line: bold label + value
|
||||
lines_2.push(`${ANSI_BOLD_START}${label}:${ANSI_BOLD_END} ${wrappedValue[0] || ''}`);
|
||||
lines.push(
|
||||
`${ANSI_BOLD_START}${label}:${ANSI_BOLD_END} ${wrappedValue[0] || ''}`,
|
||||
)
|
||||
|
||||
// Subsequent lines with small indent (skip empty lines)
|
||||
for (let i_3 = 1; i_3 < wrappedValue.length; i_3++) {
|
||||
const line_1 = wrappedValue[i_3]!;
|
||||
if (!line_1.trim()) continue;
|
||||
lines_2.push(`${wrapIndent}${line_1}`);
|
||||
for (let i = 1; i < wrappedValue.length; i++) {
|
||||
const line = wrappedValue[i]!
|
||||
if (!line.trim()) continue
|
||||
lines.push(`${wrapIndent}${line}`)
|
||||
}
|
||||
});
|
||||
});
|
||||
return lines_2.join('\n');
|
||||
})
|
||||
})
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
// Choose format based on available width
|
||||
if (useVerticalFormat) {
|
||||
return <Ansi>{renderVerticalFormat()}</Ansi>;
|
||||
return <Ansi>{renderVerticalFormat()}</Ansi>
|
||||
}
|
||||
|
||||
// Build the complete horizontal table as an array of strings
|
||||
const tableLines: string[] = [];
|
||||
tableLines.push(renderBorderLine('top'));
|
||||
tableLines.push(...renderRowLines(token.header, true));
|
||||
tableLines.push(renderBorderLine('middle'));
|
||||
token.rows.forEach((row_3, rowIndex_0) => {
|
||||
tableLines.push(...renderRowLines(row_3, false));
|
||||
if (rowIndex_0 < token.rows.length - 1) {
|
||||
tableLines.push(renderBorderLine('middle'));
|
||||
const tableLines: string[] = []
|
||||
tableLines.push(renderBorderLine('top'))
|
||||
tableLines.push(...renderRowLines(token.header, true))
|
||||
tableLines.push(renderBorderLine('middle'))
|
||||
token.rows.forEach((row, rowIndex) => {
|
||||
tableLines.push(...renderRowLines(row, false))
|
||||
if (rowIndex < token.rows.length - 1) {
|
||||
tableLines.push(renderBorderLine('middle'))
|
||||
}
|
||||
});
|
||||
tableLines.push(renderBorderLine('bottom'));
|
||||
})
|
||||
tableLines.push(renderBorderLine('bottom'))
|
||||
|
||||
// Safety check: verify no line exceeds terminal width.
|
||||
// This catches edge cases during terminal resize where calculations
|
||||
// were based on a different width than the current render target.
|
||||
const maxLineWidth = Math.max(...tableLines.map(line_2 => stringWidth(stripAnsi(line_2))));
|
||||
const maxLineWidth = Math.max(
|
||||
...tableLines.map(line => stringWidth(stripAnsi(line))),
|
||||
)
|
||||
|
||||
// If we're within SAFETY_MARGIN characters of the edge, use vertical format
|
||||
// to account for terminal resize race conditions.
|
||||
if (maxLineWidth > terminalWidth - SAFETY_MARGIN) {
|
||||
return <Ansi>{renderVerticalFormat()}</Ansi>;
|
||||
return <Ansi>{renderVerticalFormat()}</Ansi>
|
||||
}
|
||||
|
||||
// Render as a single Ansi block to prevent Ink from wrapping mid-row
|
||||
return <Ansi>{tableLines.join('\n')}</Ansi>;
|
||||
return <Ansi>{tableLines.join('\n')}</Ansi>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user