Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/fixtures/tests/for-method-issue-3699/foldingRanges.ts
13405 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
export interface ILineRange {
7
startLineNumber: number;
8
endLineNumber: number;
9
}
10
11
export const enum FoldSource {
12
provider = 0,
13
userDefined = 1,
14
recovered = 2
15
}
16
17
export const foldSourceAbbr = {
18
[FoldSource.provider]: ' ',
19
[FoldSource.userDefined]: 'u',
20
[FoldSource.recovered]: 'r',
21
};
22
23
export interface FoldRange {
24
startLineNumber: number;
25
endLineNumber: number;
26
type: string | undefined;
27
isCollapsed: boolean;
28
source: FoldSource;
29
}
30
31
export const MAX_FOLDING_REGIONS = 0xFFFF;
32
export const MAX_LINE_NUMBER = 0xFFFFFF;
33
34
const MASK_INDENT = 0xFF000000;
35
36
class BitField {
37
private readonly _states: Uint32Array;
38
constructor(size: number) {
39
const numWords = Math.ceil(size / 32);
40
this._states = new Uint32Array(numWords);
41
}
42
43
public get(index: number): boolean {
44
const arrayIndex = (index / 32) | 0;
45
const bit = index % 32;
46
return (this._states[arrayIndex] & (1 << bit)) !== 0;
47
}
48
49
public set(index: number, newState: boolean) {
50
const arrayIndex = (index / 32) | 0;
51
const bit = index % 32;
52
const value = this._states[arrayIndex];
53
if (newState) {
54
this._states[arrayIndex] = value | (1 << bit);
55
} else {
56
this._states[arrayIndex] = value & ~(1 << bit);
57
}
58
}
59
}
60
61
export class FoldingRegions {
62
private readonly _startIndexes: Uint32Array;
63
private readonly _endIndexes: Uint32Array;
64
private readonly _collapseStates: BitField;
65
private readonly _userDefinedStates: BitField;
66
private readonly _recoveredStates: BitField;
67
68
private _parentsComputed: boolean;
69
private readonly _types: Array<string | undefined> | undefined;
70
71
constructor(startIndexes: Uint32Array, endIndexes: Uint32Array, types?: Array<string | undefined>) {
72
if (startIndexes.length !== endIndexes.length || startIndexes.length > MAX_FOLDING_REGIONS) {
73
throw new Error('invalid startIndexes or endIndexes size');
74
}
75
this._startIndexes = startIndexes;
76
this._endIndexes = endIndexes;
77
this._collapseStates = new BitField(startIndexes.length);
78
this._userDefinedStates = new BitField(startIndexes.length);
79
this._recoveredStates = new BitField(startIndexes.length);
80
this._types = types;
81
this._parentsComputed = false;
82
}
83
84
private ensureParentIndices() {
85
if (!this._parentsComputed) {
86
this._parentsComputed = true;
87
const parentIndexes: number[] = [];
88
const isInsideLast = (startLineNumber: number, endLineNumber: number) => {
89
const index = parentIndexes[parentIndexes.length - 1];
90
return this.getStartLineNumber(index) <= startLineNumber && this.getEndLineNumber(index) >= endLineNumber;
91
};
92
for (let i = 0, len = this._startIndexes.length; i < len; i++) {
93
const startLineNumber = this._startIndexes[i];
94
const endLineNumber = this._endIndexes[i];
95
if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) {
96
throw new Error('startLineNumber or endLineNumber must not exceed ' + MAX_LINE_NUMBER);
97
}
98
while (parentIndexes.length > 0 && !isInsideLast(startLineNumber, endLineNumber)) {
99
parentIndexes.pop();
100
}
101
const parentIndex = parentIndexes.length > 0 ? parentIndexes[parentIndexes.length - 1] : -1;
102
parentIndexes.push(i);
103
this._startIndexes[i] = startLineNumber + ((parentIndex & 0xFF) << 24);
104
this._endIndexes[i] = endLineNumber + ((parentIndex & 0xFF00) << 16);
105
}
106
}
107
}
108
109
public get length(): number {
110
return this._startIndexes.length;
111
}
112
113
public getStartLineNumber(index: number): number {
114
return this._startIndexes[index] & MAX_LINE_NUMBER;
115
}
116
117
public getEndLineNumber(index: number): number {
118
return this._endIndexes[index] & MAX_LINE_NUMBER;
119
}
120
121
public getType(index: number): string | undefined {
122
return this._types ? this._types[index] : undefined;
123
}
124
125
public hasTypes() {
126
return !!this._types;
127
}
128
129
public isCollapsed(index: number): boolean {
130
return this._collapseStates.get(index);
131
}
132
133
public setCollapsed(index: number, newState: boolean) {
134
this._collapseStates.set(index, newState);
135
}
136
137
private isUserDefined(index: number): boolean {
138
return this._userDefinedStates.get(index);
139
}
140
141
private setUserDefined(index: number, newState: boolean) {
142
return this._userDefinedStates.set(index, newState);
143
}
144
145
private isRecovered(index: number): boolean {
146
return this._recoveredStates.get(index);
147
}
148
149
private setRecovered(index: number, newState: boolean) {
150
return this._recoveredStates.set(index, newState);
151
}
152
153
public getSource(index: number): FoldSource {
154
if (this.isUserDefined(index)) {
155
return FoldSource.userDefined;
156
} else if (this.isRecovered(index)) {
157
return FoldSource.recovered;
158
}
159
return FoldSource.provider;
160
}
161
162
public setSource(index: number, source: FoldSource): void {
163
if (source === FoldSource.userDefined) {
164
this.setUserDefined(index, true);
165
this.setRecovered(index, false);
166
} else if (source === FoldSource.recovered) {
167
this.setUserDefined(index, false);
168
this.setRecovered(index, true);
169
} else {
170
this.setUserDefined(index, false);
171
this.setRecovered(index, false);
172
}
173
}
174
175
public setCollapsedAllOfType(type: string, newState: boolean) {
176
let hasChanged = false;
177
if (this._types) {
178
for (let i = 0; i < this._types.length; i++) {
179
if (this._types[i] === type) {
180
this.setCollapsed(i, newState);
181
hasChanged = true;
182
}
183
}
184
}
185
return hasChanged;
186
}
187
188
public toRegion(index: number): FoldingRegion {
189
return new FoldingRegion(this, index);
190
}
191
192
public getParentIndex(index: number) {
193
this.ensureParentIndices();
194
const parent = ((this._startIndexes[index] & MASK_INDENT) >>> 24) + ((this._endIndexes[index] & MASK_INDENT) >>> 16);
195
if (parent === MAX_FOLDING_REGIONS) {
196
return -1;
197
}
198
return parent;
199
}
200
201
public contains(index: number, line: number) {
202
return this.getStartLineNumber(index) <= line && this.getEndLineNumber(index) >= line;
203
}
204
205
private findIndex(line: number) {
206
let low = 0, high = this._startIndexes.length;
207
if (high === 0) {
208
return -1; // no children
209
}
210
while (low < high) {
211
const mid = Math.floor((low + high) / 2);
212
if (line < this.getStartLineNumber(mid)) {
213
high = mid;
214
} else {
215
low = mid + 1;
216
}
217
}
218
return low - 1;
219
}
220
221
public findRange(line: number): number {
222
let index = this.findIndex(line);
223
if (index >= 0) {
224
const endLineNumber = this.getEndLineNumber(index);
225
if (endLineNumber >= line) {
226
return index;
227
}
228
index = this.getParentIndex(index);
229
while (index !== -1) {
230
if (this.contains(index, line)) {
231
return index;
232
}
233
index = this.getParentIndex(index);
234
}
235
}
236
return -1;
237
}
238
239
240
public toString() {
241
const res: string[] = [];
242
for (let i = 0; i < this.length; i++) {
243
res[i] = `[${foldSourceAbbr[this.getSource(i)]}${this.isCollapsed(i) ? '+' : '-'}] ${this.getStartLineNumber(i)}/${this.getEndLineNumber(i)}`;
244
}
245
return res.join(', ');
246
}
247
248
public toFoldRange(index: number): FoldRange {
249
return <FoldRange>{
250
startLineNumber: this._startIndexes[index] & MAX_LINE_NUMBER,
251
endLineNumber: this._endIndexes[index] & MAX_LINE_NUMBER,
252
type: this._types ? this._types[index] : undefined,
253
isCollapsed: this.isCollapsed(index),
254
source: this.getSource(index)
255
};
256
}
257
258
public static fromFoldRanges(ranges: FoldRange[]): FoldingRegions {
259
const rangesLength = ranges.length;
260
const startIndexes = new Uint32Array(rangesLength);
261
const endIndexes = new Uint32Array(rangesLength);
262
let types: Array<string | undefined> | undefined = [];
263
let gotTypes = false;
264
for (let i = 0; i < rangesLength; i++) {
265
const range = ranges[i];
266
startIndexes[i] = range.startLineNumber;
267
endIndexes[i] = range.endLineNumber;
268
types.push(range.type);
269
if (range.type) {
270
gotTypes = true;
271
}
272
}
273
if (!gotTypes) {
274
types = undefined;
275
}
276
const regions = new FoldingRegions(startIndexes, endIndexes, types);
277
for (let i = 0; i < rangesLength; i++) {
278
if (ranges[i].isCollapsed) {
279
regions.setCollapsed(i, true);
280
}
281
regions.setSource(i, ranges[i].source);
282
}
283
return regions;
284
}
285
286
/**
287
* Two inputs, each a FoldingRegions or a FoldRange[], are merged.
288
* Each input must be pre-sorted on startLineNumber.
289
* The first list is assumed to always include all regions currently defined by range providers.
290
* The second list only contains the previously collapsed and all manual ranges.
291
* If the line position matches, the range of the new range is taken, and the range is no longer manual
292
* When an entry in one list overlaps an entry in the other, the second list's entry "wins" and
293
* overlapping entries in the first list are discarded.
294
* Invalid entries are discarded. An entry is invalid if:
295
* the start and end line numbers aren't a valid range of line numbers,
296
* it is out of sequence or has the same start line as a preceding entry,
297
* it overlaps a preceding entry and is not fully contained by that entry.
298
*/
299
public static sanitizeAndMerge(
300
rangesA: FoldingRegions | FoldRange[],
301
rangesB: FoldingRegions | FoldRange[],
302
maxLineNumber: number | undefined): FoldRange[] {
303
maxLineNumber = maxLineNumber ?? Number.MAX_VALUE;
304
305
const getIndexedFunction = (r: FoldingRegions | FoldRange[], limit: number) => {
306
return Array.isArray(r)
307
? ((i: number) => { return (i < limit) ? r[i] : undefined; })
308
: ((i: number) => { return (i < limit) ? r.toFoldRange(i) : undefined; });
309
};
310
const getA = getIndexedFunction(rangesA, rangesA.length);
311
const getB = getIndexedFunction(rangesB, rangesB.length);
312
let indexA = 0;
313
let indexB = 0;
314
let nextA = getA(0);
315
let nextB = getB(0);
316
317
const stackedRanges: FoldRange[] = [];
318
let topStackedRange: FoldRange | undefined;
319
let prevLineNumber = 0;
320
const resultRanges: FoldRange[] = [];
321
322
while (nextA || nextB) {
323
324
let useRange: FoldRange | undefined = undefined;
325
if (nextB && (!nextA || nextA.startLineNumber >= nextB.startLineNumber)) {
326
if (nextA && nextA.startLineNumber === nextB.startLineNumber) {
327
if (nextB.source === FoldSource.userDefined) {
328
// a user defined range (possibly unfolded)
329
useRange = nextB;
330
} else {
331
// a previously folded range or a (possibly unfolded) recovered range
332
useRange = nextA;
333
useRange.isCollapsed = nextB.isCollapsed && nextA.endLineNumber === nextB.endLineNumber;
334
useRange.source = FoldSource.provider;
335
}
336
nextA = getA(++indexA); // not necessary, just for speed
337
} else {
338
useRange = nextB;
339
if (nextB.isCollapsed && nextB.source === FoldSource.provider) {
340
// a previously collapsed range
341
useRange.source = FoldSource.recovered;
342
}
343
}
344
nextB = getB(++indexB);
345
} else {
346
// nextA is next. The user folded B set takes precedence and we sometimes need to look
347
// ahead in it to check for an upcoming conflict.
348
let scanIndex = indexB;
349
let prescanB = nextB;
350
while (true) {
351
if (!prescanB || prescanB.startLineNumber > nextA!.endLineNumber) {
352
useRange = nextA;
353
break; // no conflict, use this nextA
354
}
355
if (prescanB.source === FoldSource.userDefined && prescanB.endLineNumber > nextA!.endLineNumber) {
356
// we found a user folded range, it wins
357
break; // without setting nextResult, so this nextA gets skipped
358
}
359
prescanB = getB(++scanIndex);
360
}
361
nextA = getA(++indexA);
362
}
363
364
if (useRange) {
365
while (topStackedRange
366
&& topStackedRange.endLineNumber < useRange.startLineNumber) {
367
topStackedRange = stackedRanges.pop();
368
}
369
if (useRange.endLineNumber > useRange.startLineNumber
370
&& useRange.startLineNumber > prevLineNumber
371
&& useRange.endLineNumber <= maxLineNumber
372
&& (!topStackedRange
373
|| topStackedRange.endLineNumber >= useRange.endLineNumber)) {
374
resultRanges.push(useRange);
375
prevLineNumber = useRange.startLineNumber;
376
if (topStackedRange) {
377
stackedRanges.push(topStackedRange);
378
}
379
topStackedRange = useRange;
380
}
381
}
382
383
}
384
return resultRanges;
385
}
386
387
}
388
389
export class FoldingRegion {
390
391
constructor(private readonly ranges: FoldingRegions, private index: number) {
392
}
393
394
public get startLineNumber() {
395
return this.ranges.getStartLineNumber(this.index);
396
}
397
398
public get endLineNumber() {
399
return this.ranges.getEndLineNumber(this.index);
400
}
401
402
public get regionIndex() {
403
return this.index;
404
}
405
406
public get parentIndex() {
407
return this.ranges.getParentIndex(this.index);
408
}
409
410
public get isCollapsed() {
411
return this.ranges.isCollapsed(this.index);
412
}
413
414
containedBy(range: ILineRange): boolean {
415
return range.startLineNumber <= this.startLineNumber && range.endLineNumber >= this.endLineNumber;
416
}
417
containsLine(lineNumber: number) {
418
return this.startLineNumber <= lineNumber && lineNumber <= this.endLineNumber;
419
}
420
hidesLine(lineNumber: number) {
421
return this.startLineNumber < lineNumber && lineNumber <= this.endLineNumber;
422
}
423
}
424
425