From ab71e39a9d7f64ce7e3dd8e138c7c99abf4dd9cd Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Thu, 3 Oct 2024 10:12:14 -0500 Subject: [PATCH 01/16] fix: links --- CONRTIBUTING.md | 8 ++++---- package.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CONRTIBUTING.md b/CONRTIBUTING.md index cce18a7..8927b14 100644 --- a/CONRTIBUTING.md +++ b/CONRTIBUTING.md @@ -2,20 +2,20 @@ ## Reporting Bugs -When submitting a new bug report, please first [search](https://github.com/oclif/multi-stage-output/issues) for an existing or similar report & then use one of our existing [issue templates](https://github.com/oclif/multi-stage-output/issues/new/choose) if you believe you've come across a unique problem. Duplicate issues, or issues that don't use one of our templates may get closed without a response. +When submitting a new bug report, please first [search](https://github.com/oclif/table/issues) for an existing or similar report & then use one of our existing [issue templates](https://github.com/oclif/table/issues/new/choose) if you believe you've come across a unique problem. Duplicate issues, or issues that don't use one of our templates may get closed without a response. ## Development **1. Clone this repository...** ```bash -$ git clone git@github.com:oclif/multi-stage-output.git +$ git clone git@github.com:oclif/table.git ``` **2. Navigate into project & install development-specific dependencies...** ```bash -$ cd ./multi-stage-output && yarn +$ cd ./table && yarn ``` **3. Write some code &/or add some tests...** @@ -30,7 +30,7 @@ $ cd ./multi-stage-output && yarn $ yarn test ``` -**5. Open a [Pull Request](https://github.com/oclif/multi-stage-output/pulls) for your work & become the newest contributor to `@oclif/multi-stage-output`! 🎉** +**5. Open a [Pull Request](https://github.com/oclif/table/pulls) for your work & become the newest contributor to `@oclif/table`! 🎉** ## Pull Request Conventions diff --git a/package.json b/package.json index 84186cf..541197a 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,9 @@ "description": "Display table in terminal", "version": "0.1.10", "author": "Salesforce", - "bugs": "https://github.com/oclif/multi-stage-output/issues", + "bugs": "https://github.com/oclif/table/issues", "dependencies": { "@oclif/core": "^4", - "@types/react": "^18.3.10", "change-case": "^5.4.4", "cli-truncate": "^4.0.0", "ink": "^5.0.1", @@ -23,6 +22,7 @@ "@types/mocha": "^10.0.8", "@types/node": "^18", "@types/object-hash": "^3.0.6", + "@types/react": "^18.3.10", "@types/sinon": "^17.0.3", "ansis": "^3.3.2", "chai": "^4.5.0", @@ -52,7 +52,7 @@ "files": [ "/lib" ], - "homepage": "https://github.com/oclif/core", + "homepage": "https://github.com/oclif/table", "keywords": [ "oclif", "cli", @@ -62,7 +62,7 @@ "exports": { ".": "./lib/index.js" }, - "repository": "oclif/core", + "repository": "oclif/table", "publishConfig": { "access": "public" }, From 8c3923203a0cee99db7c1b23aaa878bd8c04b79b Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Thu, 3 Oct 2024 10:52:24 -0500 Subject: [PATCH 02/16] chore: width and wrap notes --- examples/multiple.ts | 3 +++ examples/orientation.ts | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/examples/multiple.ts b/examples/multiple.ts index df68c47..eddf46a 100644 --- a/examples/multiple.ts +++ b/examples/multiple.ts @@ -63,6 +63,9 @@ const projects = [ }, ] +// Occasionally, if you have two maxWidths that add up to 100% they will stack vertically instead of horizontally. +// At first I thought this might be when the window has an odd number of pixels, but it seems to be more random than that. + const employeesTable: TableOptions<(typeof employees)[number]> = { columns: ['id', 'name', 'age', 'description'], data: employees, diff --git a/examples/orientation.ts b/examples/orientation.ts index 4926435..45b0840 100644 --- a/examples/orientation.ts +++ b/examples/orientation.ts @@ -33,6 +33,27 @@ printTable({ titleOptions: {bold: true}, }) +// When the vertical orientation table wraps, the "value" does not stay in its column +// Example (note the "description" column): +// +// ────────────────────────────────────────────────────────────────────────────────────── +// Item Item 1 +// Description +// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor +// incididunt ut labore et dolore magna aliqua. +// Url https://www.example.com/item/1 +// +// I would expect it to look like this: +// +// Example (note the "description" column): +// ------------------------------------------------ +// Item Item 1 +// Description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do +// eiusmod tempor incididunt ut labore et dolore magna aliqua. +// Url https://www.example.com/item/1 + + + printTable({ columns: ['item', 'description', 'url'], data, From 3578e3f4636a6696d5eec60de5b5eaff4d9b1145 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Thu, 3 Oct 2024 11:54:59 -0500 Subject: [PATCH 03/16] chore: wrapped alignment --- examples/overflow.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/examples/overflow.ts b/examples/overflow.ts index e5253e5..cb6a4f5 100644 --- a/examples/overflow.ts +++ b/examples/overflow.ts @@ -60,6 +60,22 @@ printTable({ titleOptions: {bold: true}, }) +// I would expect this for wrapping on align-center: +// +// Wrap (aligned center) +// ┌───────┬─────────┬─────┬───────────────────────────────────────────────────────────────────────────┐ +// │ Id │ Name │ Age │ Description │ +// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ +// │ 36329 │ Alice │ 20 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ +// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ +// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ +// │ 49032 │ Bob │ 21 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ +// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ +// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ +// │ 51786 │ Charlie │ 22 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ +// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ +// └───────┴─────────┴─────┴───────────────────────────────────────────────────────────────────────────┘ + printTable({ columns: ['id', 'name', 'age', 'description'], data, @@ -84,6 +100,22 @@ printTable({ titleOptions: {bold: true}, }) +// Similar for align-right: +// +// Wrap (aligned right) +// ┌───────┬─────────┬─────┬───────────────────────────────────────────────────────────────────────────┐ +// │ Id │ Name │ Age │ Description │ +// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ +// │ 36329 │ Alice │ 20 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ +// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ +// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ +// │ 49032 │ Bob │ 21 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ +// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ +// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ +// │ 51786 │ Charlie │ 22 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ +// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ +// └───────┴─────────┴─────┴───────────────────────────────────────────────────────────────────────────┘ + printTable({ columns: ['id', 'name', 'age', 'description'], data, From 4ec1bfaa4c97c4f50f7361267b1c4d8f1af60fd7 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Thu, 3 Oct 2024 12:09:26 -0500 Subject: [PATCH 04/16] chore: another example, value tweaks to test correct ordering --- examples/sort-and-filter.ts | 4 ++-- examples/styles.ts | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/sort-and-filter.ts b/examples/sort-and-filter.ts index e95fa9d..a5e95c2 100644 --- a/examples/sort-and-filter.ts +++ b/examples/sort-and-filter.ts @@ -1,8 +1,8 @@ import {printTable} from '../src/index.js' const data = [ - {age: 25, id: '10245', name: 'Bob'}, - {age: 26, id: '10345', name: 'Bill'}, + {age: 100, id: '10245', name: 'Bob'}, + {age: 10, id: '10345', name: 'Bill'}, {age: 30, id: '20245', name: 'Alice'}, {age: 20, id: '20345', name: 'Amy'}, {age: 30, id: '30245', name: 'Charlie'}, diff --git a/examples/styles.ts b/examples/styles.ts index 2ca60e5..916c22d 100644 --- a/examples/styles.ts +++ b/examples/styles.ts @@ -33,3 +33,16 @@ for (const borderStyle of BORDER_STYLES) { }) console.log() } + +printTable({ + borderStyle: 'all', + columns: ['id', {key: 'name', name: 'First Name'}, 'age'], + data, + headerOptions: { + formatter: 'capitalCase', + }, + horizontalAlignment: 'center', + title: 'Remove style with "noStyle: true"', + titleOptions: {bold: true}, + noStyle: true, +}) From 77f9c1a7e757ecd12cc8fdc1f1bbe19cc39f159c Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Thu, 3 Oct 2024 12:21:44 -0500 Subject: [PATCH 05/16] chore: paddingBefore paddingAfter question --- src/table.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/table.tsx b/src/table.tsx index b7f31fc..3d1884c 100644 --- a/src/table.tsx +++ b/src/table.tsx @@ -407,6 +407,7 @@ export function Skeleton(props: React.PropsWithChildren & {readonly height?: num export function printTable>(options: TableOptions): void { const instance = render() instance.unmount() + // It might be nice to have a "paddingBefore" and "paddingAfter" option for the number of newlines to enter. process.stdout.write('\n') } From 7a4ee69da381f1f30249c56e042da3ffd052a30b Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Thu, 3 Oct 2024 12:39:42 -0500 Subject: [PATCH 06/16] chore: new skeleton --- src/skeletons.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/skeletons.ts b/src/skeletons.ts index 320c607..6a65053 100644 --- a/src/skeletons.ts +++ b/src/skeletons.ts @@ -8,6 +8,7 @@ export const BORDER_STYLES = [ 'none', 'outline', 'vertical-with-outline', + 'vertical-rows-with-outline', 'vertical', ] as const @@ -337,6 +338,44 @@ export const BORDER_SKELETONS: Record< right: '', }, }, + 'vertical-rows-with-outline': { + data: { + cross: '│', + left: '│', + line: ' ', + right: '│', + }, + footer: { + cross: '┴', + left: '└', + line: '─', + right: '┘', + }, + header: { + cross: '─', + left: '┌', + line: '─', + right: '┐', + }, + headerFooter: { + cross: '┬', + left: '├', + line: '─', + right: '┤', + }, + heading: { + cross: ' ', + left: '│', + line: ' ', + right: '│', + }, + separator: { + cross: '', + left: '', + line: '', + right: '', + }, + }, 'vertical-with-outline': { data: { cross: '│', From 0b0069d534ce295059232e53d625264865021cf6 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Thu, 3 Oct 2024 14:03:53 -0500 Subject: [PATCH 07/16] chore: test fix --- examples/styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/styles.ts b/examples/styles.ts index 916c22d..7f22cc1 100644 --- a/examples/styles.ts +++ b/examples/styles.ts @@ -42,7 +42,7 @@ printTable({ formatter: 'capitalCase', }, horizontalAlignment: 'center', + noStyle: true, title: 'Remove style with "noStyle: true"', titleOptions: {bold: true}, - noStyle: true, }) From a2b816c9ff9edef4c2bb19abd6b799ae6a702acc Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 3 Oct 2024 10:34:20 -0600 Subject: [PATCH 08/16] fix: account for columnGap when sizing multiple tables --- src/table.tsx | 4 ++-- src/types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/table.tsx b/src/table.tsx index 3d1884c..73a5635 100644 --- a/src/table.tsx +++ b/src/table.tsx @@ -429,8 +429,8 @@ export function printTables[]>( const processed = tables.map((table) => ({ ...table, - // adjust maxWidth to account for margin - maxWidth: determineConfiguredWidth(table.maxWidth, columns), + // adjust maxWidth to account for margin and columnGap + maxWidth: determineConfiguredWidth(table.maxWidth, columns) - (options?.columnGap ?? 0) * tables.length, })) const instance = render( diff --git a/src/types.ts b/src/types.ts index 0d89dfb..d0d6261 100644 --- a/src/types.ts +++ b/src/types.ts @@ -113,7 +113,7 @@ export type TableOptions> = { * * If you provide a number or percentage that is larger than the terminal width, it will default to the terminal width. * - * If you provide a number or percentage that is too small to fit the table, it will default to the width of the table. + * If you provide a number or percentage that is too small to fit the table, it will default to the minimum width of the table. */ maxWidth?: Percentage | number /** From 36415d9acd9a9270fe67faefb2f60c841d6ae9ce Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 3 Oct 2024 10:35:42 -0600 Subject: [PATCH 09/16] fix: remove orientation prop --- examples/orientation.ts | 68 ----------------------------------------- src/table.tsx | 43 -------------------------- src/types.ts | 25 ++------------- 3 files changed, 3 insertions(+), 133 deletions(-) delete mode 100644 examples/orientation.ts diff --git a/examples/orientation.ts b/examples/orientation.ts deleted file mode 100644 index 45b0840..0000000 --- a/examples/orientation.ts +++ /dev/null @@ -1,68 +0,0 @@ -import {printTable} from '../src/index.js' - -const description = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' - -const data = [ - { - description, - item: 'Item 1', - url: 'https://www.example.com/item/1', - }, - { - description, - item: 'Item 2', - url: 'https://www.example.com/item/2', - }, - { - description, - item: 'Item 3', - url: 'https://www.example.com/item/3', - }, -] - -printTable({ - columns: ['item', 'description', 'url'], - data, - headerOptions: { - formatter: 'capitalCase', - }, - horizontalAlignment: 'center', - overflow: 'wrap', - title: 'Horizontal Orientation', - titleOptions: {bold: true}, -}) - -// When the vertical orientation table wraps, the "value" does not stay in its column -// Example (note the "description" column): -// -// ────────────────────────────────────────────────────────────────────────────────────── -// Item Item 1 -// Description -// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor -// incididunt ut labore et dolore magna aliqua. -// Url https://www.example.com/item/1 -// -// I would expect it to look like this: -// -// Example (note the "description" column): -// ------------------------------------------------ -// Item Item 1 -// Description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do -// eiusmod tempor incididunt ut labore et dolore magna aliqua. -// Url https://www.example.com/item/1 - - - -printTable({ - columns: ['item', 'description', 'url'], - data, - headerOptions: { - formatter: 'capitalCase', - }, - horizontalAlignment: 'center', - orientation: 'vertical', - overflow: 'wrap', - title: 'Vertical Orientation', - titleOptions: {bold: true}, -}) diff --git a/src/table.tsx b/src/table.tsx index 73a5635..d1948b7 100644 --- a/src/table.tsx +++ b/src/table.tsx @@ -163,7 +163,6 @@ export function Table>(props: TableOptions) horizontalAlignment = 'left', maxWidth, noStyle = false, - orientation = 'horizontal', overflow = 'truncate', padding = 1, sort, @@ -237,48 +236,6 @@ export function Table>(props: TableOptions) skeleton: BORDER_SKELETONS[config.borderStyle].separator, }) - if (orientation === 'vertical') { - return ( - - {title && {title}} - {processedData.map((row, index) => { - // Calculate the hash of the row based on its value and position - const key = `row-${sha1(row)}-${index}` - const maxKeyLength = Math.max(...Object.values(headings).map((c) => c.length)) - // Construct a row. - return ( - - {/* print all data in key:value pairs */} - {columns.map((column) => { - const value = (row[column.column] ?? '').toString() - const keyName = (headings[column.key] ?? column.key).toString() - const keyPadding = ' '.repeat(maxKeyLength - keyName.length + padding) - return ( - - - {keyName} - {keyPadding} - - {value} - - ) - })} - - ) - })} - - ) - } - return ( {title && {title}} diff --git a/src/types.ts b/src/types.ts index d0d6261..c5624de 100644 --- a/src/types.ts +++ b/src/types.ts @@ -125,7 +125,7 @@ export type TableOptions> = { */ headerOptions?: HeaderOptions /** - * Border style for the table. Defaults to 'all'. Only applies to horizontal orientation. + * Border style for the table. Defaults to 'all'. */ borderStyle?: BorderStyle /** @@ -133,7 +133,7 @@ export type TableOptions> = { */ borderColor?: SupportedColor /** - * Align data in columns. Defaults to 'left'. Only applies to horizontal orientation. + * Align data in columns. Defaults to 'left'. */ horizontalAlignment?: HorizontalAlignment /** @@ -170,26 +170,7 @@ export type TableOptions> = { */ sort?: Sort /** - * The orientation of the table. Defaults to 'horizontal'. - * - * If 'vertical', individual records will be displayed vertically in key:value pairs. - * - * @example - * ``` - * ───────────── - * Name Alice - * Id 36329 - * Age 20 - * ───────────── - * Name Bob - * Id 49032 - * Age 21 - * ───────────── - * ``` - */ - orientation?: 'horizontal' | 'vertical' - /** - * Vertical alignment of cell content. Defaults to 'top'. Only applies to horizontal orientation. + * Vertical alignment of cell content. Defaults to 'top'. */ verticalAlignment?: VerticalAlignment /** From bbe9e90918528c0194580e6b8d9adfbcadaf258a Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 3 Oct 2024 13:08:06 -0600 Subject: [PATCH 10/16] fix: wrapping and alignment --- examples/overflow.ts | 32 -------------------------------- src/table.tsx | 23 ++++++++++++++++++++--- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/examples/overflow.ts b/examples/overflow.ts index cb6a4f5..e5253e5 100644 --- a/examples/overflow.ts +++ b/examples/overflow.ts @@ -60,22 +60,6 @@ printTable({ titleOptions: {bold: true}, }) -// I would expect this for wrapping on align-center: -// -// Wrap (aligned center) -// ┌───────┬─────────┬─────┬───────────────────────────────────────────────────────────────────────────┐ -// │ Id │ Name │ Age │ Description │ -// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ -// │ 36329 │ Alice │ 20 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ -// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ -// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ -// │ 49032 │ Bob │ 21 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ -// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ -// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ -// │ 51786 │ Charlie │ 22 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ -// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ -// └───────┴─────────┴─────┴───────────────────────────────────────────────────────────────────────────┘ - printTable({ columns: ['id', 'name', 'age', 'description'], data, @@ -100,22 +84,6 @@ printTable({ titleOptions: {bold: true}, }) -// Similar for align-right: -// -// Wrap (aligned right) -// ┌───────┬─────────┬─────┬───────────────────────────────────────────────────────────────────────────┐ -// │ Id │ Name │ Age │ Description │ -// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ -// │ 36329 │ Alice │ 20 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ -// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ -// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ -// │ 49032 │ Bob │ 21 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ -// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ -// ├───────┼─────────┼─────┼───────────────────────────────────────────────────────────────────────────┤ -// │ 51786 │ Charlie │ 22 │ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod │ -// │ │ │ │ tempor incididunt ut labore et dolore magna aliqua. │ -// └───────┴─────────┴─────┴───────────────────────────────────────────────────────────────────────────┘ - printTable({ columns: ['id', 'name', 'age', 'description'], data, diff --git a/src/table.tsx b/src/table.tsx index d1948b7..96a63e2 100644 --- a/src/table.tsx +++ b/src/table.tsx @@ -128,7 +128,7 @@ function formatTextWithMargins({ const valueWithNoZeroWidthChars = String(value).replaceAll('​', ' ') const spaceForText = width - padding * 2 - if (stripAnsi(valueWithNoZeroWidthChars).length < spaceForText) { + if (stripAnsi(valueWithNoZeroWidthChars).length <= spaceForText) { const spaces = width - stripAnsi(valueWithNoZeroWidthChars).length return { text: valueWithNoZeroWidthChars, @@ -139,12 +139,28 @@ function formatTextWithMargins({ if (overflow === 'wrap') { const wrappedText = wrapAnsi(valueWithNoZeroWidthChars, spaceForText, {hard: true, trim: true, wordWrap: true}) const {marginLeft, marginRight} = calculateMargins(width - determineWidthOfWrappedText(stripAnsi(wrappedText))) - const text = wrappedText.replaceAll('\n', `${' '.repeat(marginRight)}\n${' '.repeat(marginLeft)}`) + + const lines = wrappedText.split('\n').map((line, idx) => { + const lineMargins = calculateMargins(spaceForText - stripAnsi(line).length) + if (idx === 0) { + return `${line}${' '.repeat(lineMargins.marginRight)}` + } + + if (horizontalAlignment === 'left') { + return `${' '.repeat(marginLeft)}${line}${' '.repeat(lineMargins.marginRight)}` + } + + if (horizontalAlignment === 'right') { + return `${' '.repeat(lineMargins.marginLeft + marginLeft)}${line}${' '.repeat(marginRight)}` + } + + return `${' '.repeat(lineMargins.marginLeft + marginLeft)}${line}${' '.repeat(lineMargins.marginRight)}` + }) return { marginLeft, marginRight, - text, + text: lines.join('\n'), } } @@ -289,6 +305,7 @@ function row>(config: RowConfig): (props: RowP value, width, }) + // console.log(text, {marginLeft, marginRight}, text.match(/ +$/)?.[0]?.length) const alignItems = verticalAlignment === 'top' ? 'flex-start' : verticalAlignment === 'center' ? 'center' : 'flex-end' From f5f15127626f8c204600023cb88434135da175cc Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 3 Oct 2024 13:33:03 -0600 Subject: [PATCH 11/16] refactor: better alignment --- src/table.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/table.tsx b/src/table.tsx index 96a63e2..7d0742a 100644 --- a/src/table.tsx +++ b/src/table.tsx @@ -141,20 +141,24 @@ function formatTextWithMargins({ const {marginLeft, marginRight} = calculateMargins(width - determineWidthOfWrappedText(stripAnsi(wrappedText))) const lines = wrappedText.split('\n').map((line, idx) => { - const lineMargins = calculateMargins(spaceForText - stripAnsi(line).length) + const {marginLeft: lineSpecificLeftMargin} = calculateMargins(width - stripAnsi(line).length) if (idx === 0) { - return `${line}${' '.repeat(lineMargins.marginRight)}` + // if it's the first line, only add margin to the right side (The left margin will be applied later) + return `${line}${' '.repeat(marginRight)}` } if (horizontalAlignment === 'left') { - return `${' '.repeat(marginLeft)}${line}${' '.repeat(lineMargins.marginRight)}` + // if left alignment, add the overall margin to the left side only + return `${' '.repeat(marginLeft)}${line}` } if (horizontalAlignment === 'right') { - return `${' '.repeat(lineMargins.marginLeft + marginLeft)}${line}${' '.repeat(marginRight)}` + // if right alignment, add line specific margin to the left side only + return `${' '.repeat(lineSpecificLeftMargin)}${line}` } - return `${' '.repeat(lineMargins.marginLeft + marginLeft)}${line}${' '.repeat(lineMargins.marginRight)}` + // if center alignment, add line specific margin to the left side + return `${' '.repeat(lineSpecificLeftMargin)}${line}` }) return { @@ -381,7 +385,6 @@ export function Skeleton(props: React.PropsWithChildren & {readonly height?: num export function printTable>(options: TableOptions): void { const instance = render(
) instance.unmount() - // It might be nice to have a "paddingBefore" and "paddingAfter" option for the number of newlines to enter. process.stdout.write('\n') } From 9397e8ee723dbe4d505a410e58e4d891510edc7f Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 3 Oct 2024 14:03:20 -0600 Subject: [PATCH 12/16] chore: remove console.log --- src/table.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/table.tsx b/src/table.tsx index 7d0742a..c699bcb 100644 --- a/src/table.tsx +++ b/src/table.tsx @@ -309,7 +309,6 @@ function row>(config: RowConfig): (props: RowP value, width, }) - // console.log(text, {marginLeft, marginRight}, text.match(/ +$/)?.[0]?.length) const alignItems = verticalAlignment === 'top' ? 'flex-start' : verticalAlignment === 'center' ? 'center' : 'flex-end' From 8637b07533cb3e491ffca5935d64b53ee149bc47 Mon Sep 17 00:00:00 2001 From: Eric Willhoit Date: Thu, 3 Oct 2024 15:35:34 -0500 Subject: [PATCH 13/16] chore: showcase verticalAlignment --- examples/overflow.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/overflow.ts b/examples/overflow.ts index e5253e5..7aa33b2 100644 --- a/examples/overflow.ts +++ b/examples/overflow.ts @@ -82,6 +82,7 @@ printTable({ overflow: 'wrap', title: 'Wrap (aligned left)', titleOptions: {bold: true}, + verticalAlignment: 'center', }) printTable({ @@ -94,4 +95,5 @@ printTable({ overflow: 'wrap', title: 'Wrap (aligned right)', titleOptions: {bold: true}, + verticalAlignment: 'bottom', }) From ab3f94b12f6385a4e31b00c43d5303f753da3413 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 3 Oct 2024 15:06:06 -0600 Subject: [PATCH 14/16] fix: right alignment --- src/table.tsx | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/table.tsx b/src/table.tsx index c699bcb..642aa1a 100644 --- a/src/table.tsx +++ b/src/table.tsx @@ -138,27 +138,42 @@ function formatTextWithMargins({ if (overflow === 'wrap') { const wrappedText = wrapAnsi(valueWithNoZeroWidthChars, spaceForText, {hard: true, trim: true, wordWrap: true}) - const {marginLeft, marginRight} = calculateMargins(width - determineWidthOfWrappedText(stripAnsi(wrappedText))) + const spaces = width - determineWidthOfWrappedText(stripAnsi(wrappedText)) + const {marginLeft, marginRight} = calculateMargins(spaces) const lines = wrappedText.split('\n').map((line, idx) => { const {marginLeft: lineSpecificLeftMargin} = calculateMargins(width - stripAnsi(line).length) - if (idx === 0) { - // if it's the first line, only add margin to the right side (The left margin will be applied later) - return `${line}${' '.repeat(marginRight)}` - } + // if (idx === 0) { + // // if it's the first line, only add margin to the right side (The left margin will be applied later) + // return `${line}${' '.repeat(marginRight)}` + // } if (horizontalAlignment === 'left') { - // if left alignment, add the overall margin to the left side only - return `${' '.repeat(marginLeft)}${line}` + if (idx === 0) { + // if it's the first line, only add margin to the right side (The left margin will be applied later) + return `${line}${' '.repeat(marginRight)}` + } + + // if left alignment, add the overall margin to the left side and right sides + return `${' '.repeat(marginLeft)}${line}${' '.repeat(marginRight)}` } - if (horizontalAlignment === 'right') { - // if right alignment, add line specific margin to the left side only - return `${' '.repeat(lineSpecificLeftMargin)}${line}` + if (horizontalAlignment === 'center') { + if (idx === 0) { + // if it's the first line, only add margin to the right side (The left margin will be applied later) + return `${line}${' '.repeat(marginRight)}` + } + + // if center alignment, add line specific margin to the left side and the overall margin to the right side + return `${' '.repeat(lineSpecificLeftMargin)}${line}${' '.repeat(marginRight)}` + } + + // right alignment + if (idx === 0) { + return `${' '.repeat(Math.max(0, lineSpecificLeftMargin - marginLeft))}${line}${' '.repeat(marginRight)}` } - // if center alignment, add line specific margin to the left side - return `${' '.repeat(lineSpecificLeftMargin)}${line}` + return `${' '.repeat(lineSpecificLeftMargin)}${line}${' '.repeat(marginRight)}` }) return { @@ -310,6 +325,10 @@ function row>(config: RowConfig): (props: RowP width, }) + // console.log( + // {marginLeft, marginRight, text}, + // JSON.stringify(`${skeleton.line.repeat(marginLeft)}${text}${skeleton.line.repeat(marginRight)}`), + // ) const alignItems = verticalAlignment === 'top' ? 'flex-start' : verticalAlignment === 'center' ? 'center' : 'flex-end' return ( From 1332fe36f10e224a854a89e5a9c2f0d62aedcab9 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 3 Oct 2024 15:08:06 -0600 Subject: [PATCH 15/16] chore: remove console.log --- src/table.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/table.tsx b/src/table.tsx index 642aa1a..6a0719a 100644 --- a/src/table.tsx +++ b/src/table.tsx @@ -325,10 +325,6 @@ function row>(config: RowConfig): (props: RowP width, }) - // console.log( - // {marginLeft, marginRight, text}, - // JSON.stringify(`${skeleton.line.repeat(marginLeft)}${text}${skeleton.line.repeat(marginRight)}`), - // ) const alignItems = verticalAlignment === 'top' ? 'flex-start' : verticalAlignment === 'center' ? 'center' : 'flex-end' return ( From eae80413e05288b4e88ea025c96f8811b6107cf2 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Thu, 3 Oct 2024 15:11:44 -0600 Subject: [PATCH 16/16] chore: clean up --- src/table.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/table.tsx b/src/table.tsx index 6a0719a..2988c93 100644 --- a/src/table.tsx +++ b/src/table.tsx @@ -138,15 +138,10 @@ function formatTextWithMargins({ if (overflow === 'wrap') { const wrappedText = wrapAnsi(valueWithNoZeroWidthChars, spaceForText, {hard: true, trim: true, wordWrap: true}) - const spaces = width - determineWidthOfWrappedText(stripAnsi(wrappedText)) - const {marginLeft, marginRight} = calculateMargins(spaces) + const {marginLeft, marginRight} = calculateMargins(width - determineWidthOfWrappedText(stripAnsi(wrappedText))) const lines = wrappedText.split('\n').map((line, idx) => { const {marginLeft: lineSpecificLeftMargin} = calculateMargins(width - stripAnsi(line).length) - // if (idx === 0) { - // // if it's the first line, only add margin to the right side (The left margin will be applied later) - // return `${line}${' '.repeat(marginRight)}` - // } if (horizontalAlignment === 'left') { if (idx === 0) {