Path: blob/main/src/vs/base/browser/ui/list/rangeMap.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IRange, Range } from '../../../common/range.js';67export interface IItem {8size: number;9}1011export interface IRangedGroup {12range: IRange;13size: number;14}1516/**17* Returns the intersection between a ranged group and a range.18* Returns `[]` if the intersection is empty.19*/20export function groupIntersect(range: IRange, groups: IRangedGroup[]): IRangedGroup[] {21const result: IRangedGroup[] = [];2223for (const r of groups) {24if (range.start >= r.range.end) {25continue;26}2728if (range.end < r.range.start) {29break;30}3132const intersection = Range.intersect(range, r.range);3334if (Range.isEmpty(intersection)) {35continue;36}3738result.push({39range: intersection,40size: r.size41});42}4344return result;45}4647/**48* Shifts a range by that `much`.49*/50export function shift({ start, end }: IRange, much: number): IRange {51return { start: start + much, end: end + much };52}5354/**55* Consolidates a collection of ranged groups.56*57* Consolidation is the process of merging consecutive ranged groups58* that share the same `size`.59*/60export function consolidate(groups: IRangedGroup[]): IRangedGroup[] {61const result: IRangedGroup[] = [];62let previousGroup: IRangedGroup | null = null;6364for (const group of groups) {65const start = group.range.start;66const end = group.range.end;67const size = group.size;6869if (previousGroup && size === previousGroup.size) {70previousGroup.range.end = end;71continue;72}7374previousGroup = { range: { start, end }, size };75result.push(previousGroup);76}7778return result;79}8081/**82* Concatenates several collections of ranged groups into a single83* collection.84*/85function concat(...groups: IRangedGroup[][]): IRangedGroup[] {86return consolidate(groups.reduce((r, g) => r.concat(g), []));87}8889export interface IRangeMap {90readonly size: number;91readonly count: number;92paddingTop: number;93splice(index: number, deleteCount: number, items?: IItem[]): void;94indexAt(position: number): number;95indexAfter(position: number): number;96positionAt(index: number): number;97}9899export class RangeMap implements IRangeMap {100101private groups: IRangedGroup[] = [];102private _size = 0;103private _paddingTop = 0;104105get paddingTop() {106return this._paddingTop;107}108109set paddingTop(paddingTop: number) {110this._size = this._size + paddingTop - this._paddingTop;111this._paddingTop = paddingTop;112}113114constructor(topPadding?: number) {115this._paddingTop = topPadding ?? 0;116this._size = this._paddingTop;117}118119splice(index: number, deleteCount: number, items: IItem[] = []): void {120const diff = items.length - deleteCount;121const before = groupIntersect({ start: 0, end: index }, this.groups);122const after = groupIntersect({ start: index + deleteCount, end: Number.POSITIVE_INFINITY }, this.groups)123.map<IRangedGroup>(g => ({ range: shift(g.range, diff), size: g.size }));124125const middle = items.map<IRangedGroup>((item, i) => ({126range: { start: index + i, end: index + i + 1 },127size: item.size128}));129130this.groups = concat(before, middle, after);131this._size = this._paddingTop + this.groups.reduce((t, g) => t + (g.size * (g.range.end - g.range.start)), 0);132}133134/**135* Returns the number of items in the range map.136*/137get count(): number {138const len = this.groups.length;139140if (!len) {141return 0;142}143144return this.groups[len - 1].range.end;145}146147/**148* Returns the sum of the sizes of all items in the range map.149*/150get size(): number {151return this._size;152}153154/**155* Returns the index of the item at the given position.156*/157indexAt(position: number): number {158if (position < 0) {159return -1;160}161162if (position < this._paddingTop) {163return 0;164}165166let index = 0;167let size = this._paddingTop;168169for (const group of this.groups) {170const count = group.range.end - group.range.start;171const newSize = size + (count * group.size);172173if (position < newSize) {174return index + Math.floor((position - size) / group.size);175}176177index += count;178size = newSize;179}180181return index;182}183184/**185* Returns the index of the item right after the item at the186* index of the given position.187*/188indexAfter(position: number): number {189return Math.min(this.indexAt(position) + 1, this.count);190}191192/**193* Returns the start position of the item at the given index.194*/195positionAt(index: number): number {196if (index < 0) {197return -1;198}199200let position = 0;201let count = 0;202203for (const group of this.groups) {204const groupCount = group.range.end - group.range.start;205const newCount = count + groupCount;206207if (index < newCount) {208return this._paddingTop + position + ((index - count) * group.size);209}210211position += groupCount * group.size;212count = newCount;213}214215return -1;216}217}218219220