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

Draft: Add width option #54

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions example.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,12 @@ console.log('\n\n' + boxen(sentences, {align: 'right', padding: {left: 1, right:

const longWord = 'x'.repeat(process.stdout.columns + 20);
console.log('\n\n' + boxen(longWord, {align: 'center'}) + '\n');

// Normal width
console.log('\n\n' + boxen(sentences, {align: 'center', width: 100}) + '\n');
// Higher than max terminal width
console.log('\n\n' + boxen(sentences, {align: 'center', width: 999}) + '\n');
// Normal width with small text
console.log('\n\n' + boxen('small word', {align: 'center', width: 100}) + '\n');
// Width smaller than text
console.log('\n\n' + boxen('small word', {align: 'center', width: 9}) + '\n');
7 changes: 7 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ declare namespace boxen {
*/
readonly padding?: number | Spacing;

/**
Width of the text content

@default undefined
*/
readonly width?: number;

/**
Space around the box.

Expand Down
82 changes: 48 additions & 34 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ const chalk = require('chalk');
const widestLine = require('widest-line');
const cliBoxes = require('cli-boxes');
const camelCase = require('camelcase');
const ansiAlign = require('ansi-align');
const wrapAnsi = require('wrap-ansi');

const NL = '\n';
const PAD = ' ';

const terminalColumns = () => {
const {env, stdout, stderr} = process;

Expand Down Expand Up @@ -71,6 +73,20 @@ const getBorderChars = borderStyle => {
return chararacters;
};

const wrapLines = (max, lines) => {
const newLines = [];
for (const line of lines) {
const createdLines = wrapAnsi(line, max, {hard: true});
const alignedLinesArray = createdLines.split(NL);

for (const alignedLine of alignedLinesArray) {
newLines.push(alignedLine);
}
}

return newLines;
};

const isHex = color => color.match(/^#(?:[0-f]{3}){1,2}$/i);
const isColorValid = color => typeof color === 'string' && ((chalk[color]) || isHex(color));
const getColorFn = color => isHex(color) ? chalk.hex(color) : chalk[color];
Expand Down Expand Up @@ -105,46 +121,24 @@ module.exports = (text, options) => {

const colorizeContent = content => options.backgroundColor ? getBGColorFn(options.backgroundColor)(content) : content;

const NL = '\n';
const PAD = ' ';
const columns = terminalColumns();

text = ansiAlign(text, {align: options.align});

let lines = text.split(NL);

let contentWidth = widestLine(text) + padding.left + padding.right;

const BORDERS_WIDTH = 2;
if (contentWidth + BORDERS_WIDTH > columns) {

if (options.width && contentWidth > options.width) {
contentWidth = options.width - BORDERS_WIDTH;
const max = contentWidth - padding.left - padding.right;

lines = wrapLines(max, lines);
} else if (contentWidth + BORDERS_WIDTH > columns) {
contentWidth = columns - BORDERS_WIDTH;
const max = contentWidth - padding.left - padding.right;
const newLines = [];
for (const line of lines) {
const createdLines = wrapAnsi(line, max, {hard: true});
const alignedLines = ansiAlign(createdLines, {align: options.align});
const alignedLinesArray = alignedLines.split('\n');
const longestLength = Math.max(...alignedLinesArray.map(s => stringWidth(s)));

for (const alignedLine of alignedLinesArray) {
let paddedLine;
switch (options.align) {
case 'center':
paddedLine = PAD.repeat((max - longestLength) / 2) + alignedLine;
break;
case 'right':
paddedLine = PAD.repeat(max - longestLength) + alignedLine;
break;
default:
paddedLine = alignedLine;
break;
}

newLines.push(paddedLine);
}
}

lines = newLines;
lines = wrapLines(max, lines);
}

if (contentWidth + BORDERS_WIDTH + margin.left + margin.right > columns) {
Expand All @@ -168,6 +162,7 @@ module.exports = (text, options) => {
}

const paddingLeft = PAD.repeat(padding.left);
const paddingRight = PAD.repeat(padding.right);
let marginLeft = PAD.repeat(margin.left);

if (options.float === 'center') {
Expand All @@ -178,16 +173,35 @@ module.exports = (text, options) => {
marginLeft = PAD.repeat(padWidth);
}

const horizontal = chars.horizontal.repeat(contentWidth);
const totalWidth = options.width ? (options.width + BORDERS_WIDTH + padding.left + padding.right > columns ? columns - BORDERS_WIDTH : options.width) : contentWidth;

const horizontal = chars.horizontal.repeat(totalWidth);
const top = colorizeBorder(NL.repeat(margin.top) + marginLeft + chars.topLeft + horizontal + chars.topRight);
const bottom = colorizeBorder(marginLeft + chars.bottomLeft + horizontal + chars.bottomRight + NL.repeat(margin.bottom));
const side = colorizeBorder(chars.vertical);

const LINE_SEPARATOR = (contentWidth + BORDERS_WIDTH + margin.left >= columns) ? '' : NL;

const middle = lines.map(line => {
const paddingRight = PAD.repeat(contentWidth - stringWidth(line) - padding.left);
return marginLeft + side + colorizeContent(paddingLeft + line + paddingRight) + side;
const alignmentPadding = totalWidth - stringWidth(line) - padding.left - padding.right;
let rightAlignmentPadding = '';
let leftAlignmentPadding = '';
switch (options.align) {
case 'center':
rightAlignmentPadding = PAD.repeat(Math.ceil(alignmentPadding / 2));
leftAlignmentPadding = PAD.repeat(alignmentPadding / 2);
break;
case 'right':
leftAlignmentPadding = PAD.repeat(alignmentPadding);
break;
default:
rightAlignmentPadding = PAD.repeat(alignmentPadding);
break;
}

return marginLeft + side + colorizeContent(
paddingLeft + leftAlignmentPadding + line + rightAlignmentPadding + paddingRight
) + side;
}).join(LINE_SEPARATOR);

return top + LINE_SEPARATOR + middle + LINE_SEPARATOR + bottom;
Expand Down
1 change: 1 addition & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ expectType<string>(boxen('unicorns', {float: 'center'}));
expectType<string>(boxen('unicorns', {backgroundColor: 'green'}));
expectType<string>(boxen('unicorns', {backgroundColor: '#ff0000'}));
expectType<string>(boxen('unicorns', {align: 'right'}));
expectType<string>(boxen('unicorns', {width: 30}));
8 changes: 8 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ Space between the text and box border.

Accepts a number or an object with any of the `top`, `right`, `bottom`, `left` properties. When a number is specified, the left/right padding is 3 times the top/bottom to make it look nice.

##### width

Type: `number`\
Default: `undefined`

Determine the width of the area the text will render in


##### margin

Type: `number | object`\
Expand Down