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

Feature: height and fullscreen option #75

Merged
merged 11 commits into from
Jun 13, 2022
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.
Caesarovich marked this conversation as resolved.
Show resolved Hide resolved

__function__: Pass a callback function to control box dimensions.
Caesarovich marked this conversation as resolved.
Show resolved Hide resolved

Caesarovich marked this conversation as resolved.
Show resolved Hide resolved
@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