Skip to content

Commit

Permalink
Add height and fullscreen option (#75)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <[email protected]>
  • Loading branch information
Caesarovich and sindresorhus authored Jun 13, 2022
1 parent a3bfac7 commit d6b4b32
Show file tree
Hide file tree
Showing 11 changed files with 356 additions and 20 deletions.
12 changes: 12 additions & 0 deletions example.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,15 @@ console.log('\n\n' + boxen('This box has a centered title', {title, titleAlignme
console.log('\n\n' + boxen('This box has fixed width of 20', {width: 20}) + '\n');

console.log('\n\n' + boxen('This box has fixed width of 50', {width: 50}) + '\n');

console.log('\n\n' + boxen('This box has fixed height of 5', {height: 5}) + '\n');

console.log('\n\n' + boxen('This box has fixed height of 5', {height: 5, padding: 2}) + '\n');

console.log('\n\n' + boxen('This box has fixed height of 5 and width of 15', {height: 8, width: 15}) + '\n');

console.log('\n\n' + boxen('This box is in fullscreen !', {fullscreen: true}) + '\n');

console.log('\n\n' + boxen('This box is in full-width and half-height !', {fullscreen: (w, h) => [w, h / 2]}) + '\n');

console.log('\n\n' + boxen('And this one is in half-width and full-height !', {fullscreen: (w, h) => [w / 2, h]}) + '\n');
37 changes: 36 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export interface Options {
/**
Set a fixed width for the box.
**Note*: This disables terminal overflow handling and may cause the box to look broken if the user's terminal is not wide enough.
__Note__: This disables terminal overflow handling and may cause the box to look broken if the user's terminal is not wide enough.
@example
```
Expand All @@ -193,6 +193,41 @@ export interface Options {
```
*/
readonly width?: number;

/**
Set a fixed height for the box.
__Note__: This option will crop overflowing content.
@example
```
import boxen from 'boxen';
console.log(boxen('foo bar', {height: 5}));
// ┌───────┐
// │foo bar│
// │ │
// │ │
// └───────┘
```
*/
readonly height?: number;

/**
__boolean__: Wether or not to fit all available space within the terminal.
__function__: Pass a callback function to control box dimensions.
@example
```
import boxen from 'boxen';
console.log(boxen('foo bar', {
fullscreen: (width, height) => [width, height - 1];
}));
```
*/
readonly fullscreen?: boolean | ((width: number, height: number) => [width: number, height: number]);
}

/**
Expand Down
76 changes: 57 additions & 19 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,24 +116,24 @@ const makeTitle = (text, horizontal, alignement) => {
return title;
};

const makeContentText = (text, padding, columns, align) => {
text = ansiAlign(text, {align});
const makeContentText = (text, {padding, width, textAlignment, height}) => {
text = ansiAlign(text, {align: textAlignment});
let lines = text.split(NEWLINE);
const textWidth = widestLine(text);

const max = columns - padding.left - padding.right;
const max = width - padding.left - padding.right;

if (textWidth > max) {
const newLines = [];
for (const line of lines) {
const createdLines = wrapAnsi(line, max, {hard: true});
const alignedLines = ansiAlign(createdLines, {align});
const alignedLines = ansiAlign(createdLines, {align: textAlignment});
const alignedLinesArray = alignedLines.split('\n');
const longestLength = Math.max(...alignedLinesArray.map(s => stringWidth(s)));

for (const alignedLine of alignedLinesArray) {
let paddedLine;
switch (align) {
switch (textAlignment) {
case 'center':
paddedLine = PAD.repeat((max - longestLength) / 2) + alignedLine;
break;
Expand All @@ -152,9 +152,9 @@ const makeContentText = (text, padding, columns, align) => {
lines = newLines;
}

if (align === 'center' && textWidth < max) {
if (textAlignment === 'center' && textWidth < max) {
lines = lines.map(line => PAD.repeat((max - textWidth) / 2) + line);
} else if (align === 'right' && textWidth < max) {
} else if (textAlignment === 'right' && textWidth < max) {
lines = lines.map(line => PAD.repeat(max - textWidth) + line);
}

Expand All @@ -164,26 +164,32 @@ const makeContentText = (text, padding, columns, align) => {
lines = lines.map(line => paddingLeft + line + paddingRight);

lines = lines.map(line => {
if (columns - stringWidth(line) > 0) {
switch (align) {
if (width - stringWidth(line) > 0) {
switch (textAlignment) {
case 'center':
return line + PAD.repeat(columns - stringWidth(line));
return line + PAD.repeat(width - stringWidth(line));
case 'right':
return line + PAD.repeat(columns - stringWidth(line));
return line + PAD.repeat(width - stringWidth(line));
default:
return line + PAD.repeat(columns - stringWidth(line));
return line + PAD.repeat(width - stringWidth(line));
}
}

return line;
});

if (padding.top > 0) {
lines = [...Array.from({length: padding.top}).fill(PAD.repeat(columns)), ...lines];
lines = [...Array.from({length: padding.top}).fill(PAD.repeat(width)), ...lines];
}

if (padding.bottom > 0) {
lines = [...lines, ...Array.from({length: padding.bottom}).fill(PAD.repeat(columns))];
lines = [...lines, ...Array.from({length: padding.bottom}).fill(PAD.repeat(width))];
}

if (height && lines.length > height) {
lines = lines.slice(0, height);
} else if (height && lines.length < height) {
lines = [...lines, ...Array.from({length: height - lines.length}).fill(PAD.repeat(width))];
}

return lines.join(NEWLINE);
Expand Down Expand Up @@ -221,16 +227,43 @@ const boxContent = (content, contentWidth, options) => {
return top + LINE_SEPARATOR + middle + LINE_SEPARATOR + bottom;
};

const determineDimensions = (text, options) => {
const widthOverride = options.width !== undefined;
const columns = terminalColumns();
const maxWidth = columns - options.margin.left - options.margin.right - BORDERS_WIDTH;
const sanitizeOptions = options => {
// If fullscreen is enabled, max-out unspecified width/height
if (options.fullscreen && process && process.stdout) {
let newDimensions = [process.stdout.columns, process.stdout.rows];

if (typeof options.fullscreen === 'function') {
newDimensions = options.fullscreen(...newDimensions);
}

if (!options.width) {
options.width = newDimensions[0];
}

if (!options.height) {
options.height = newDimensions[1];
}
}

// If width is provided, make sure it's not below 1
if (options.width) {
options.width = Math.max(1, options.width - BORDERS_WIDTH);
}

// If height is provided, make sure it's not below 1
if (options.height) {
options.height = Math.max(1, options.height - BORDERS_WIDTH);
}

return options;
};

const determineDimensions = (text, options) => {
options = sanitizeOptions(options);
const widthOverride = options.width !== undefined;
const columns = terminalColumns();
const maxWidth = columns - options.margin.left - options.margin.right - BORDERS_WIDTH;

const widest = widestLine(wrapAnsi(text, columns - BORDERS_WIDTH, {hard: true, trim: false})) + options.padding.left + options.padding.right;

// If title and width are provided, title adheres to fixed width
Expand Down Expand Up @@ -278,6 +311,11 @@ const determineDimensions = (text, options) => {
options.padding.right = 0;
}

if (options.height && options.height - (options.padding.top + options.padding.bottom) <= 0) {
options.padding.top = 0;
options.padding.bottom = 0;
}

return options;
};

Expand Down Expand Up @@ -315,7 +353,7 @@ export default function boxen(text, options) {

options = determineDimensions(text, options);

text = makeContentText(text, options.padding, options.width, options.textAlignment);
text = makeContentText(text, options);

return boxContent(text, options.width, options);
}
Expand Down
2 changes: 2 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ expectType<string>(boxen('unicorns', {backgroundColor: 'green'}));
expectType<string>(boxen('unicorns', {backgroundColor: '#ff0000'}));
expectType<string>(boxen('unicorns', {textAlignment: 'right'}));
expectType<string>(boxen('unicorns', {width: 20}));
expectType<string>(boxen('unicorns', {height: 5}));
expectType<string>(boxen('unicorns', {fullscreen: true}));
44 changes: 44 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,50 @@ Set a fixed width for the box.

*Note:* This disables terminal overflow handling and may cause the box to look broken if the user's terminal is not wide enough.

```js
import boxen from 'boxen';

console.log(boxen('foo bar', {width: 15}));
// ┌─────────────┐
// │foo bar │
// └─────────────┘
```

##### height

Type: `number`

Set a fixed height for the box.

*Note:* This option will crop overflowing content.

```js
import boxen from 'boxen';

console.log(boxen('foo bar', {height: 5}));
// ┌───────┐
// │foo bar│
// │ │
// │ │
// └───────┘
```

##### fullscreen

Type: `boolean | (width: number, height: number) => [width: number, height: number]`

Wether or not to fit all available space within the terminal.

Pass a callback function to control box dimensions:

```js
import boxen from 'boxen';

console.log(boxen('foo bar', {
fullscreen: (width, height) => [width, height - 1];
}));
```

##### padding

Type: `number | object`\
Expand Down
36 changes: 36 additions & 0 deletions tests/fullscreen-option.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import test from 'ava';
import boxen from '../index.js';

test('fullscreen option', t => {
const box = boxen('foo', {
fullscreen: true,
});

t.snapshot(box);
});

test('fullscreen option + width', t => {
const box = boxen('foo', {
fullscreen: true,
width: 10,
});

t.snapshot(box);
});

test('fullscreen option + height', t => {
const box = boxen('foo', {
fullscreen: true,
height: 10,
});

t.snapshot(box);
});

test('fullscreen option with callback', t => {
const box = boxen('foo', {
fullscreen: (width, height) => [width - 2, height - 2],
});

t.snapshot(box);
});
51 changes: 51 additions & 0 deletions tests/height-option.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import test from 'ava';
import boxen from '../index.js';

test('height option works', t => {
// Creates a tall box with empty rows
t.snapshot(
boxen('foo', {
height: 5,
}),
);

// Creates a 1 line box, cropping the other lines
t.snapshot(
boxen('foo bar\nfoo bar', {
height: 3,
}),
);
});

test('height option with padding + margin', t => {
// Creates a wide box for little text
const box = boxen('foo', {
height: 20,
margin: 2,
padding: 1,
});

t.snapshot(box);
});

test('height option with width', t => {
// Creates a wide box for little text
const box = boxen('foo', {
height: 5,
width: 20,
});

t.snapshot(box);
});

test('height option with width + padding + margin', t => {
// Creates a wide box for little text
const box = boxen('foo', {
height: 5,
width: 20,
margin: 2,
padding: 1,
});

t.snapshot(box);
});
Loading

0 comments on commit d6b4b32

Please sign in to comment.