141 lines
3.1 KiB
JavaScript
141 lines
3.1 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
var reAnsi = require('ansi-regex')
|
||
|
, stringifiable = require('es5-ext/object/validate-stringifiable-value')
|
||
|
, length = require('./get-stripped-length')
|
||
|
, sgr = require('./lib/sgr')
|
||
|
|
||
|
, max = Math.max;
|
||
|
|
||
|
var Token = function Token(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;
|
||
|
};
|
||
|
|
||
|
var sliceSeq = function (seq, begin, end) {
|
||
|
var sliced = seq.reduce(function (state, chunk) {
|
||
|
var index = state.index;
|
||
|
|
||
|
if (!(chunk instanceof Token)) {
|
||
|
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;
|
||
|
} else {
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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('');
|
||
|
};
|