From 557fe834b1d555210d77463b25eb8582381b2639 Mon Sep 17 00:00:00 2001 From: speedytwenty Date: Thu, 31 Mar 2022 14:51:12 -0600 Subject: [PATCH] Add wrapOnWordBoundary option (#125) --- README.md | 2 +- basic-usage.md | 41 ++++++++++++++++++++++------ examples/basic-usage-examples.js | 47 ++++++++++++++++++++++++++------ index.d.ts | 1 + src/cell.js | 5 ++-- src/utils.js | 26 ++++++++++++++++-- test/utils-test.js | 12 ++++++++ 7 files changed, 111 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index a5be414..d872fa4 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ unmaintained. `cli-table3` includes all the additional features from - Ability to make cells span columns and/or rows. - Ability to set custom styles per cell (border characters/colors, padding, etc). - Vertical alignment (top, bottom, center). -- Automatic word wrapping. +- [Word wrapping options](./basic-usage.md#set-wordwrap-to-true-to-wrap-text-on-word-boundaries). - More robust truncation of cell text that contains ansi color characters. - Better handling of text color that spans multiple lines. - API compatible with the original cli-table. diff --git a/basic-usage.md b/basic-usage.md index 0bc35dc..1aab54c 100644 --- a/basic-usage.md +++ b/basic-usage.md @@ -125,21 +125,44 @@ ``` -##### Set `wordWrap` to true to make lines of text wrap instead of being truncated - ┌───────┬─────────┐ - │ Hello │ I am │ - │ how │ fine │ - │ are │ thanks! │ - │ you? │ │ - └───────┴─────────┘ +##### Set `wordWrap` to true to wrap text on word boundaries + ┌───────┬─────────┬───────────────────┬──────────────┐ + │ Hello │ I am │ Words that exceed │ Text is only │ + │ how │ fine │ the colWidth will │ wrapped for │ + │ are │ thanks! │ be truncated. │ fixed width │ + │ you? │ Looooo… │ │ columns. │ + └───────┴─────────┴───────────────────┴──────────────┘ ```javascript let table = new Table({ style: { border: [], header: [] }, - colWidths: [7, 9], + colWidths: [7, 9], // Requires fixed column widths wordWrap: true, }); - table.push(['Hello how are you?', 'I am fine thanks!']); + table.push([ + 'Hello how are you?', + 'I am fine thanks! Looooooong', + ['Words that exceed', 'the colWidth will', 'be truncated.'].join('\n'), + ['Text is only', 'wrapped for', 'fixed width', 'columns.'].join('\n'), + ]); ``` + +##### Using `wordWrap`, set `wrapOnWordBoundary` to false to ignore word boundaries + ┌───┬───┐ + │ W │ T │ + │ r │ e │ + │ a │ x │ + │ p │ t │ + └───┴───┘ +```javascript + const table = new Table({ + style: { border: [], header: [] }, + colWidths: [3, 3], // colWidths must all be greater than 2!!!! + wordWrap: true, + wrapOnWordBoundary: false, + }); + table.push(['Wrap', 'Text']); +``` + diff --git a/examples/basic-usage-examples.js b/examples/basic-usage-examples.js index 9a9154c..a9dc99b 100644 --- a/examples/basic-usage-examples.js +++ b/examples/basic-usage-examples.js @@ -182,26 +182,55 @@ module.exports = function (runTest) { return [makeTable, expected, 'multi-line-colors']; }); - it('Set `wordWrap` to true to make lines of text wrap instead of being truncated', function () { + it('Set `wordWrap` to true to wrap text on word boundaries', function () { function makeTable() { let table = new Table({ style: { border: [], header: [] }, - colWidths: [7, 9], + colWidths: [7, 9], // Requires fixed column widths wordWrap: true, }); - table.push(['Hello how are you?', 'I am fine thanks!']); + table.push([ + 'Hello how are you?', + 'I am fine thanks! Looooooong', + ['Words that exceed', 'the colWidth will', 'be truncated.'].join('\n'), + ['Text is only', 'wrapped for', 'fixed width', 'columns.'].join('\n'), + ]); return table; } let expected = [ - '┌───────┬─────────┐', - '│ Hello │ I am │', - '│ how │ fine │', - '│ are │ thanks! │', - '│ you? │ │', - '└───────┴─────────┘', + '┌───────┬─────────┬───────────────────┬──────────────┐', + '│ Hello │ I am │ Words that exceed │ Text is only │', + '│ how │ fine │ the colWidth will │ wrapped for │', + '│ are │ thanks! │ be truncated. │ fixed width │', + '│ you? │ Looooo… │ │ columns. │', + '└───────┴─────────┴───────────────────┴──────────────┘', + ]; + + return [makeTable, expected]; + }); + + it('Using `wordWrap`, set `wrapOnWordBoundary` to false to ignore word boundaries', function () { + function makeTable() { + const table = new Table({ + style: { border: [], header: [] }, + colWidths: [3, 3], // colWidths must all be greater than 2!!!! + wordWrap: true, + wrapOnWordBoundary: false, + }); + table.push(['Wrap', 'Text']); + return table; + } + + let expected = [ + '┌───┬───┐', + '│ W │ T │', + '│ r │ e │', + '│ a │ x │', + '│ p │ t │', + '└───┴───┘', ]; return [makeTable, expected]; diff --git a/index.d.ts b/index.d.ts index aa94b74..16980f8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -27,6 +27,7 @@ declare namespace CliTable3 { rowAligns: VerticalAlignment[]; head: string[]; wordWrap: boolean; + wrapOnWordBoundary: boolean; } interface TableInstanceOptions extends TableOptions { diff --git a/src/cell.js b/src/cell.js index 1761d26..c69aa0c 100644 --- a/src/cell.js +++ b/src/cell.js @@ -58,7 +58,7 @@ class Cell { this.border = style.border || tableStyle.border; let fixedWidth = tableOptions.colWidths[this.x]; - if (tableOptions.wordWrap && fixedWidth) { + if ((tableOptions.wordWrap || tableOptions.textWrap) && fixedWidth) { fixedWidth -= this.paddingLeft + this.paddingRight; if (this.colSpan) { let i = 1; @@ -67,7 +67,8 @@ class Cell { i++; } } - this.lines = utils.colorizeLines(utils.wordWrap(fixedWidth, this.content)); + const { wrapOnWordBoundary = true } = tableOptions; + this.lines = utils.colorizeLines(utils.wordWrap(fixedWidth, this.content, wrapOnWordBoundary)); } else { this.lines = utils.colorizeLines(this.content.split('\n')); } diff --git a/src/utils.js b/src/utils.js index 1e6254a..1204a30 100644 --- a/src/utils.js +++ b/src/utils.js @@ -240,6 +240,7 @@ function mergeOptions(options, defaults) { return ret; } +// Wrap on word boundary function wordWrap(maxLength, input) { let lines = []; let split = input.split(/(\s+)/g); @@ -270,11 +271,32 @@ function wordWrap(maxLength, input) { return lines; } -function multiLineWordWrap(maxLength, input) { +// Wrap text (ignoring word boundaries) +function textWrap(maxLength, input) { + let lines = []; + let line = ''; + function pushLine(str, ws) { + if (line.length && ws) line += ws; + line += str; + while (line.length > maxLength) { + lines.push(line.slice(0, maxLength)); + line = line.slice(maxLength); + } + } + let split = input.split(/(\s+)/g); + for (let i = 0; i < split.length; i += 2) { + pushLine(split[i], i && split[i - 1]); + } + if (line.length) lines.push(line); + return lines; +} + +function multiLineWordWrap(maxLength, input, wrapOnWordBoundary = true) { let output = []; input = input.split('\n'); + const handler = wrapOnWordBoundary ? wordWrap : textWrap; for (let i = 0; i < input.length; i++) { - output.push.apply(output, wordWrap(maxLength, input[i])); + output.push.apply(output, handler(maxLength, input[i])); } return output; } diff --git a/test/utils-test.js b/test/utils-test.js index 3d612be..3ef1632 100644 --- a/test/utils-test.js +++ b/test/utils-test.js @@ -322,6 +322,18 @@ describe('utils', function () { let expected = ['\x1b[31m漢字\x1b[0m', ' 漢字']; expect(wordWrap(5, input)).toEqual(expected); }); + + describe('textWrap', function () { + it('wraps long words', function () { + expect(wordWrap(10, 'abcdefghijklmnopqrstuvwxyz', false)).toEqual(['abcdefghij', 'klmnopqrst', 'uvwxyz']); + expect(wordWrap(10, 'abcdefghijk lmnopqrstuv wxyz', false)).toEqual(['abcdefghij', 'k lmnopqrs', 'tuv wxyz']); + expect(wordWrap(10, 'ab cdefghijk lmnopqrstuv wx yz', false)).toEqual([ + 'ab cdefghi', + 'jk lmnopqr', + 'stuv wx yz', + ]); + }); + }); }); describe('colorizeLines', function () {