diff --git a/index.d.ts b/index.d.ts index 8bef1f0..438fe95 100644 --- a/index.d.ts +++ b/index.d.ts @@ -50,6 +50,26 @@ export interface Options { ```` */ readonly preferTruncationOnSpace?: boolean; + + /** + The character to use at the breaking point. + + @default '…' + + @example + ``` + import cliTruncate from 'cli-truncate'; + + cliTruncate('unicorns', 5, {position: 'end'}); + //=> 'unic…' + + cliTruncate('unicorns', 5, {position: 'end', truncationCharacter: '.'}); + //=> 'unic.' + + cliTruncate('unicorns', 5, {position: 'end', truncationCharacter: ''}); + //=> 'unico' + */ + readonly truncationCharacter?: string; } /** diff --git a/index.js b/index.js index e119653..c7e8de7 100644 --- a/index.js +++ b/index.js @@ -23,12 +23,12 @@ export default function cliTruncate(text, columns, options) { options = { position: 'end', preferTruncationOnSpace: false, + truncationCharacter: '…', ...options, }; const {position, space, preferTruncationOnSpace} = options; - let ellipsis = '…'; - let ellipsisWidth = 1; + let {truncationCharacter} = options; if (typeof text !== 'string') { throw new TypeError(`Expected \`input\` to be a string, got ${typeof text}`); @@ -43,7 +43,7 @@ export default function cliTruncate(text, columns, options) { } if (columns === 1) { - return ellipsis; + return truncationCharacter; } const length = stringWidth(text); @@ -55,21 +55,19 @@ export default function cliTruncate(text, columns, options) { if (position === 'start') { if (preferTruncationOnSpace) { const nearestSpace = getIndexOfNearestSpace(text, length - columns + 1, true); - return ellipsis + sliceAnsi(text, nearestSpace, length).trim(); + return truncationCharacter + sliceAnsi(text, nearestSpace, length).trim(); } if (space === true) { - ellipsis += ' '; - ellipsisWidth = 2; + truncationCharacter += ' '; } - return ellipsis + sliceAnsi(text, length - columns + ellipsisWidth, length); + return truncationCharacter + sliceAnsi(text, length - columns + stringWidth(truncationCharacter), length); } if (position === 'middle') { if (space === true) { - ellipsis = ` ${ellipsis} `; - ellipsisWidth = 3; + truncationCharacter = ` ${truncationCharacter} `; } const half = Math.floor(columns / 2); @@ -77,28 +75,27 @@ export default function cliTruncate(text, columns, options) { 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, spaceNearFirstBreakPoint) + truncationCharacter + sliceAnsi(text, spaceNearSecondBreakPoint, length).trim(); } return ( sliceAnsi(text, 0, half) - + ellipsis - + sliceAnsi(text, length - (columns - half) + ellipsisWidth, length) + + truncationCharacter + + sliceAnsi(text, length - (columns - half) + stringWidth(truncationCharacter), length) ); } if (position === 'end') { if (preferTruncationOnSpace) { const nearestSpace = getIndexOfNearestSpace(text, columns - 1); - return sliceAnsi(text, 0, nearestSpace) + ellipsis; + return sliceAnsi(text, 0, nearestSpace) + truncationCharacter; } if (space === true) { - ellipsis = ` ${ellipsis}`; - ellipsisWidth = 2; + truncationCharacter = ` ${truncationCharacter}`; } - return sliceAnsi(text, 0, columns - ellipsisWidth) + ellipsis; + return sliceAnsi(text, 0, columns - stringWidth(truncationCharacter)) + truncationCharacter; } throw new Error(`Expected \`options.position\` to be either \`start\`, \`middle\` or \`end\`, got ${position}`); diff --git a/readme.md b/readme.md index afa93bc..22570a7 100644 --- a/readme.md +++ b/readme.md @@ -124,6 +124,26 @@ cliTruncate('unicorns rainbow dragons', 6, {position: 'middle', preferTruncation //=> 'uni…ns' ``` +##### truncationCharacter + +Type: `string`\ +Default: `…` + +The character to use at the breaking point. + +```js +import cliTruncate from 'cli-truncate'; + +cliTruncate('unicorns', 5, {position: 'end'}); +//=> 'unic…' + +cliTruncate('unicorns', 5, {position: 'end', truncationCharacter: '.'}); +//=> 'unic.' + +cliTruncate('unicorns', 5, {position: 'end', truncationCharacter: ''}); +//=> 'unico' +``` + ## Related - [wrap-ansi](https://github.com/chalk/wrap-ansi) - Wordwrap a string with ANSI escape codes diff --git a/test.js b/test.js index a71aca0..7ceffbe 100644 --- a/test.js +++ b/test.js @@ -43,3 +43,14 @@ test('preferTruncationOnSpace option', t => { t.is(cliTruncate('unicorns rainbow dragons', 6, {position: 'middle', preferTruncationOnSpace: true}), 'uni…ns'); t.is(cliTruncate('unicorns partying with dragons', 20, {position: 'middle', preferTruncationOnSpace: true}), 'unicorns…dragons'); }); + +test('truncationCharacter option', t => { + t.is(cliTruncate('unicorns', 5, {position: 'end', truncationCharacter: '.'}), 'unic.'); + t.is(cliTruncate('unicorns', 5, {position: 'start', truncationCharacter: '.'}), '.orns'); + t.is(cliTruncate('unicorns', 5, {position: 'middle', truncationCharacter: '.'}), 'un.ns'); + t.is(cliTruncate('unicorns', 5, {position: 'end', truncationCharacter: '.', space: true}), 'uni .'); + t.is(cliTruncate('unicorns', 5, {position: 'end', truncationCharacter: ' .'}), 'uni .'); + t.is(cliTruncate('unicorns partying with dragons', 20, {position: 'middle', truncationCharacter: '.', preferTruncationOnSpace: true}), 'unicorns.dragons'); + t.is(cliTruncate('안녕하세요', 4, {position: 'start', space: true, truncationCharacter: '.'}), '. 요', 'wide char'); + t.is(cliTruncate('\u001B[31municornsareawesome\u001B[39m', 10, {position: 'middle', space: true, truncationCharacter: '.'}), '\u001B[31munico\u001B[39m . \u001B[31mme\u001B[39m'); +});