* Render a table with width-aware cell wrapping. * Cells that don't fit are wrapped to multiple lines.
( token: Tokens.Table, availableWidth: number, nextTokenType?: string, styleContext?: InlineStyleContext, )
| 683 | * Cells that don't fit are wrapped to multiple lines. |
| 684 | */ |
| 685 | private renderTable( |
| 686 | token: Tokens.Table, |
| 687 | availableWidth: number, |
| 688 | nextTokenType?: string, |
| 689 | styleContext?: InlineStyleContext, |
| 690 | ): string[] { |
| 691 | const lines: string[] = []; |
| 692 | const numCols = token.header.length; |
| 693 | |
| 694 | if (numCols === 0) { |
| 695 | return lines; |
| 696 | } |
| 697 | |
| 698 | // Calculate border overhead: "│ " + (n-1) * " │ " + " │" |
| 699 | // = 2 + (n-1) * 3 + 2 = 3n + 1 |
| 700 | const borderOverhead = 3 * numCols + 1; |
| 701 | const availableForCells = availableWidth - borderOverhead; |
| 702 | if (availableForCells < numCols) { |
| 703 | // Too narrow to render a stable table. Fall back to raw markdown. |
| 704 | const fallbackLines = token.raw ? wrapTextWithAnsi(token.raw, availableWidth) : []; |
| 705 | if (nextTokenType && nextTokenType !== "space") { |
| 706 | fallbackLines.push(""); |
| 707 | } |
| 708 | return fallbackLines; |
| 709 | } |
| 710 | |
| 711 | const maxUnbrokenWordWidth = 30; |
| 712 | |
| 713 | // Calculate natural column widths (what each column needs without constraints) |
| 714 | const naturalWidths: number[] = []; |
| 715 | const minWordWidths: number[] = []; |
| 716 | for (let i = 0; i < numCols; i++) { |
| 717 | const headerText = this.renderInlineTokens(token.header[i]!.tokens || [], styleContext); |
| 718 | naturalWidths[i] = visibleWidth(headerText); |
| 719 | minWordWidths[i] = Math.max(1, this.getLongestWordWidth(headerText, maxUnbrokenWordWidth)); |
| 720 | } |
| 721 | for (const row of token.rows) { |
| 722 | for (let i = 0; i < row.length; i++) { |
| 723 | const cellText = this.renderInlineTokens(row[i]!.tokens || [], styleContext); |
| 724 | naturalWidths[i] = Math.max(naturalWidths[i] || 0, visibleWidth(cellText)); |
| 725 | minWordWidths[i] = Math.max( |
| 726 | minWordWidths[i] || 1, |
| 727 | this.getLongestWordWidth(cellText, maxUnbrokenWordWidth), |
| 728 | ); |
| 729 | } |
| 730 | } |
| 731 | |
| 732 | let minColumnWidths = minWordWidths; |
| 733 | let minCellsWidth = minColumnWidths.reduce((a, b) => a + b, 0); |
| 734 | |
| 735 | if (minCellsWidth > availableForCells) { |
| 736 | minColumnWidths = new Array(numCols).fill(1); |
| 737 | const remaining = availableForCells - numCols; |
| 738 | |
| 739 | if (remaining > 0) { |
| 740 | const totalWeight = minWordWidths.reduce((total, width) => total + Math.max(0, width - 1), 0); |
| 741 | const growth = minWordWidths.map((width) => { |
| 742 | const weight = Math.max(0, width - 1); |
no test coverage detected