9d1e837873478c57d656b90b323a3e784dcaeb47.svn-base 2.49 KB
'use strict';
const sliceAnsi = require('slice-ansi');
const stringWidth = require('string-width');

function getIndexOfNearestSpace(string, index, shouldSearchRight) {
	if (string.charAt(index) === ' ') {
		return index;
	}

	for (let i = 1; i <= 3; i++) {
		if (shouldSearchRight) {
			if (string.charAt(index + i) === ' ') {
				return index + i;
			}
		} else if (string.charAt(index - i) === ' ') {
			return index - i;
		}
	}

	return index;
}

module.exports = (text, columns, options) => {
	options = {
		position: 'end',
		preferTruncationOnSpace: false,
		...options
	};

	const {position, space, preferTruncationOnSpace} = options;
	let ellipsis = '…';
	let ellipsisWidth = 1;

	if (typeof text !== 'string') {
		throw new TypeError(`Expected \`input\` to be a string, got ${typeof text}`);
	}

	if (typeof columns !== 'number') {
		throw new TypeError(`Expected \`columns\` to be a number, got ${typeof columns}`);
	}

	if (columns < 1) {
		return '';
	}

	if (columns === 1) {
		return ellipsis;
	}

	const length = stringWidth(text);

	if (length <= columns) {
		return text;
	}

	if (position === 'start') {
		if (preferTruncationOnSpace) {
			const nearestSpace = getIndexOfNearestSpace(text, length - columns + 1, true);
			return ellipsis + sliceAnsi(text, nearestSpace, length).trim();
		}

		if (space === true) {
			ellipsis += ' ';
			ellipsisWidth = 2;
		}

		return ellipsis + sliceAnsi(text, length - columns + ellipsisWidth, length);
	}

	if (position === 'middle') {
		if (space === true) {
			ellipsis = ' ' + ellipsis + ' ';
			ellipsisWidth = 3;
		}

		const half = Math.floor(columns / 2);

		if (preferTruncationOnSpace) {
			const spaceNearFirstBreakPoint = getIndexOfNearestSpace(text, half);
			const spaceNearSecondBreakPoint = getIndexOfNearestSpace(text, length - (columns - half) + 1, true);
			return sliceAnsi(text, 0, spaceNearFirstBreakPoint) + ellipsis + sliceAnsi(text, spaceNearSecondBreakPoint, length).trim();
		}

		return (
			sliceAnsi(text, 0, half) +
			ellipsis +
			sliceAnsi(text, length - (columns - half) + ellipsisWidth, length)
		);
	}

	if (position === 'end') {
		if (preferTruncationOnSpace) {
			const nearestSpace = getIndexOfNearestSpace(text, columns - 1);
			return sliceAnsi(text, 0, nearestSpace) + ellipsis;
		}

		if (space === true) {
			ellipsis = ' ' + ellipsis;
			ellipsisWidth = 2;
		}

		return sliceAnsi(text, 0, columns - ellipsisWidth) + ellipsis;
	}

	throw new Error(`Expected \`options.position\` to be either \`start\`, \`middle\` or \`end\`, got ${position}`);
};