cli-color
Version:
Colors, formatting and other tools for the console
144 lines (114 loc) • 3.33 kB
JavaScript
/* eslint max-lines: "off" */
;
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("");
};