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