You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 2.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import isFullwidthCodePoint from 'is-fullwidth-code-point';
  2. import ansiStyles from 'ansi-styles';
  3. const astralRegex = /^[\uD800-\uDBFF][\uDC00-\uDFFF]$/;
  4. const ESCAPES = [
  5. '\u001B',
  6. '\u009B'
  7. ];
  8. const wrapAnsi = code => `${ESCAPES[0]}[${code}m`;
  9. const checkAnsi = (ansiCodes, isEscapes, endAnsiCode) => {
  10. let output = [];
  11. ansiCodes = [...ansiCodes];
  12. for (let ansiCode of ansiCodes) {
  13. const ansiCodeOrigin = ansiCode;
  14. if (ansiCode.includes(';')) {
  15. ansiCode = ansiCode.split(';')[0][0] + '0';
  16. }
  17. const item = ansiStyles.codes.get(Number.parseInt(ansiCode, 10));
  18. if (item) {
  19. const indexEscape = ansiCodes.indexOf(item.toString());
  20. if (indexEscape === -1) {
  21. output.push(wrapAnsi(isEscapes ? item : ansiCodeOrigin));
  22. } else {
  23. ansiCodes.splice(indexEscape, 1);
  24. }
  25. } else if (isEscapes) {
  26. output.push(wrapAnsi(0));
  27. break;
  28. } else {
  29. output.push(wrapAnsi(ansiCodeOrigin));
  30. }
  31. }
  32. if (isEscapes) {
  33. output = output.filter((element, index) => output.indexOf(element) === index);
  34. if (endAnsiCode !== undefined) {
  35. const fistEscapeCode = wrapAnsi(ansiStyles.codes.get(Number.parseInt(endAnsiCode, 10)));
  36. // TODO: Remove the use of `.reduce` here.
  37. // eslint-disable-next-line unicorn/no-array-reduce
  38. output = output.reduce((current, next) => next === fistEscapeCode ? [next, ...current] : [...current, next], []);
  39. }
  40. }
  41. return output.join('');
  42. };
  43. export default function sliceAnsi(string, begin, end) {
  44. const characters = [...string];
  45. const ansiCodes = [];
  46. let stringEnd = typeof end === 'number' ? end : characters.length;
  47. let isInsideEscape = false;
  48. let ansiCode;
  49. let visible = 0;
  50. let output = '';
  51. for (const [index, character] of characters.entries()) {
  52. let leftEscape = false;
  53. if (ESCAPES.includes(character)) {
  54. const code = /\d[^m]*/.exec(string.slice(index, index + 18));
  55. ansiCode = code && code.length > 0 ? code[0] : undefined;
  56. if (visible < stringEnd) {
  57. isInsideEscape = true;
  58. if (ansiCode !== undefined) {
  59. ansiCodes.push(ansiCode);
  60. }
  61. }
  62. } else if (isInsideEscape && character === 'm') {
  63. isInsideEscape = false;
  64. leftEscape = true;
  65. }
  66. if (!isInsideEscape && !leftEscape) {
  67. visible++;
  68. }
  69. if (!astralRegex.test(character) && isFullwidthCodePoint(character.codePointAt())) {
  70. visible++;
  71. if (typeof end !== 'number') {
  72. stringEnd++;
  73. }
  74. }
  75. if (visible > begin && visible <= stringEnd) {
  76. output += character;
  77. } else if (visible === begin && !isInsideEscape && ansiCode !== undefined) {
  78. output = checkAnsi(ansiCodes);
  79. } else if (visible >= stringEnd) {
  80. output += checkAnsi(ansiCodes, true, ansiCode);
  81. break;
  82. }
  83. }
  84. return output;
  85. }