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  };