Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wrapOnWordBoundary option for consistently readable word wrapping #217

Merged
merged 1 commit into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
41 changes: 32 additions & 9 deletions basic-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
```

47 changes: 38 additions & 9 deletions examples/basic-usage-examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ declare namespace CliTable3 {
rowAligns: VerticalAlignment[];
head: string[];
wordWrap: boolean;
wrapOnWordBoundary: boolean;
}

interface TableInstanceOptions extends TableOptions {
Expand Down
5 changes: 3 additions & 2 deletions src/cell.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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'));
}
Expand Down
26 changes: 24 additions & 2 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down
12 changes: 12 additions & 0 deletions test/utils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down