index.js 2.6 KB

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