Skip to content

feat(output): optimize table formatting with width capping and key/value layout#1081

Open
Benjamin-eecs wants to merge 1 commit intojackwener:mainfrom
Benjamin-eecs:feat/table-output-formatting
Open

feat(output): optimize table formatting with width capping and key/value layout#1081
Benjamin-eecs wants to merge 1 commit intojackwener:mainfrom
Benjamin-eecs:feat/table-output-formatting

Conversation

@Benjamin-eecs
Copy link
Copy Markdown
Contributor

Summary

Closes #1017.

Improves the default table output to fit within the terminal width and be easier to scan.

Before: long text columns (titles, URLs) push the table way past terminal width, making output unreadable.

After:

  • Columns are capped to fit the terminal width, long text truncated with ...
  • Numeric columns (play counts, scores) are right-aligned
  • Single-row results (e.g. bilibili me) render as vertical key/value pairs instead of a wide single-row table
  • CJK characters correctly measured as double-width

Full content is always available via -f json or -f yaml.

Changes

All changes are in src/output.ts:

  • Added isWideCodePoint(), displayWidth(), truncateToWidth() for CJK-aware width handling
  • renderTable() now measures columns, caps widths to terminal, and truncates long text
  • Added renderKeyValue() for single-row results
  • Extracted printFooter() to share between table and key/value renderers

Test plan

  • npx tsc --noEmit passes
  • All existing tests pass
  • opencli bilibili search "AI" --limit 5 -f table - long titles truncated, scores right-aligned
  • opencli bilibili me -f table - renders as key/value layout
  • opencli bilibili hot --limit 5 -f json - full content preserved

Copilot AI review requested due to automatic review settings April 18, 2026 21:28
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves -f table output readability by introducing terminal-width-aware table rendering with truncation, numeric right-alignment, and a key/value layout for single-row results (with CJK-aware width measurement).

Changes:

  • Added CJK-aware display width + truncation helpers and applied them to table cell rendering.
  • Updated renderTable() to infer numeric columns, cap column widths to terminal width, and truncate overflow.
  • Added renderKeyValue() for single-row results and extracted printFooter() for shared footer rendering.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/output.ts Outdated
Comment on lines +144 to +163
const availableWidth = Math.max(termWidth - borderOverhead, colCount * 5);

const colWidths = new Array<number>(colCount);
let shortTotal = 0;
const longIndices: number[] = [];

for (let i = 0; i < colCount; i++) {
if (colContentWidths[i] <= SHORT_COL_THRESHOLD) {
colWidths[i] = colContentWidths[i];
shortTotal += colContentWidths[i];
} else {
longIndices.push(i);
}
}

const remainingWidth = availableWidth - shortTotal;
if (longIndices.length > 0 && remainingWidth > 0) {
const perLong = Math.max(Math.floor(remainingWidth / longIndices.length), 10);
for (const i of longIndices) {
colWidths[i] = Math.min(colContentWidths[i], perLong);
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

availableWidth is computed with Math.max(termWidth - borderOverhead, colCount * 5), which can exceed the actual terminal width when colCount is large or termWidth - borderOverhead is small/negative. In those cases the subsequent width-capping logic cannot keep the rendered table within the terminal, contradicting the intended behavior. Consider clamping availableWidth to Math.max(0, termWidth - borderOverhead) (and then handling narrow terminals by shrinking columns below 5 or switching to a different layout).

Suggested change
const availableWidth = Math.max(termWidth - borderOverhead, colCount * 5);
const colWidths = new Array<number>(colCount);
let shortTotal = 0;
const longIndices: number[] = [];
for (let i = 0; i < colCount; i++) {
if (colContentWidths[i] <= SHORT_COL_THRESHOLD) {
colWidths[i] = colContentWidths[i];
shortTotal += colContentWidths[i];
} else {
longIndices.push(i);
}
}
const remainingWidth = availableWidth - shortTotal;
if (longIndices.length > 0 && remainingWidth > 0) {
const perLong = Math.max(Math.floor(remainingWidth / longIndices.length), 10);
for (const i of longIndices) {
colWidths[i] = Math.min(colContentWidths[i], perLong);
const availableWidth = Math.max(0, termWidth - borderOverhead);
const colWidths = new Array<number>(colCount).fill(0);
let shortTotal = 0;
const longIndices: number[] = [];
for (let i = 0; i < colCount; i++) {
if (colContentWidths[i] <= SHORT_COL_THRESHOLD) {
colWidths[i] = Math.min(colContentWidths[i], availableWidth - shortTotal);
shortTotal += colWidths[i];
} else {
longIndices.push(i);
}
}
let remainingWidth = Math.max(0, availableWidth - shortTotal);
if (longIndices.length > 0 && remainingWidth > 0) {
for (let li = 0; li < longIndices.length; li++) {
const i = longIndices[li];
const colsLeft = longIndices.length - li;
const perLong = Math.floor(remainingWidth / colsLeft);
colWidths[i] = Math.min(colContentWidths[i], perLong);
remainingWidth -= colWidths[i];

Copilot uses AI. Check for mistakes.
Comment thread src/output.ts
Comment on lines +159 to +165
const remainingWidth = availableWidth - shortTotal;
if (longIndices.length > 0 && remainingWidth > 0) {
const perLong = Math.max(Math.floor(remainingWidth / longIndices.length), 10);
for (const i of longIndices) {
colWidths[i] = Math.min(colContentWidths[i], perLong);
}
}
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

colWidths is only assigned for the “long” columns when remainingWidth > 0. If remainingWidth <= 0, the entries for longIndices remain undefined, and later cellWidths[ri][ci] > colWidths[ci] will never be true (because undefined becomes NaN in the comparison), so no truncation happens for those columns. Ensure every column gets an explicit width (even under tight width budgets), and then truncate based on that.

Copilot uses AI. Check for mistakes.
Comment thread src/output.ts
Comment on lines +150 to +157
for (let i = 0; i < colCount; i++) {
if (colContentWidths[i] <= SHORT_COL_THRESHOLD) {
colWidths[i] = colContentWidths[i];
shortTotal += colContentWidths[i];
} else {
longIndices.push(i);
}
}
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The width allocation logic only caps columns whose measured width is above SHORT_COL_THRESHOLD; “short” columns always keep their full content width. With many columns (or a narrow terminal), the sum of short columns alone can exceed the available width, so the table will still overflow even when there are no “long” columns to shrink. Consider adding a fallback pass that enforces a global width budget (e.g., iteratively shrink the widest columns above a minimum width until the sum fits).

Copilot uses AI. Check for mistakes.
Comment thread src/output.ts
Comment on lines +101 to +104
if (rows.length === 1) {
renderKeyValue(rows[0], columns, opts);
return;
}
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR introduces several new output behaviors (single-row key/value rendering, numeric alignment inference, terminal-width capping + truncation) but src/output.test.ts currently only asserts TTY auto-downgrade behavior. Adding focused tests for these new formatting rules would help prevent regressions (e.g., a snapshot-style assertion for truncation/ellipsis and for the key/value layout).

Copilot uses AI. Check for mistakes.
@Benjamin-eecs Benjamin-eecs force-pushed the feat/table-output-formatting branch from c473135 to 26ae6ec Compare April 18, 2026 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: 优化命令行输出的表格排版

2 participants