/* eslint max-lines: "off" */ "use strict"; var reAnsi = require("./regex-ansi") , stringifiable = require("es5-ext/object/validate-stringifiable-value") , length = require("./get-stripped-length") , sgr = require("./lib/sgr") , max = Math.max; var Token = function (token) { this.token = token; }; var tokenize = function (str) { var match = reAnsi().exec(str); if (!match) { return [str]; } var index = match.index, head, prehead, tail; if (index === 0) { head = match[0]; tail = str.slice(head.length); return [new Token(head)].concat(tokenize(tail)); } prehead = str.slice(0, index); head = match[0]; tail = str.slice(index + head.length); return [prehead, new Token(head)].concat(tokenize(tail)); }; var isChunkInSlice = function (chunk, index, begin, end) { var endIndex = chunk.length + index; if (begin > endIndex) return false; if (end < index) return false; return true; }; // eslint-disable-next-line max-lines-per-function var sliceSeq = function (seq, begin, end) { var sliced = seq.reduce( function (state, chunk) { var index = state.index; if (chunk instanceof Token) { var code = sgr.extractCode(chunk.token); if (index <= begin) { if (code in sgr.openers) { sgr.openStyle(state.preOpeners, code); } if (code in sgr.closers) { sgr.closeStyle(state.preOpeners, code); } } else if (index < end) { if (code in sgr.openers) { sgr.openStyle(state.inOpeners, code); state.seq.push(chunk); } else if (code in sgr.closers) { state.inClosers.push(code); state.seq.push(chunk); } } } else { var nextChunk = ""; if (isChunkInSlice(chunk, index, begin, end)) { var relBegin = Math.max(begin - index, 0) , relEnd = Math.min(end - index, chunk.length); nextChunk = chunk.slice(relBegin, relEnd); } state.seq.push(nextChunk); state.index = index + chunk.length; } return state; }, { index: 0, seq: [], // preOpeners -> [ mod ] // preOpeners must be prepended to the slice if they wasn't closed til the end of it // preOpeners must be closed if they wasn't closed til the end of the slice preOpeners: [], // inOpeners -> [ mod ] // inOpeners already in the slice and must not be prepended to the slice // inOpeners must be closed if they wasn't closed til the end of the slice inOpeners: [], // opener CSI inside slice // inClosers -> [ code ] // closer CSIs for determining which pre/in-Openers must be closed inClosers: [] } ); sliced.seq = [].concat( sgr.prepend(sliced.preOpeners), sliced.seq, sgr.complete([].concat(sliced.preOpeners, sliced.inOpeners), sliced.inClosers) ); return sliced.seq; }; module.exports = function (str /*, begin, end*/) { var seq, begin = Number(arguments[1]), end = Number(arguments[2]), len; str = stringifiable(str); len = length(str); if (isNaN(begin)) { begin = 0; } if (isNaN(end)) { end = len; } if (begin < 0) { begin = max(len + begin, 0); } if (end < 0) { end = max(len + end, 0); } seq = tokenize(str); seq = sliceSeq(seq, begin, end); return seq .map(function (chunk) { if (chunk instanceof Token) { return chunk.token; } return chunk; }) .join(""); };