index.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import sliceAnsi from 'slice-ansi';
  2. import stringWidth from 'string-width';
  3. function getIndexOfNearestSpace(string, wantedIndex, shouldSearchRight) {
  4. if (string.charAt(wantedIndex) === ' ') {
  5. return wantedIndex;
  6. }
  7. for (let index = 1; index <= 3; index++) {
  8. if (shouldSearchRight) {
  9. if (string.charAt(wantedIndex + index) === ' ') {
  10. return wantedIndex + index;
  11. }
  12. } else if (string.charAt(wantedIndex - index) === ' ') {
  13. return wantedIndex - index;
  14. }
  15. }
  16. return wantedIndex;
  17. }
  18. export default function cliTruncate(text, columns, options) {
  19. options = {
  20. position: 'end',
  21. preferTruncationOnSpace: false,
  22. truncationCharacter: '…',
  23. ...options,
  24. };
  25. const {position, space, preferTruncationOnSpace} = options;
  26. let {truncationCharacter} = options;
  27. if (typeof text !== 'string') {
  28. throw new TypeError(`Expected \`input\` to be a string, got ${typeof text}`);
  29. }
  30. if (typeof columns !== 'number') {
  31. throw new TypeError(`Expected \`columns\` to be a number, got ${typeof columns}`);
  32. }
  33. if (columns < 1) {
  34. return '';
  35. }
  36. if (columns === 1) {
  37. return truncationCharacter;
  38. }
  39. const length = stringWidth(text);
  40. if (length <= columns) {
  41. return text;
  42. }
  43. if (position === 'start') {
  44. if (preferTruncationOnSpace) {
  45. const nearestSpace = getIndexOfNearestSpace(text, length - columns + 1, true);
  46. return truncationCharacter + sliceAnsi(text, nearestSpace, length).trim();
  47. }
  48. if (space === true) {
  49. truncationCharacter += ' ';
  50. }
  51. return truncationCharacter + sliceAnsi(text, length - columns + stringWidth(truncationCharacter), length);
  52. }
  53. if (position === 'middle') {
  54. if (space === true) {
  55. truncationCharacter = ` ${truncationCharacter} `;
  56. }
  57. const half = Math.floor(columns / 2);
  58. if (preferTruncationOnSpace) {
  59. const spaceNearFirstBreakPoint = getIndexOfNearestSpace(text, half);
  60. const spaceNearSecondBreakPoint = getIndexOfNearestSpace(text, length - (columns - half) + 1, true);
  61. return sliceAnsi(text, 0, spaceNearFirstBreakPoint) + truncationCharacter + sliceAnsi(text, spaceNearSecondBreakPoint, length).trim();
  62. }
  63. return (
  64. sliceAnsi(text, 0, half)
  65. + truncationCharacter
  66. + sliceAnsi(text, length - (columns - half) + stringWidth(truncationCharacter), length)
  67. );
  68. }
  69. if (position === 'end') {
  70. if (preferTruncationOnSpace) {
  71. const nearestSpace = getIndexOfNearestSpace(text, columns - 1);
  72. return sliceAnsi(text, 0, nearestSpace) + truncationCharacter;
  73. }
  74. if (space === true) {
  75. truncationCharacter = ` ${truncationCharacter}`;
  76. }
  77. return sliceAnsi(text, 0, columns - stringWidth(truncationCharacter)) + truncationCharacter;
  78. }
  79. throw new Error(`Expected \`options.position\` to be either \`start\`, \`middle\` or \`end\`, got ${position}`);
  80. }