diff --git a/src/vs/base/parts/list/rangeMap.ts b/src/vs/base/parts/list/rangeMap.ts index 53c47511463a883e1728db65edaf02865922969d..24b81cf812dd919675599207ab82799ac70aabea 100644 --- a/src/vs/base/parts/list/rangeMap.ts +++ b/src/vs/base/parts/list/rangeMap.ts @@ -18,6 +18,10 @@ export interface IRangedGroup { size: number; } +/** + * Returns the intersection between two ranges as a range itself. + * Returns `null` if the intersection is empty. + */ export function intersect(one: IRange, other: IRange): IRange { if (one.start >= other.end || other.start >= one.end) { return null; @@ -33,6 +37,10 @@ export function intersect(one: IRange, other: IRange): IRange { return { start, end }; } +/** + * Returns the intersection between a ranged group and a range. + * Returns `[]` if the intersection is empty. + */ export function groupIntersect(range: IRange, groups: IRangedGroup[]): IRangedGroup[] { const result: IRangedGroup[] = []; @@ -60,14 +68,48 @@ export function groupIntersect(range: IRange, groups: IRangedGroup[]): IRangedGr return result; } +/** + * Shifts a range by that `much`. + */ function shift({ start, end }: IRange, much: number): IRange { return { start: start + much, end: end + much }; } +/** + * Concatenates several collections of ranged groups into a single + * collection. + */ function concat(...groups: IRangedGroup[][]): IRangedGroup[] { return groups.reduce((r, g) => r.concat(g), [] as IRangedGroup[]); } +/** + * Consolidates a collection of ranged groups. + * + * Consolidation is the process of merging consecutive ranged groups + * that share the same `size`. + */ +export function consolidate(groups: IRangedGroup[]): IRangedGroup[] { + const result: IRangedGroup[] = []; + let previousGroup: IRangedGroup = null; + + for (const group of groups) { + const start = group.range.start; + const end = group.range.end; + const size = group.size; + + if (previousGroup && size === previousGroup.size) { + previousGroup.range.end = end; + continue; + } + + previousGroup = { range: { start, end }, size }; + result.push(previousGroup); + } + + return result; +} + export class RangeMap { private groups: IRangedGroup[] = []; @@ -93,7 +135,7 @@ export class RangeMap { return result; }); - this.groups = concat(before, middle, after); + this.groups = consolidate(concat(before, middle, after)); } get count(): number { @@ -110,41 +152,42 @@ export class RangeMap { return this.groups.reduce((t, g) => t + (g.size * (g.range.end - g.range.start)), 0); } - // indexAt(position: number): number { - // let index = 0; - // let size = 0; + indexAt(position: number): number { + let index = 0; + let size = 0; - // for (const range of this._ranges) { - // const newSize = size + (range.count * range.size); + for (const group of this.groups) { + const newSize = size + ((group.range.end - group.range.start) * group.size); - // if (position < newSize) { - // return index + Math.floor((position - size) / range.size); - // } + if (position < newSize) { + return index + Math.floor((position - size) / group.size); + } - // index += range.size; - // size = newSize; - // } + index += group.size; + size = newSize; + } - // return -1; - // } + return -1; + } - // positionAt(index: number): number { - // let position = 0; - // let count = 0; + positionAt(index: number): number { + let position = 0; + let count = 0; - // for (const range of this._ranges) { - // const newCount = count + range.count; + for (const group of this.groups) { + const groupCount = group.range.end - group.range.start; + const newCount = count + groupCount; - // if (index < newCount) { - // return position + ((index - count) * range.size); - // } + if (index < newCount) { + return position + ((index - count) * group.size); + } - // position += range.count * range.size; - // count = newCount; - // } + position += groupCount * group.size; + count = newCount; + } - // return -1; - // } + return -1; + } dispose() { this.groups = null; diff --git a/src/vs/base/parts/list/test/rangeMap.test.ts b/src/vs/base/parts/list/test/rangeMap.test.ts index 22f19e7fb3648d16790c70f53040dfeb2c96ff44..7b3b9b045818748b4f67347e61eaf901cb8e91c2 100644 --- a/src/vs/base/parts/list/test/rangeMap.test.ts +++ b/src/vs/base/parts/list/test/rangeMap.test.ts @@ -4,7 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import assert = require('assert'); -import { RangeMap, intersect, groupIntersect, IRangedGroup } from '../rangeMap'; +import { + RangeMap, + intersect, + groupIntersect, + IRangedGroup, + consolidate +} from '../rangeMap'; suite('RangeMap', () => { var rangeMap: RangeMap; @@ -92,11 +98,56 @@ suite('RangeMap', () => { ); }); - // test('splice', () => { - // rangeMap.splice(0, 0, { count: 5, size: 1 }); - // rangeMap.splice(2, 0, { count: 5, size: 2 }); - // console.log(rangeMap.groups); - // }); + test('consolidate', () => { + assert.deepEqual(consolidate([]), []); + + assert.deepEqual( + consolidate([{ range: { start: 0, end: 10 }, size: 1 }]), + [{ range: { start: 0, end: 10 }, size: 1 }] + ); + + assert.deepEqual( + consolidate([ + { range: { start: 0, end: 10 }, size: 1 }, + { range: { start: 10, end: 20 }, size: 1 } + ]), + [{ range: { start: 0, end: 20 }, size: 1 }] + ); + + assert.deepEqual( + consolidate([ + { range: { start: 0, end: 10 }, size: 1 }, + { range: { start: 10, end: 20 }, size: 1 }, + { range: { start: 20, end: 100 }, size: 1 } + ]), + [{ range: { start: 0, end: 100 }, size: 1 }] + ); + + assert.deepEqual( + consolidate([ + { range: { start: 0, end: 10 }, size: 1 }, + { range: { start: 10, end: 20 }, size: 5 }, + { range: { start: 20, end: 30 }, size: 10 } + ]), + [ + { range: { start: 0, end: 10 }, size: 1 }, + { range: { start: 10, end: 20 }, size: 5 }, + { range: { start: 20, end: 30 }, size: 10 } + ] + ); + + assert.deepEqual( + consolidate([ + { range: { start: 0, end: 10 }, size: 1 }, + { range: { start: 10, end: 20 }, size: 2 }, + { range: { start: 20, end: 100 }, size: 2 } + ]), + [ + { range: { start: 0, end: 10 }, size: 1 }, + { range: { start: 10, end: 100 }, size: 2 } + ] + ); + }); test('empty', () => { assert.equal(rangeMap.size, 0);