Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/ui/list/rangeMap.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { IRange, Range } from '../../../common/range.js';
7
8
export interface IItem {
9
size: number;
10
}
11
12
export interface IRangedGroup {
13
range: IRange;
14
size: number;
15
}
16
17
/**
18
* Returns the intersection between a ranged group and a range.
19
* Returns `[]` if the intersection is empty.
20
*/
21
export function groupIntersect(range: IRange, groups: IRangedGroup[]): IRangedGroup[] {
22
const result: IRangedGroup[] = [];
23
24
for (const r of groups) {
25
if (range.start >= r.range.end) {
26
continue;
27
}
28
29
if (range.end < r.range.start) {
30
break;
31
}
32
33
const intersection = Range.intersect(range, r.range);
34
35
if (Range.isEmpty(intersection)) {
36
continue;
37
}
38
39
result.push({
40
range: intersection,
41
size: r.size
42
});
43
}
44
45
return result;
46
}
47
48
/**
49
* Shifts a range by that `much`.
50
*/
51
export function shift({ start, end }: IRange, much: number): IRange {
52
return { start: start + much, end: end + much };
53
}
54
55
/**
56
* Consolidates a collection of ranged groups.
57
*
58
* Consolidation is the process of merging consecutive ranged groups
59
* that share the same `size`.
60
*/
61
export function consolidate(groups: IRangedGroup[]): IRangedGroup[] {
62
const result: IRangedGroup[] = [];
63
let previousGroup: IRangedGroup | null = null;
64
65
for (const group of groups) {
66
const start = group.range.start;
67
const end = group.range.end;
68
const size = group.size;
69
70
if (previousGroup && size === previousGroup.size) {
71
previousGroup.range.end = end;
72
continue;
73
}
74
75
previousGroup = { range: { start, end }, size };
76
result.push(previousGroup);
77
}
78
79
return result;
80
}
81
82
/**
83
* Concatenates several collections of ranged groups into a single
84
* collection.
85
*/
86
function concat(...groups: IRangedGroup[][]): IRangedGroup[] {
87
return consolidate(groups.reduce((r, g) => r.concat(g), []));
88
}
89
90
export interface IRangeMap {
91
readonly size: number;
92
readonly count: number;
93
paddingTop: number;
94
splice(index: number, deleteCount: number, items?: IItem[]): void;
95
indexAt(position: number): number;
96
indexAfter(position: number): number;
97
positionAt(index: number): number;
98
}
99
100
export class RangeMap implements IRangeMap {
101
102
private groups: IRangedGroup[] = [];
103
private _size = 0;
104
private _paddingTop = 0;
105
106
get paddingTop() {
107
return this._paddingTop;
108
}
109
110
set paddingTop(paddingTop: number) {
111
this._size = this._size + paddingTop - this._paddingTop;
112
this._paddingTop = paddingTop;
113
}
114
115
constructor(topPadding?: number) {
116
this._paddingTop = topPadding ?? 0;
117
this._size = this._paddingTop;
118
}
119
120
splice(index: number, deleteCount: number, items: IItem[] = []): void {
121
const diff = items.length - deleteCount;
122
const before = groupIntersect({ start: 0, end: index }, this.groups);
123
const after = groupIntersect({ start: index + deleteCount, end: Number.POSITIVE_INFINITY }, this.groups)
124
.map<IRangedGroup>(g => ({ range: shift(g.range, diff), size: g.size }));
125
126
const middle = items.map<IRangedGroup>((item, i) => ({
127
range: { start: index + i, end: index + i + 1 },
128
size: item.size
129
}));
130
131
this.groups = concat(before, middle, after);
132
this._size = this._paddingTop + this.groups.reduce((t, g) => t + (g.size * (g.range.end - g.range.start)), 0);
133
}
134
135
/**
136
* Returns the number of items in the range map.
137
*/
138
get count(): number {
139
const len = this.groups.length;
140
141
if (!len) {
142
return 0;
143
}
144
145
return this.groups[len - 1].range.end;
146
}
147
148
/**
149
* Returns the sum of the sizes of all items in the range map.
150
*/
151
get size(): number {
152
return this._size;
153
}
154
155
/**
156
* Returns the index of the item at the given position.
157
*/
158
indexAt(position: number): number {
159
if (position < 0) {
160
return -1;
161
}
162
163
if (position < this._paddingTop) {
164
return 0;
165
}
166
167
let index = 0;
168
let size = this._paddingTop;
169
170
for (const group of this.groups) {
171
const count = group.range.end - group.range.start;
172
const newSize = size + (count * group.size);
173
174
if (position < newSize) {
175
return index + Math.floor((position - size) / group.size);
176
}
177
178
index += count;
179
size = newSize;
180
}
181
182
return index;
183
}
184
185
/**
186
* Returns the index of the item right after the item at the
187
* index of the given position.
188
*/
189
indexAfter(position: number): number {
190
return Math.min(this.indexAt(position) + 1, this.count);
191
}
192
193
/**
194
* Returns the start position of the item at the given index.
195
*/
196
positionAt(index: number): number {
197
if (index < 0) {
198
return -1;
199
}
200
201
let position = 0;
202
let count = 0;
203
204
for (const group of this.groups) {
205
const groupCount = group.range.end - group.range.start;
206
const newCount = count + groupCount;
207
208
if (index < newCount) {
209
return this._paddingTop + position + ((index - count) * group.size);
210
}
211
212
position += groupCount * group.size;
213
count = newCount;
214
}
215
216
return -1;
217
}
218
}
219
220