index.js 2.5 KB

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