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.

59 lines
1.3 KiB

4 years ago
  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 END_CODE = 39;
  10. const wrapAnsi = code => `${ESCAPES[0]}[${code}m`;
  11. module.exports = (str, begin, end) => {
  12. const arr = [...str.normalize()];
  13. end = typeof end === 'number' ? end : arr.length;
  14. let insideEscape = false;
  15. let escapeCode = null;
  16. let visible = 0;
  17. let output = '';
  18. for (const [i, x] of arr.entries()) {
  19. let leftEscape = false;
  20. if (ESCAPES.includes(x)) {
  21. insideEscape = true;
  22. const code = /\d[^m]*/.exec(str.slice(i, i + 18));
  23. escapeCode = code === END_CODE ? null : code;
  24. } else if (insideEscape && x === 'm') {
  25. insideEscape = false;
  26. leftEscape = true;
  27. }
  28. if (!insideEscape && !leftEscape) {
  29. ++visible;
  30. }
  31. if (!astralRegex({exact: true}).test(x) && isFullwidthCodePoint(x.codePointAt())) {
  32. ++visible;
  33. }
  34. if (visible > begin && visible <= end) {
  35. output += x;
  36. } else if (visible === begin && !insideEscape && escapeCode !== null && escapeCode !== END_CODE) {
  37. output += wrapAnsi(escapeCode);
  38. } else if (visible >= end) {
  39. if (escapeCode !== null) {
  40. output += wrapAnsi(ansiStyles.codes.get(parseInt(escapeCode, 10)) || END_CODE);
  41. }
  42. break;
  43. }
  44. }
  45. return output;
  46. };