@teaui/core
Version:
A high-level terminal UI library for Node
206 lines • 8.52 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.debug = debug;
exports.leftPad = leftPad;
exports.rightPad = rightPad;
exports.centerPad = centerPad;
exports.define = define;
exports.wrap = wrap;
const sys_1 = require("./sys");
let _debug = false;
/**
* A global function to turn debugging on/off, useful to test things that would
* otherwise output way too much, ie console in render()
*/
function debug(value) {
return (_debug = value ?? _debug);
}
/**
* Left pads (with spaces) according to terminal width
*/
function leftPad(str, length) {
const lines = str.split('\n');
if (lines.length > 1) {
return lines.map(line => leftPad(line, length)).join('\n');
}
const width = sys_1.unicode.lineWidth(str);
if (width >= length) {
return str;
}
return ' '.repeat(length - width).concat(str);
}
/**
* Right pads (with spaces) according to terminal width
*/
function rightPad(str, length) {
const lines = str.split('\n');
if (lines.length > 1) {
return lines.map(line => rightPad(line, length)).join('\n');
}
const width = sys_1.unicode.lineWidth(str);
if (width >= length) {
return str;
}
return str.concat(' '.repeat(length - width));
}
/**
* Center pads (with spaces) according to terminal width
*/
function centerPad(str, length) {
const lines = str.split('\n');
if (lines.length > 1) {
return lines.map(line => centerPad(line, length)).join('\n');
}
const width = sys_1.unicode.lineWidth(str);
if (width >= length) {
return str;
}
const left = ' '.repeat(~~((length - width) / 2));
const right = ' '.repeat(~~((length - width) / 2 + 0.5));
return left.concat(str, right);
}
/**
* Used to add {enumerable: true} to defined properties on Components, so they
* are picked up by inspect().
*/
function define(object, property, attributes) {
let kls = object;
do {
const descriptor = Object.getOwnPropertyDescriptor(kls, property);
if (descriptor) {
const modified_descriptor = Object.assign(descriptor, attributes);
Object.defineProperty(object, property, modified_descriptor);
return;
}
else {
kls = Object.getPrototypeOf(kls);
}
} while (kls && kls !== Object.prototype);
}
function isStringArray(input) {
return input.length > 0 && typeof input[0] === 'string';
}
function isStringAndWidthArray(input) {
return input.length > 0 && Array.isArray(input[0]);
}
/**
* Wraps lines to fit within contentWidth. Lots to solve for here.
* 1. Word wrapping is accomplished using unicode.words
* 2. ANSI codes are restored by scanning the segments, inserting the ANSI back where it belongs
*/
function wrap(input, contentWidth) {
if (contentWidth === 0) {
return [];
}
let lines;
if (typeof input === 'string') {
lines = input
.split('\n')
.map(line => [line, sys_1.unicode.lineWidth(line)]);
}
else if (isStringArray(input)) {
lines = input.map(line => [line, sys_1.unicode.lineWidth(line)]);
}
else if (isStringAndWidthArray(input)) {
lines = input;
}
else {
return [];
}
return lines.flatMap(([line, width]) => {
if (width <= contentWidth) {
return [[line, width]];
}
const outputLines = [];
function pushTrimmed(line, width) {
const trimmed = line.replace(/\s+$/, '');
const trimmedWidth = width - (line.length - trimmed.length);
outputLines.push([trimmed, trimmedWidth]);
}
let currentChars = [];
let currentWidth = 0;
const STOP = [];
const words = sys_1.unicode.words(line).concat([[STOP, 0]]);
for (const [wordChars] of words) {
let wordWidth = sys_1.unicode.lineWidth(wordChars);
if ((!currentWidth || currentWidth + wordWidth <= contentWidth) &&
wordChars !== STOP) {
// 1) there's enough room on the line (and it's not the sentinel STOP)
// or the line is empty, in which case it must have at least _one_ word.
// it's possible that this will add a word _longer_ than contentWidth.
// that's ok, it will get picked up below (on the next word, or by STOP)
currentChars.push(...wordChars);
currentWidth += wordWidth;
}
else {
// 2) there wasn't enough room on the line – or we are at the STOP sentinel.
if (currentWidth <= contentWidth) {
// 3) if currentChars fits, push it to the buffer and reset currentChars
// if wordChars has content it didn't fit on the current line. It will
// be picked up below
pushTrimmed(currentChars.join(''), currentWidth);
currentChars = [];
currentWidth = 0;
}
else {
// 4) currentChars is longer than contentWidth. Add characters one at a time
// until >contentWidth. The remaining characters can stay in currentChars.
// If we are at the end (STOP), add
do {
let buffer = '', bufferWidth = 0, index = 0;
for (let char of currentChars) {
const charWidth = sys_1.unicode.charWidth(char);
if (bufferWidth && bufferWidth + charWidth > contentWidth) {
pushTrimmed(buffer, bufferWidth);
// Some delicate math here; remove the bufferWidth from currentWidth, and then
// scan past whitespace, removing that width, too. (Avoids an expensive call to
// unicode.lineWidth())
currentWidth -= bufferWidth;
while (currentChars[index]?.match(/^\s+$/)) {
currentWidth -= sys_1.unicode.charWidth(currentChars[index]);
index += 1;
}
currentChars = currentChars.slice(index);
index = 0;
if (currentWidth > contentWidth) {
// if remaining width is still longer than contentWidth, reset bufferWidth and
// continue
buffer = '';
bufferWidth = 0;
}
else {
// either we have more words, or we are at STOP but we have enough room for
// currentChars in contentWidth
break;
}
}
buffer += char;
bufferWidth += charWidth;
index += 1;
}
} while (currentWidth > contentWidth);
}
if (wordChars === STOP && currentChars.length) {
pushTrimmed(currentChars.join(''), currentWidth);
}
else if (wordChars.length) {
// until now, we've only processed the _previous_ currentChars buffer, and it
// needed to be wrapped to contentWidth. If any remains in currentChars, it
// starts with a printable character (we skipped whitespace when buffer was
// full).
// If it's empty, we should skip the preceding whitespace in wordChars.
if (currentChars.length === 0) {
while (wordChars.length && wordChars[0].match(/^\s+$/)) {
const char = wordChars.shift();
wordWidth -= sys_1.unicode.charWidth(char);
}
}
currentChars.push(...wordChars);
currentWidth += wordWidth;
}
}
}
return outputLines;
});
}
//# sourceMappingURL=util.js.map