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