/** * This class manages a set of numeric spans (2-element arrays marking begin and end * points). The method of this class coalesce and split spans as necessary to store * the fewest possible pairs needed to represent the covered (one-dimensional) area. * * @private * @since 6.5.0 */Ext.define('Ext.util.Spans', { isSpans: true, constructor: function() { this.spans = this.spans || []; }, /** * Clears all spans. * @return {Ext.util.Spans} This Spans object. */ clear: function() { this.spans.length = 0; return this; }, /** * Adds a new span to the current set of spans. This will coalesce adjacent spans * as necessary to store the minimum number of spans possible. * * @param {Number/Number[]} begin Either the beginning of the span or a 2-element * array of `[begin,end]`. * @param {Number} [end] If `begin` is just the position, the second argument is * the end of the span to add. This value is exclusive of the span, that is it * marks the first position beyond the span. This ensures that `end - begin` is * the length of the span. * @return {Boolean} `true` if the new span changes this object, `false` if the * span was already in the set. */ add: function(begin, end) { if (end === undefined) { if (typeof begin === 'number') { end = begin + 1; } else { end = begin[1]; begin = begin[0]; } } // eslint-disable-next-line vars-on-top var me = this, spans = me.spans, b, e, first, last, span; first = me.bisect(begin); if (first) { // if (there is a previous span) span = spans[first - 1]; b = span[0]; e = span[1]; if (begin <= e) { // This new span touches the previous one, but perhaps this new // span is contained inside the previous span... if (end <= e) { return false; // no change } // The new begin touches the previous span, but the new end goes // beyond it, so we extend it by replacing it. begin = b; spans.splice(--first, 1); } } // Now there is either no previous span, or if there was one, it does // not touch the new one. last = me.bisect(end); if (last > first) { // If we are replacing any spans, make sure the new "end" is at // least as large as the end of the last span we are replacing. span = spans[last - 1]; end = Math.max(end, span[1]); } if (last < spans.length) { span = spans[last]; // The span beyond our new span may be touching the end of the // new span, in which case we need to coalesce there as well. // Since we are removing it, we need to expand "end" to include // this additional span. if (end === span[0]) { end = span[1]; ++last; } } spans.splice(first, last - first, [begin, end]); return true; }, /** * Returns `true` if the given span is fully in the current set of spans. * @param {Number/Number[]} begin Either the beginning of the span or a 2-element * array of `[begin,end]`. * @param {Number} [end] If `begin` is just the position, the second argument is * the end of the span to add. This value is exclusive of the span, that is it * marks the first position beyond the span. This ensures that `end - begin` is * the length of the span. * @return {Boolean} */ contains: function(begin, end) { if (end === undefined) { if (typeof begin === 'number') { end = begin + 1; } else { end = begin[1]; begin = begin[0]; } } // eslint-disable-next-line vars-on-top var spans = this.spans, index = this.bisect(begin), ret = false, e, span; if (index && begin < (e = spans[index - 1][1])) { ret = end <= e; } else if (index < spans.length) { span = spans[index]; ret = span[0] <= begin && end <= span[1]; } return ret; }, /** * Calls the passed function for every integer in every span. * @param {Function} fn The function to call. Returning `false` will abort the operation. * @param {Mixed} scope The scope (`this` reference) in which the function will execute. */ each: function(fn, scope) { var spans = this.spans, len = spans.length, i, span, j; for (i = 0; i < len; i++) { span = spans[i]; for (j = span[0]; j < span[1]; j++) { if (fn.call(scope || this, i) === false) { return; } } } }, /** * Returns `true` if the specified span intersects with the current set of spans. * * @param {Number/Number[]} begin Either the beginning of the span or a 2-element * array of `[begin,end]`. * @param {Number} [end] If `begin` is just the position, the second argument is * the end of the span to add. This value is exclusive of the span, that is it * marks the first position beyond the span. This ensures that `end - begin` is * the length of the span. * @return {Boolean} */ intersects: function(begin, end) { if (end === undefined) { if (typeof begin === 'number') { end = begin + 1; } else { end = begin[1]; begin = begin[0]; } } // eslint-disable-next-line vars-on-top var spans = this.spans, index = this.bisect(begin), ret = false; if (index && begin < spans[index - 1][1]) { ret = true; } else if (index < spans.length) { ret = spans[index][0] < end; } return ret; }, /** * Removes a span from the current set of spans. This will coalesce adjacent spans * as necessary to store the minimum number of spans possible. * * @param {Number/Number[]} begin Either the beginning of the span or a 2-element * array of `[begin,end]`. * @param {Number} [end] If `begin` is just the position, the second argument is * the end of the span to add. This value is exclusive of the span, that is it * marks the first position beyond the span. This ensures that `end - begin` is * the length of the span. * @return {Boolean} `true` if removing the span changes this object, `false` if the * span was not in the set. */ remove: function(begin, end) { if (end === undefined) { if (typeof begin === 'number') { end = begin + 1; } else { end = begin[1]; begin = begin[0]; } } // eslint-disable-next-line vars-on-top var me = this, spans = me.spans, first = me.bisect(begin), ret = false, last, span, tmp; if (first) { span = spans[first - 1]; tmp = span[1]; if (begin < tmp) { span[1] = begin; if (end < tmp) { spans.splice(first, 0, [end, tmp]); return true; } ret = true; } } last = me.bisect(end); if (first < last) { ret = true; span = spans[last - 1]; if (end < span[1]) { span[0] = end; --last; } last -= first; if (last) { spans.splice(first, last); } } return ret; }, /** * Returns an object that holds the current state and can be passed back later * to `unstash` to restore that state. * @return {Object} */ stash: function() { return this.spans.slice(); }, /** * Takes an object a state object returned by `stash` and makes that the current * state. * @return {Ext.util.Spans} This Spans object. */ unstash: function(pickle) { this.spans = pickle; return this; }, /** * @return {Number} the number of integer locations covered by all the spans. */ getCount: function() { var spans = this.spans, len = spans.length, result = 0, i, span; for (i = 0; i < len; i++) { span = spans[i]; result += span[1] - span[0]; } return result; }, privates: { bisect: function(value) { return Ext.Number.bisectTuples(this.spans, value, 0); } }});