Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/folding/browser/foldingModel.ts
5256 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 { Emitter, Event } from '../../../../base/common/event.js';
7
import { IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from '../../../common/model.js';
8
import { FoldingRegion, FoldingRegions, ILineRange, FoldRange, FoldSource } from './foldingRanges.js';
9
import { hash } from '../../../../base/common/hash.js';
10
import { SelectedLines } from './folding.js';
11
import { IDisposable } from '../../../../base/common/lifecycle.js';
12
13
export interface IDecorationProvider {
14
getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManual: boolean): IModelDecorationOptions;
15
changeDecorations<T>(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null;
16
removeDecorations(decorationIds: string[]): void;
17
}
18
19
export interface FoldingModelChangeEvent {
20
model: FoldingModel;
21
collapseStateChanged?: FoldingRegion[];
22
}
23
24
interface ILineMemento extends ILineRange {
25
checksum?: number;
26
isCollapsed?: boolean;
27
source?: FoldSource;
28
}
29
30
export type CollapseMemento = ILineMemento[];
31
32
export class FoldingModel implements IDisposable {
33
private readonly _textModel: ITextModel;
34
private readonly _decorationProvider: IDecorationProvider;
35
36
private _regions: FoldingRegions;
37
private _editorDecorationIds: string[];
38
39
private readonly _updateEventEmitter = new Emitter<FoldingModelChangeEvent>();
40
public readonly onDidChange: Event<FoldingModelChangeEvent> = this._updateEventEmitter.event;
41
42
public get regions(): FoldingRegions { return this._regions; }
43
public get textModel() { return this._textModel; }
44
public get decorationProvider() { return this._decorationProvider; }
45
46
constructor(textModel: ITextModel, decorationProvider: IDecorationProvider) {
47
this._textModel = textModel;
48
this._decorationProvider = decorationProvider;
49
this._regions = new FoldingRegions(new Uint32Array(0), new Uint32Array(0));
50
this._editorDecorationIds = [];
51
}
52
53
public toggleCollapseState(toggledRegions: FoldingRegion[]) {
54
if (!toggledRegions.length) {
55
return;
56
}
57
toggledRegions = toggledRegions.sort((r1, r2) => r1.regionIndex - r2.regionIndex);
58
59
const processed: { [key: string]: boolean | undefined } = {};
60
this._decorationProvider.changeDecorations(accessor => {
61
let k = 0; // index from [0 ... this.regions.length]
62
let dirtyRegionEndLine = -1; // end of the range where decorations need to be updated
63
let lastHiddenLine = -1; // the end of the last hidden lines
64
const updateDecorationsUntil = (index: number) => {
65
while (k < index) {
66
const endLineNumber = this._regions.getEndLineNumber(k);
67
const isCollapsed = this._regions.isCollapsed(k);
68
if (endLineNumber <= dirtyRegionEndLine) {
69
const isManual = this.regions.getSource(k) !== FoldSource.provider;
70
accessor.changeDecorationOptions(this._editorDecorationIds[k], this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine, isManual));
71
}
72
if (isCollapsed && endLineNumber > lastHiddenLine) {
73
lastHiddenLine = endLineNumber;
74
}
75
k++;
76
}
77
};
78
for (const region of toggledRegions) {
79
const index = region.regionIndex;
80
const editorDecorationId = this._editorDecorationIds[index];
81
if (editorDecorationId && !processed[editorDecorationId]) {
82
processed[editorDecorationId] = true;
83
84
updateDecorationsUntil(index); // update all decorations up to current index using the old dirtyRegionEndLine
85
86
const newCollapseState = !this._regions.isCollapsed(index);
87
this._regions.setCollapsed(index, newCollapseState);
88
89
dirtyRegionEndLine = Math.max(dirtyRegionEndLine, this._regions.getEndLineNumber(index));
90
}
91
}
92
updateDecorationsUntil(this._regions.length);
93
});
94
this._updateEventEmitter.fire({ model: this, collapseStateChanged: toggledRegions });
95
}
96
97
public removeManualRanges(ranges: ILineRange[]) {
98
const newFoldingRanges: FoldRange[] = new Array();
99
const intersects = (foldRange: FoldRange) => {
100
for (const range of ranges) {
101
if (!(range.startLineNumber > foldRange.endLineNumber || foldRange.startLineNumber > range.endLineNumber)) {
102
return true;
103
}
104
}
105
return false;
106
};
107
for (let i = 0; i < this._regions.length; i++) {
108
const foldRange = this._regions.toFoldRange(i);
109
if (foldRange.source === FoldSource.provider || !intersects(foldRange)) {
110
newFoldingRanges.push(foldRange);
111
}
112
}
113
this.updatePost(FoldingRegions.fromFoldRanges(newFoldingRanges));
114
}
115
116
public update(newRegions: FoldingRegions, selection?: SelectedLines): void {
117
const foldedOrManualRanges = this._currentFoldedOrManualRanges(selection);
118
const newRanges = FoldingRegions.sanitizeAndMerge(newRegions, foldedOrManualRanges, this._textModel.getLineCount(), selection);
119
this.updatePost(FoldingRegions.fromFoldRanges(newRanges));
120
}
121
122
public updatePost(newRegions: FoldingRegions) {
123
const newEditorDecorations: IModelDeltaDecoration[] = [];
124
let lastHiddenLine = -1;
125
for (let index = 0, limit = newRegions.length; index < limit; index++) {
126
const startLineNumber = newRegions.getStartLineNumber(index);
127
const endLineNumber = newRegions.getEndLineNumber(index);
128
const isCollapsed = newRegions.isCollapsed(index);
129
const isManual = newRegions.getSource(index) !== FoldSource.provider;
130
const decorationRange = {
131
startLineNumber: startLineNumber,
132
startColumn: this._textModel.getLineMaxColumn(startLineNumber),
133
endLineNumber: endLineNumber,
134
endColumn: this._textModel.getLineMaxColumn(endLineNumber) + 1
135
};
136
newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine, isManual) });
137
if (isCollapsed && endLineNumber > lastHiddenLine) {
138
lastHiddenLine = endLineNumber;
139
}
140
}
141
this._decorationProvider.changeDecorations(accessor => this._editorDecorationIds = accessor.deltaDecorations(this._editorDecorationIds, newEditorDecorations));
142
this._regions = newRegions;
143
this._updateEventEmitter.fire({ model: this });
144
}
145
146
private _currentFoldedOrManualRanges(selection?: SelectedLines): FoldRange[] {
147
const foldedRanges: FoldRange[] = [];
148
for (let i = 0, limit = this._regions.length; i < limit; i++) {
149
let isCollapsed = this.regions.isCollapsed(i);
150
const source = this.regions.getSource(i);
151
if (isCollapsed || source !== FoldSource.provider) {
152
const foldRange = this._regions.toFoldRange(i);
153
const decRange = this._textModel.getDecorationRange(this._editorDecorationIds[i]);
154
if (decRange) {
155
if (isCollapsed && selection?.startsInside(decRange.startLineNumber + 1, decRange.endLineNumber)) {
156
isCollapsed = false; // uncollapse is the range is blocked
157
}
158
foldedRanges.push({
159
startLineNumber: decRange.startLineNumber,
160
endLineNumber: decRange.endLineNumber,
161
type: foldRange.type,
162
isCollapsed,
163
source
164
});
165
}
166
}
167
}
168
169
return foldedRanges;
170
}
171
172
/**
173
* Collapse state memento, for persistence only
174
*/
175
public getMemento(): CollapseMemento | undefined {
176
const foldedOrManualRanges = this._currentFoldedOrManualRanges();
177
const result: ILineMemento[] = [];
178
const maxLineNumber = this._textModel.getLineCount();
179
for (let i = 0, limit = foldedOrManualRanges.length; i < limit; i++) {
180
const range = foldedOrManualRanges[i];
181
if (range.startLineNumber >= range.endLineNumber || range.startLineNumber < 1 || range.endLineNumber > maxLineNumber) {
182
continue;
183
}
184
const checksum = this._getLinesChecksum(range.startLineNumber + 1, range.endLineNumber);
185
result.push({
186
startLineNumber: range.startLineNumber,
187
endLineNumber: range.endLineNumber,
188
isCollapsed: range.isCollapsed,
189
source: range.source,
190
checksum: checksum
191
});
192
}
193
return (result.length > 0) ? result : undefined;
194
}
195
196
/**
197
* Apply persisted state, for persistence only
198
*/
199
public applyMemento(state: CollapseMemento) {
200
if (!Array.isArray(state)) {
201
return;
202
}
203
const rangesToRestore: FoldRange[] = [];
204
const maxLineNumber = this._textModel.getLineCount();
205
for (const range of state) {
206
if (range.startLineNumber >= range.endLineNumber || range.startLineNumber < 1 || range.endLineNumber > maxLineNumber) {
207
continue;
208
}
209
const checksum = this._getLinesChecksum(range.startLineNumber + 1, range.endLineNumber);
210
if (!range.checksum || checksum === range.checksum) {
211
rangesToRestore.push({
212
startLineNumber: range.startLineNumber,
213
endLineNumber: range.endLineNumber,
214
type: undefined,
215
isCollapsed: range.isCollapsed ?? true,
216
source: range.source ?? FoldSource.provider
217
});
218
}
219
}
220
221
const newRanges = FoldingRegions.sanitizeAndMerge(this._regions, rangesToRestore, maxLineNumber);
222
this.updatePost(FoldingRegions.fromFoldRanges(newRanges));
223
}
224
225
private _getLinesChecksum(lineNumber1: number, lineNumber2: number): number {
226
const h = hash(this._textModel.getLineContent(lineNumber1)
227
+ this._textModel.getLineContent(lineNumber2));
228
return h % 1000000; // 6 digits is plenty
229
}
230
231
public dispose() {
232
this._decorationProvider.removeDecorations(this._editorDecorationIds);
233
this._updateEventEmitter.dispose();
234
}
235
236
getAllRegionsAtLine(lineNumber: number, filter?: (r: FoldingRegion, level: number) => boolean): FoldingRegion[] {
237
const result: FoldingRegion[] = [];
238
if (this._regions) {
239
let index = this._regions.findRange(lineNumber);
240
let level = 1;
241
while (index >= 0) {
242
const current = this._regions.toRegion(index);
243
if (!filter || filter(current, level)) {
244
result.push(current);
245
}
246
level++;
247
index = current.parentIndex;
248
}
249
}
250
return result;
251
}
252
253
getRegionAtLine(lineNumber: number): FoldingRegion | null {
254
if (this._regions) {
255
const index = this._regions.findRange(lineNumber);
256
if (index >= 0) {
257
return this._regions.toRegion(index);
258
}
259
}
260
return null;
261
}
262
263
getRegionsInside(region: FoldingRegion | null, filter?: RegionFilter | RegionFilterWithLevel): FoldingRegion[] {
264
const result: FoldingRegion[] = [];
265
const index = region ? region.regionIndex + 1 : 0;
266
const endLineNumber = region ? region.endLineNumber : Number.MAX_VALUE;
267
268
if (filter && filter.length === 2) {
269
const levelStack: FoldingRegion[] = [];
270
for (let i = index, len = this._regions.length; i < len; i++) {
271
const current = this._regions.toRegion(i);
272
if (this._regions.getStartLineNumber(i) < endLineNumber) {
273
while (levelStack.length > 0 && !current.containedBy(levelStack[levelStack.length - 1])) {
274
levelStack.pop();
275
}
276
levelStack.push(current);
277
if (filter(current, levelStack.length)) {
278
result.push(current);
279
}
280
} else {
281
break;
282
}
283
}
284
} else {
285
for (let i = index, len = this._regions.length; i < len; i++) {
286
const current = this._regions.toRegion(i);
287
if (this._regions.getStartLineNumber(i) < endLineNumber) {
288
if (!filter || (filter as RegionFilter)(current)) {
289
result.push(current);
290
}
291
} else {
292
break;
293
}
294
}
295
}
296
return result;
297
}
298
299
}
300
301
type RegionFilter = (r: FoldingRegion) => boolean;
302
type RegionFilterWithLevel = (r: FoldingRegion, level: number) => boolean;
303
304
305
/**
306
* Collapse or expand the regions at the given locations
307
* @param levels The number of levels. Use 1 to only impact the regions at the location, use Number.MAX_VALUE for all levels.
308
* @param lineNumbers the location of the regions to collapse or expand, or if not set, all regions in the model.
309
*/
310
export function toggleCollapseState(foldingModel: FoldingModel, levels: number, lineNumbers: number[]) {
311
const toToggle: FoldingRegion[] = [];
312
for (const lineNumber of lineNumbers) {
313
const region = foldingModel.getRegionAtLine(lineNumber);
314
if (region) {
315
const doCollapse = !region.isCollapsed;
316
toToggle.push(region);
317
if (levels > 1) {
318
const regionsInside = foldingModel.getRegionsInside(region, (r, level: number) => r.isCollapsed !== doCollapse && level < levels);
319
toToggle.push(...regionsInside);
320
}
321
}
322
}
323
foldingModel.toggleCollapseState(toToggle);
324
}
325
326
327
/**
328
* Collapse or expand the regions at the given locations including all children.
329
* @param doCollapse Whether to collapse or expand
330
* @param levels The number of levels. Use 1 to only impact the regions at the location, use Number.MAX_VALUE for all levels.
331
* @param lineNumbers the location of the regions to collapse or expand, or if not set, all regions in the model.
332
*/
333
export function setCollapseStateLevelsDown(foldingModel: FoldingModel, doCollapse: boolean, levels = Number.MAX_VALUE, lineNumbers?: number[]): void {
334
const toToggle: FoldingRegion[] = [];
335
if (lineNumbers && lineNumbers.length > 0) {
336
for (const lineNumber of lineNumbers) {
337
const region = foldingModel.getRegionAtLine(lineNumber);
338
if (region) {
339
if (region.isCollapsed !== doCollapse) {
340
toToggle.push(region);
341
}
342
if (levels > 1) {
343
const regionsInside = foldingModel.getRegionsInside(region, (r, level: number) => r.isCollapsed !== doCollapse && level < levels);
344
toToggle.push(...regionsInside);
345
}
346
}
347
}
348
} else {
349
const regionsInside = foldingModel.getRegionsInside(null, (r, level: number) => r.isCollapsed !== doCollapse && level < levels);
350
toToggle.push(...regionsInside);
351
}
352
foldingModel.toggleCollapseState(toToggle);
353
}
354
355
/**
356
* Collapse or expand the regions at the given locations including all parents.
357
* @param doCollapse Whether to collapse or expand
358
* @param levels The number of levels. Use 1 to only impact the regions at the location, use Number.MAX_VALUE for all levels.
359
* @param lineNumbers the location of the regions to collapse or expand.
360
*/
361
export function setCollapseStateLevelsUp(foldingModel: FoldingModel, doCollapse: boolean, levels: number, lineNumbers: number[]): void {
362
const toToggle: FoldingRegion[] = [];
363
for (const lineNumber of lineNumbers) {
364
const regions = foldingModel.getAllRegionsAtLine(lineNumber, (region, level) => region.isCollapsed !== doCollapse && level <= levels);
365
toToggle.push(...regions);
366
}
367
foldingModel.toggleCollapseState(toToggle);
368
}
369
370
/**
371
* Collapse or expand a region at the given locations. If the inner most region is already collapsed/expanded, uses the first parent instead.
372
* @param doCollapse Whether to collapse or expand
373
* @param lineNumbers the location of the regions to collapse or expand.
374
*/
375
export function setCollapseStateUp(foldingModel: FoldingModel, doCollapse: boolean, lineNumbers: number[]): void {
376
const toToggle: FoldingRegion[] = [];
377
for (const lineNumber of lineNumbers) {
378
const regions = foldingModel.getAllRegionsAtLine(lineNumber, (region,) => region.isCollapsed !== doCollapse);
379
if (regions.length > 0) {
380
toToggle.push(regions[0]);
381
}
382
}
383
foldingModel.toggleCollapseState(toToggle);
384
}
385
386
/**
387
* Folds or unfolds all regions that have a given level, except if they contain one of the blocked lines.
388
* @param foldLevel level. Level == 1 is the top level
389
* @param doCollapse Whether to collapse or expand
390
*/
391
export function setCollapseStateAtLevel(foldingModel: FoldingModel, foldLevel: number, doCollapse: boolean, blockedLineNumbers: number[]): void {
392
const filter = (region: FoldingRegion, level: number) => level === foldLevel && region.isCollapsed !== doCollapse && !blockedLineNumbers.some(line => region.containsLine(line));
393
const toToggle = foldingModel.getRegionsInside(null, filter);
394
foldingModel.toggleCollapseState(toToggle);
395
}
396
397
/**
398
* Folds or unfolds all regions, except if they contain or are contained by a region of one of the blocked lines.
399
* @param doCollapse Whether to collapse or expand
400
* @param blockedLineNumbers the location of regions to not collapse or expand
401
*/
402
export function setCollapseStateForRest(foldingModel: FoldingModel, doCollapse: boolean, blockedLineNumbers: number[]): void {
403
const filteredRegions: FoldingRegion[] = [];
404
for (const lineNumber of blockedLineNumbers) {
405
const regions = foldingModel.getAllRegionsAtLine(lineNumber, undefined);
406
if (regions.length > 0) {
407
filteredRegions.push(regions[0]);
408
}
409
}
410
const filter = (region: FoldingRegion) => filteredRegions.every((filteredRegion) => !filteredRegion.containedBy(region) && !region.containedBy(filteredRegion)) && region.isCollapsed !== doCollapse;
411
const toToggle = foldingModel.getRegionsInside(null, filter);
412
foldingModel.toggleCollapseState(toToggle);
413
}
414
415
/**
416
* Folds all regions for which the lines start with a given regex
417
* @param foldingModel the folding model
418
*/
419
export function setCollapseStateForMatchingLines(foldingModel: FoldingModel, regExp: RegExp, doCollapse: boolean): void {
420
const editorModel = foldingModel.textModel;
421
const regions = foldingModel.regions;
422
const toToggle: FoldingRegion[] = [];
423
for (let i = regions.length - 1; i >= 0; i--) {
424
if (doCollapse !== regions.isCollapsed(i)) {
425
const startLineNumber = regions.getStartLineNumber(i);
426
if (regExp.test(editorModel.getLineContent(startLineNumber))) {
427
toToggle.push(regions.toRegion(i));
428
}
429
}
430
}
431
foldingModel.toggleCollapseState(toToggle);
432
}
433
434
/**
435
* Folds all regions of the given type
436
* @param foldingModel the folding model
437
*/
438
export function setCollapseStateForType(foldingModel: FoldingModel, type: string, doCollapse: boolean): void {
439
const regions = foldingModel.regions;
440
const toToggle: FoldingRegion[] = [];
441
for (let i = regions.length - 1; i >= 0; i--) {
442
if (doCollapse !== regions.isCollapsed(i) && type === regions.getType(i)) {
443
toToggle.push(regions.toRegion(i));
444
}
445
}
446
foldingModel.toggleCollapseState(toToggle);
447
}
448
449
/**
450
* Get line to go to for parent fold of current line
451
* @param lineNumber the current line number
452
* @param foldingModel the folding model
453
*
454
* @return Parent fold start line
455
*/
456
export function getParentFoldLine(lineNumber: number, foldingModel: FoldingModel): number | null {
457
let startLineNumber: number | null = null;
458
const foldingRegion = foldingModel.getRegionAtLine(lineNumber);
459
if (foldingRegion !== null) {
460
startLineNumber = foldingRegion.startLineNumber;
461
// If current line is not the start of the current fold, go to top line of current fold. If not, go to parent fold
462
if (lineNumber === startLineNumber) {
463
const parentFoldingIdx = foldingRegion.parentIndex;
464
if (parentFoldingIdx !== -1) {
465
startLineNumber = foldingModel.regions.getStartLineNumber(parentFoldingIdx);
466
} else {
467
startLineNumber = null;
468
}
469
}
470
}
471
return startLineNumber;
472
}
473
474
/**
475
* Get line to go to for previous fold at the same level of current line
476
* @param lineNumber the current line number
477
* @param foldingModel the folding model
478
*
479
* @return Previous fold start line
480
*/
481
export function getPreviousFoldLine(lineNumber: number, foldingModel: FoldingModel): number | null {
482
let foldingRegion = foldingModel.getRegionAtLine(lineNumber);
483
// If on the folding range start line, go to previous sibling.
484
if (foldingRegion !== null && foldingRegion.startLineNumber === lineNumber) {
485
// If current line is not the start of the current fold, go to top line of current fold. If not, go to previous fold.
486
if (lineNumber !== foldingRegion.startLineNumber) {
487
return foldingRegion.startLineNumber;
488
} else {
489
// Find min line number to stay within parent.
490
const expectedParentIndex = foldingRegion.parentIndex;
491
let minLineNumber = 0;
492
if (expectedParentIndex !== -1) {
493
minLineNumber = foldingModel.regions.getStartLineNumber(foldingRegion.parentIndex);
494
}
495
496
// Find fold at same level.
497
while (foldingRegion !== null) {
498
if (foldingRegion.regionIndex > 0) {
499
foldingRegion = foldingModel.regions.toRegion(foldingRegion.regionIndex - 1);
500
501
// Keep at same level.
502
if (foldingRegion.startLineNumber <= minLineNumber) {
503
return null;
504
} else if (foldingRegion.parentIndex === expectedParentIndex) {
505
return foldingRegion.startLineNumber;
506
}
507
} else {
508
return null;
509
}
510
}
511
}
512
} else {
513
// Go to last fold that's before the current line.
514
if (foldingModel.regions.length > 0) {
515
foldingRegion = foldingModel.regions.toRegion(foldingModel.regions.length - 1);
516
while (foldingRegion !== null) {
517
// Found fold before current line.
518
if (foldingRegion.startLineNumber < lineNumber) {
519
return foldingRegion.startLineNumber;
520
}
521
if (foldingRegion.regionIndex > 0) {
522
foldingRegion = foldingModel.regions.toRegion(foldingRegion.regionIndex - 1);
523
} else {
524
foldingRegion = null;
525
}
526
}
527
}
528
}
529
return null;
530
}
531
532
/**
533
* Get line to go to next fold at the same level of current line
534
* @param lineNumber the current line number
535
* @param foldingModel the folding model
536
*
537
* @return Next fold start line
538
*/
539
export function getNextFoldLine(lineNumber: number, foldingModel: FoldingModel): number | null {
540
let foldingRegion = foldingModel.getRegionAtLine(lineNumber);
541
// If on the folding range start line, go to next sibling.
542
if (foldingRegion !== null && foldingRegion.startLineNumber === lineNumber) {
543
// Find max line number to stay within parent.
544
const expectedParentIndex = foldingRegion.parentIndex;
545
let maxLineNumber = 0;
546
if (expectedParentIndex !== -1) {
547
maxLineNumber = foldingModel.regions.getEndLineNumber(foldingRegion.parentIndex);
548
} else if (foldingModel.regions.length === 0) {
549
return null;
550
} else {
551
maxLineNumber = foldingModel.regions.getEndLineNumber(foldingModel.regions.length - 1);
552
}
553
554
// Find fold at same level.
555
while (foldingRegion !== null) {
556
if (foldingRegion.regionIndex < foldingModel.regions.length) {
557
foldingRegion = foldingModel.regions.toRegion(foldingRegion.regionIndex + 1);
558
559
// Keep at same level.
560
if (foldingRegion.startLineNumber >= maxLineNumber) {
561
return null;
562
} else if (foldingRegion.parentIndex === expectedParentIndex) {
563
return foldingRegion.startLineNumber;
564
}
565
} else {
566
return null;
567
}
568
}
569
} else {
570
// Go to first fold that's after the current line.
571
if (foldingModel.regions.length > 0) {
572
foldingRegion = foldingModel.regions.toRegion(0);
573
while (foldingRegion !== null) {
574
// Found fold after current line.
575
if (foldingRegion.startLineNumber > lineNumber) {
576
return foldingRegion.startLineNumber;
577
}
578
if (foldingRegion.regionIndex < foldingModel.regions.length) {
579
foldingRegion = foldingModel.regions.toRegion(foldingRegion.regionIndex + 1);
580
} else {
581
foldingRegion = null;
582
}
583
}
584
}
585
}
586
return null;
587
}
588
589