index.js 2.5 KB

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