Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts
4780 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
import assert from 'assert';
6
import { escapeRegExpCharacters } from '../../../../../base/common/strings.js';
7
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
8
import { EditOperation } from '../../../../common/core/editOperation.js';
9
import { Position } from '../../../../common/core/position.js';
10
import { Range } from '../../../../common/core/range.js';
11
import { Selection } from '../../../../common/core/selection.js';
12
import { IModelDecorationsChangeAccessor, ITextModel, TrackedRangeStickiness } from '../../../../common/model.js';
13
import { ModelDecorationOptions } from '../../../../common/model/textModel.js';
14
import { toSelectedLines } from '../../browser/folding.js';
15
import { FoldingModel, getNextFoldLine, getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp } from '../../browser/foldingModel.js';
16
import { FoldingRegion } from '../../browser/foldingRanges.js';
17
import { computeRanges } from '../../browser/indentRangeProvider.js';
18
import { createTextModel } from '../../../../test/common/testTextModel.js';
19
20
21
interface ExpectedRegion {
22
startLineNumber: number;
23
endLineNumber: number;
24
isCollapsed: boolean;
25
}
26
27
interface ExpectedDecoration {
28
line: number;
29
type: 'hidden' | 'collapsed' | 'expanded';
30
}
31
32
export class TestDecorationProvider {
33
34
private static readonly collapsedDecoration = ModelDecorationOptions.register({
35
description: 'test',
36
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
37
linesDecorationsClassName: 'folding'
38
});
39
40
private static readonly expandedDecoration = ModelDecorationOptions.register({
41
description: 'test',
42
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
43
linesDecorationsClassName: 'folding'
44
});
45
46
private static readonly hiddenDecoration = ModelDecorationOptions.register({
47
description: 'test',
48
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
49
linesDecorationsClassName: 'folding'
50
});
51
52
constructor(private model: ITextModel) {
53
}
54
55
getDecorationOption(isCollapsed: boolean, isHidden: boolean): ModelDecorationOptions {
56
if (isHidden) {
57
return TestDecorationProvider.hiddenDecoration;
58
}
59
if (isCollapsed) {
60
return TestDecorationProvider.collapsedDecoration;
61
}
62
return TestDecorationProvider.expandedDecoration;
63
}
64
65
changeDecorations<T>(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): (T | null) {
66
return this.model.changeDecorations(callback);
67
}
68
69
removeDecorations(decorationIds: string[]): void {
70
this.model.changeDecorations((changeAccessor) => {
71
changeAccessor.deltaDecorations(decorationIds, []);
72
});
73
}
74
75
getDecorations(): ExpectedDecoration[] {
76
const decorations = this.model.getAllDecorations();
77
const res: ExpectedDecoration[] = [];
78
for (const decoration of decorations) {
79
if (decoration.options === TestDecorationProvider.hiddenDecoration) {
80
res.push({ line: decoration.range.startLineNumber, type: 'hidden' });
81
} else if (decoration.options === TestDecorationProvider.collapsedDecoration) {
82
res.push({ line: decoration.range.startLineNumber, type: 'collapsed' });
83
} else if (decoration.options === TestDecorationProvider.expandedDecoration) {
84
res.push({ line: decoration.range.startLineNumber, type: 'expanded' });
85
}
86
}
87
return res;
88
}
89
}
90
91
suite('Folding Model', () => {
92
ensureNoDisposablesAreLeakedInTestSuite();
93
function r(startLineNumber: number, endLineNumber: number, isCollapsed: boolean = false): ExpectedRegion {
94
return { startLineNumber, endLineNumber, isCollapsed };
95
}
96
97
function d(line: number, type: 'hidden' | 'collapsed' | 'expanded'): ExpectedDecoration {
98
return { line, type };
99
}
100
101
function assertRegion(actual: FoldingRegion | null, expected: ExpectedRegion | null, message?: string) {
102
assert.strictEqual(!!actual, !!expected, message);
103
if (actual && expected) {
104
assert.strictEqual(actual.startLineNumber, expected.startLineNumber, message);
105
assert.strictEqual(actual.endLineNumber, expected.endLineNumber, message);
106
assert.strictEqual(actual.isCollapsed, expected.isCollapsed, message);
107
}
108
}
109
110
function assertFoldedRanges(foldingModel: FoldingModel, expectedRegions: ExpectedRegion[], message?: string) {
111
const actualRanges: ExpectedRegion[] = [];
112
const actual = foldingModel.regions;
113
for (let i = 0; i < actual.length; i++) {
114
if (actual.isCollapsed(i)) {
115
actualRanges.push(r(actual.getStartLineNumber(i), actual.getEndLineNumber(i)));
116
}
117
}
118
assert.deepStrictEqual(actualRanges, expectedRegions, message);
119
}
120
121
function assertRanges(foldingModel: FoldingModel, expectedRegions: ExpectedRegion[], message?: string) {
122
const actualRanges: ExpectedRegion[] = [];
123
const actual = foldingModel.regions;
124
for (let i = 0; i < actual.length; i++) {
125
actualRanges.push(r(actual.getStartLineNumber(i), actual.getEndLineNumber(i), actual.isCollapsed(i)));
126
}
127
assert.deepStrictEqual(actualRanges, expectedRegions, message);
128
}
129
130
function assertDecorations(foldingModel: FoldingModel, expectedDecoration: ExpectedDecoration[], message?: string) {
131
const decorationProvider = foldingModel.decorationProvider as TestDecorationProvider;
132
assert.deepStrictEqual(decorationProvider.getDecorations(), expectedDecoration, message);
133
}
134
135
function assertRegions(actual: FoldingRegion[], expectedRegions: ExpectedRegion[], message?: string) {
136
assert.deepStrictEqual(actual.map(r => ({ startLineNumber: r.startLineNumber, endLineNumber: r.endLineNumber, isCollapsed: r.isCollapsed })), expectedRegions, message);
137
}
138
139
test('getRegionAtLine', () => {
140
const lines = [
141
/* 1*/ '/**',
142
/* 2*/ ' * Comment',
143
/* 3*/ ' */',
144
/* 4*/ 'class A {',
145
/* 5*/ ' void foo() {',
146
/* 6*/ ' // comment {',
147
/* 7*/ ' }',
148
/* 8*/ '}'];
149
150
const textModel = createTextModel(lines.join('\n'));
151
try {
152
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
153
154
const ranges = computeRanges(textModel, false, undefined);
155
foldingModel.update(ranges);
156
157
const r1 = r(1, 3, false);
158
const r2 = r(4, 7, false);
159
const r3 = r(5, 6, false);
160
161
assertRanges(foldingModel, [r1, r2, r3]);
162
163
assertRegion(foldingModel.getRegionAtLine(1), r1, '1');
164
assertRegion(foldingModel.getRegionAtLine(2), r1, '2');
165
assertRegion(foldingModel.getRegionAtLine(3), r1, '3');
166
assertRegion(foldingModel.getRegionAtLine(4), r2, '4');
167
assertRegion(foldingModel.getRegionAtLine(5), r3, '5');
168
assertRegion(foldingModel.getRegionAtLine(6), r3, '5');
169
assertRegion(foldingModel.getRegionAtLine(7), r2, '6');
170
assertRegion(foldingModel.getRegionAtLine(8), null, '7');
171
} finally {
172
textModel.dispose();
173
}
174
175
176
});
177
178
test('collapse', () => {
179
const lines = [
180
/* 1*/ '/**',
181
/* 2*/ ' * Comment',
182
/* 3*/ ' */',
183
/* 4*/ 'class A {',
184
/* 5*/ ' void foo() {',
185
/* 6*/ ' // comment {',
186
/* 7*/ ' }',
187
/* 8*/ '}'];
188
189
const textModel = createTextModel(lines.join('\n'));
190
try {
191
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
192
193
const ranges = computeRanges(textModel, false, undefined);
194
foldingModel.update(ranges);
195
196
const r1 = r(1, 3, false);
197
const r2 = r(4, 7, false);
198
const r3 = r(5, 6, false);
199
200
assertRanges(foldingModel, [r1, r2, r3]);
201
202
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!]);
203
foldingModel.update(ranges);
204
205
assertRanges(foldingModel, [r(1, 3, true), r2, r3]);
206
207
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(5)!]);
208
foldingModel.update(ranges);
209
210
assertRanges(foldingModel, [r(1, 3, true), r2, r(5, 6, true)]);
211
212
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(7)!]);
213
foldingModel.update(ranges);
214
215
assertRanges(foldingModel, [r(1, 3, true), r(4, 7, true), r(5, 6, true)]);
216
217
textModel.dispose();
218
} finally {
219
textModel.dispose();
220
}
221
222
});
223
224
test('update', () => {
225
const lines = [
226
/* 1*/ '/**',
227
/* 2*/ ' * Comment',
228
/* 3*/ ' */',
229
/* 4*/ 'class A {',
230
/* 5*/ ' void foo() {',
231
/* 6*/ ' // comment {',
232
/* 7*/ ' }',
233
/* 8*/ '}'];
234
235
const textModel = createTextModel(lines.join('\n'));
236
try {
237
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
238
239
const ranges = computeRanges(textModel, false, undefined);
240
foldingModel.update(ranges);
241
242
const r1 = r(1, 3, false);
243
const r2 = r(4, 7, false);
244
const r3 = r(5, 6, false);
245
246
assertRanges(foldingModel, [r1, r2, r3]);
247
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(2)!, foldingModel.getRegionAtLine(5)!]);
248
249
textModel.applyEdits([EditOperation.insert(new Position(4, 1), '//hello\n')]);
250
251
foldingModel.update(computeRanges(textModel, false, undefined));
252
253
assertRanges(foldingModel, [r(1, 3, true), r(5, 8, false), r(6, 7, true)]);
254
} finally {
255
textModel.dispose();
256
}
257
});
258
259
test('delete', () => {
260
const lines = [
261
/* 1*/ 'function foo() {',
262
/* 2*/ ' switch (x) {',
263
/* 3*/ ' case 1:',
264
/* 4*/ ' //hello1',
265
/* 5*/ ' break;',
266
/* 6*/ ' case 2:',
267
/* 7*/ ' //hello2',
268
/* 8*/ ' break;',
269
/* 9*/ ' case 3:',
270
/* 10*/ ' //hello3',
271
/* 11*/ ' break;',
272
/* 12*/ ' }',
273
/* 13*/ '}'];
274
275
const textModel = createTextModel(lines.join('\n'));
276
try {
277
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
278
279
const ranges = computeRanges(textModel, false, undefined);
280
foldingModel.update(ranges);
281
282
const r1 = r(1, 12, false);
283
const r2 = r(2, 11, false);
284
const r3 = r(3, 5, false);
285
const r4 = r(6, 8, false);
286
const r5 = r(9, 11, false);
287
288
assertRanges(foldingModel, [r1, r2, r3, r4, r5]);
289
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(6)!]);
290
291
textModel.applyEdits([EditOperation.delete(new Range(6, 11, 9, 0))]);
292
293
foldingModel.update(computeRanges(textModel, true, undefined), toSelectedLines([new Selection(7, 1, 7, 1)]));
294
295
assertRanges(foldingModel, [r(1, 9, false), r(2, 8, false), r(3, 5, false), r(6, 8, false)]);
296
} finally {
297
textModel.dispose();
298
}
299
});
300
301
test('getRegionsInside', () => {
302
const lines = [
303
/* 1*/ '/**',
304
/* 2*/ ' * Comment',
305
/* 3*/ ' */',
306
/* 4*/ 'class A {',
307
/* 5*/ ' void foo() {',
308
/* 6*/ ' // comment {',
309
/* 7*/ ' }',
310
/* 8*/ '}'];
311
312
const textModel = createTextModel(lines.join('\n'));
313
try {
314
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
315
316
const ranges = computeRanges(textModel, false, undefined);
317
foldingModel.update(ranges);
318
319
const r1 = r(1, 3, false);
320
const r2 = r(4, 7, false);
321
const r3 = r(5, 6, false);
322
323
assertRanges(foldingModel, [r1, r2, r3]);
324
const region1 = foldingModel.getRegionAtLine(r1.startLineNumber);
325
const region2 = foldingModel.getRegionAtLine(r2.startLineNumber);
326
const region3 = foldingModel.getRegionAtLine(r3.startLineNumber);
327
328
assertRegions(foldingModel.getRegionsInside(null), [r1, r2, r3], '1');
329
assertRegions(foldingModel.getRegionsInside(region1), [], '2');
330
assertRegions(foldingModel.getRegionsInside(region2), [r3], '3');
331
assertRegions(foldingModel.getRegionsInside(region3), [], '4');
332
} finally {
333
textModel.dispose();
334
}
335
336
});
337
338
test('getRegionsInsideWithLevel', () => {
339
const lines = [
340
/* 1*/ '//#region',
341
/* 2*/ '//#endregion',
342
/* 3*/ 'class A {',
343
/* 4*/ ' void foo() {',
344
/* 5*/ ' if (true) {',
345
/* 6*/ ' return;',
346
/* 7*/ ' }',
347
/* 8*/ ' if (true) {',
348
/* 9*/ ' return;',
349
/* 10*/ ' }',
350
/* 11*/ ' }',
351
/* 12*/ '}'];
352
353
const textModel = createTextModel(lines.join('\n'));
354
try {
355
356
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
357
358
const ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ });
359
foldingModel.update(ranges);
360
361
const r1 = r(1, 2, false);
362
const r2 = r(3, 11, false);
363
const r3 = r(4, 10, false);
364
const r4 = r(5, 6, false);
365
const r5 = r(8, 9, false);
366
367
const region1 = foldingModel.getRegionAtLine(r1.startLineNumber);
368
const region2 = foldingModel.getRegionAtLine(r2.startLineNumber);
369
const region3 = foldingModel.getRegionAtLine(r3.startLineNumber);
370
371
assertRanges(foldingModel, [r1, r2, r3, r4, r5]);
372
373
assertRegions(foldingModel.getRegionsInside(null, (r, level) => level === 1), [r1, r2], '1');
374
assertRegions(foldingModel.getRegionsInside(null, (r, level) => level === 2), [r3], '2');
375
assertRegions(foldingModel.getRegionsInside(null, (r, level) => level === 3), [r4, r5], '3');
376
377
assertRegions(foldingModel.getRegionsInside(region2, (r, level) => level === 1), [r3], '4');
378
assertRegions(foldingModel.getRegionsInside(region2, (r, level) => level === 2), [r4, r5], '5');
379
assertRegions(foldingModel.getRegionsInside(region3, (r, level) => level === 1), [r4, r5], '6');
380
381
assertRegions(foldingModel.getRegionsInside(region2, (r, level) => r.hidesLine(9)), [r3, r5], '7');
382
383
assertRegions(foldingModel.getRegionsInside(region1, (r, level) => level === 1), [], '8');
384
} finally {
385
textModel.dispose();
386
}
387
388
});
389
390
test('getRegionAtLine2', () => {
391
const lines = [
392
/* 1*/ '//#region',
393
/* 2*/ 'class A {',
394
/* 3*/ ' void foo() {',
395
/* 4*/ ' if (true) {',
396
/* 5*/ ' //hello',
397
/* 6*/ ' }',
398
/* 7*/ '',
399
/* 8*/ ' }',
400
/* 9*/ '}',
401
/* 10*/ '//#endregion',
402
/* 11*/ ''];
403
404
const textModel = createTextModel(lines.join('\n'));
405
try {
406
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
407
408
const ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ });
409
foldingModel.update(ranges);
410
411
const r1 = r(1, 10, false);
412
const r2 = r(2, 8, false);
413
const r3 = r(3, 7, false);
414
const r4 = r(4, 5, false);
415
416
assertRanges(foldingModel, [r1, r2, r3, r4]);
417
418
assertRegions(foldingModel.getAllRegionsAtLine(1), [r1], '1');
419
assertRegions(foldingModel.getAllRegionsAtLine(2), [r1, r2].reverse(), '2');
420
assertRegions(foldingModel.getAllRegionsAtLine(3), [r1, r2, r3].reverse(), '3');
421
assertRegions(foldingModel.getAllRegionsAtLine(4), [r1, r2, r3, r4].reverse(), '4');
422
assertRegions(foldingModel.getAllRegionsAtLine(5), [r1, r2, r3, r4].reverse(), '5');
423
assertRegions(foldingModel.getAllRegionsAtLine(6), [r1, r2, r3].reverse(), '6');
424
assertRegions(foldingModel.getAllRegionsAtLine(7), [r1, r2, r3].reverse(), '7');
425
assertRegions(foldingModel.getAllRegionsAtLine(8), [r1, r2].reverse(), '8');
426
assertRegions(foldingModel.getAllRegionsAtLine(9), [r1], '9');
427
assertRegions(foldingModel.getAllRegionsAtLine(10), [r1], '10');
428
assertRegions(foldingModel.getAllRegionsAtLine(11), [], '10');
429
} finally {
430
textModel.dispose();
431
}
432
});
433
434
test('setCollapseStateRecursivly', () => {
435
const lines = [
436
/* 1*/ '//#region',
437
/* 2*/ '//#endregion',
438
/* 3*/ 'class A {',
439
/* 4*/ ' void foo() {',
440
/* 5*/ ' if (true) {',
441
/* 6*/ ' return;',
442
/* 7*/ ' }',
443
/* 8*/ '',
444
/* 9*/ ' if (true) {',
445
/* 10*/ ' return;',
446
/* 11*/ ' }',
447
/* 12*/ ' }',
448
/* 13*/ '}'];
449
450
const textModel = createTextModel(lines.join('\n'));
451
try {
452
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
453
454
const ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ });
455
foldingModel.update(ranges);
456
457
const r1 = r(1, 2, false);
458
const r2 = r(3, 12, false);
459
const r3 = r(4, 11, false);
460
const r4 = r(5, 6, false);
461
const r5 = r(9, 10, false);
462
assertRanges(foldingModel, [r1, r2, r3, r4, r5]);
463
464
setCollapseStateLevelsDown(foldingModel, true, Number.MAX_VALUE, [4]);
465
assertFoldedRanges(foldingModel, [r3, r4, r5], '1');
466
467
setCollapseStateLevelsDown(foldingModel, false, Number.MAX_VALUE, [8]);
468
assertFoldedRanges(foldingModel, [], '2');
469
470
setCollapseStateLevelsDown(foldingModel, true, Number.MAX_VALUE, [12]);
471
assertFoldedRanges(foldingModel, [r2, r3, r4, r5], '1');
472
473
setCollapseStateLevelsDown(foldingModel, false, Number.MAX_VALUE, [7]);
474
assertFoldedRanges(foldingModel, [r2], '1');
475
476
setCollapseStateLevelsDown(foldingModel, false);
477
assertFoldedRanges(foldingModel, [], '1');
478
479
setCollapseStateLevelsDown(foldingModel, true);
480
assertFoldedRanges(foldingModel, [r1, r2, r3, r4, r5], '1');
481
} finally {
482
textModel.dispose();
483
}
484
485
});
486
487
test('setCollapseStateAtLevel', () => {
488
const lines = [
489
/* 1*/ '//#region',
490
/* 2*/ '//#endregion',
491
/* 3*/ 'class A {',
492
/* 4*/ ' void foo() {',
493
/* 5*/ ' if (true) {',
494
/* 6*/ ' return;',
495
/* 7*/ ' }',
496
/* 8*/ '',
497
/* 9*/ ' if (true) {',
498
/* 10*/ ' return;',
499
/* 11*/ ' }',
500
/* 12*/ ' }',
501
/* 13*/ ' //#region',
502
/* 14*/ ' const bar = 9;',
503
/* 15*/ ' //#endregion',
504
/* 16*/ '}'];
505
506
const textModel = createTextModel(lines.join('\n'));
507
try {
508
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
509
510
const ranges = computeRanges(textModel, false, { start: /^\s*\/\/#region$/, end: /^\s*\/\/#endregion$/ });
511
foldingModel.update(ranges);
512
513
const r1 = r(1, 2, false);
514
const r2 = r(3, 15, false);
515
const r3 = r(4, 11, false);
516
const r4 = r(5, 6, false);
517
const r5 = r(9, 10, false);
518
const r6 = r(13, 15, false);
519
assertRanges(foldingModel, [r1, r2, r3, r4, r5, r6]);
520
521
setCollapseStateAtLevel(foldingModel, 1, true, []);
522
assertFoldedRanges(foldingModel, [r1, r2], '1');
523
524
setCollapseStateAtLevel(foldingModel, 1, false, [5]);
525
assertFoldedRanges(foldingModel, [r2], '2');
526
527
setCollapseStateAtLevel(foldingModel, 1, false, [1]);
528
assertFoldedRanges(foldingModel, [], '3');
529
530
setCollapseStateAtLevel(foldingModel, 2, true, []);
531
assertFoldedRanges(foldingModel, [r3, r6], '4');
532
533
setCollapseStateAtLevel(foldingModel, 2, false, [5, 6]);
534
assertFoldedRanges(foldingModel, [r3], '5');
535
536
setCollapseStateAtLevel(foldingModel, 3, true, [4, 9]);
537
assertFoldedRanges(foldingModel, [r3, r4], '6');
538
539
setCollapseStateAtLevel(foldingModel, 3, false, [4, 9]);
540
assertFoldedRanges(foldingModel, [r3], '7');
541
} finally {
542
textModel.dispose();
543
}
544
});
545
546
test('setCollapseStateLevelsDown', () => {
547
const lines = [
548
/* 1*/ '//#region',
549
/* 2*/ '//#endregion',
550
/* 3*/ 'class A {',
551
/* 4*/ ' void foo() {',
552
/* 5*/ ' if (true) {',
553
/* 6*/ ' return;',
554
/* 7*/ ' }',
555
/* 8*/ '',
556
/* 9*/ ' if (true) {',
557
/* 10*/ ' return;',
558
/* 11*/ ' }',
559
/* 12*/ ' }',
560
/* 13*/ '}'];
561
562
const textModel = createTextModel(lines.join('\n'));
563
try {
564
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
565
566
const ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ });
567
foldingModel.update(ranges);
568
569
const r1 = r(1, 2, false);
570
const r2 = r(3, 12, false);
571
const r3 = r(4, 11, false);
572
const r4 = r(5, 6, false);
573
const r5 = r(9, 10, false);
574
assertRanges(foldingModel, [r1, r2, r3, r4, r5]);
575
576
setCollapseStateLevelsDown(foldingModel, true, 1, [4]);
577
assertFoldedRanges(foldingModel, [r3], '1');
578
579
setCollapseStateLevelsDown(foldingModel, true, 2, [4]);
580
assertFoldedRanges(foldingModel, [r3, r4, r5], '2');
581
582
setCollapseStateLevelsDown(foldingModel, false, 2, [3]);
583
assertFoldedRanges(foldingModel, [r4, r5], '3');
584
585
setCollapseStateLevelsDown(foldingModel, false, 2, [2]);
586
assertFoldedRanges(foldingModel, [r4, r5], '4');
587
588
setCollapseStateLevelsDown(foldingModel, true, 4, [2]);
589
assertFoldedRanges(foldingModel, [r1, r4, r5], '5');
590
591
setCollapseStateLevelsDown(foldingModel, false, 4, [2, 3]);
592
assertFoldedRanges(foldingModel, [], '6');
593
} finally {
594
textModel.dispose();
595
}
596
});
597
598
test('setCollapseStateLevelsUp', () => {
599
const lines = [
600
/* 1*/ '//#region',
601
/* 2*/ '//#endregion',
602
/* 3*/ 'class A {',
603
/* 4*/ ' void foo() {',
604
/* 5*/ ' if (true) {',
605
/* 6*/ ' return;',
606
/* 7*/ ' }',
607
/* 8*/ '',
608
/* 9*/ ' if (true) {',
609
/* 10*/ ' return;',
610
/* 11*/ ' }',
611
/* 12*/ ' }',
612
/* 13*/ '}'];
613
614
const textModel = createTextModel(lines.join('\n'));
615
try {
616
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
617
618
const ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ });
619
foldingModel.update(ranges);
620
621
const r1 = r(1, 2, false);
622
const r2 = r(3, 12, false);
623
const r3 = r(4, 11, false);
624
const r4 = r(5, 6, false);
625
const r5 = r(9, 10, false);
626
assertRanges(foldingModel, [r1, r2, r3, r4, r5]);
627
628
setCollapseStateLevelsUp(foldingModel, true, 1, [4]);
629
assertFoldedRanges(foldingModel, [r3], '1');
630
631
setCollapseStateLevelsUp(foldingModel, true, 2, [4]);
632
assertFoldedRanges(foldingModel, [r2, r3], '2');
633
634
setCollapseStateLevelsUp(foldingModel, false, 4, [1, 3, 4]);
635
assertFoldedRanges(foldingModel, [], '3');
636
637
setCollapseStateLevelsUp(foldingModel, true, 2, [10]);
638
assertFoldedRanges(foldingModel, [r3, r5], '4');
639
} finally {
640
textModel.dispose();
641
}
642
643
});
644
645
test('setCollapseStateUp', () => {
646
const lines = [
647
/* 1*/ '//#region',
648
/* 2*/ '//#endregion',
649
/* 3*/ 'class A {',
650
/* 4*/ ' void foo() {',
651
/* 5*/ ' if (true) {',
652
/* 6*/ ' return;',
653
/* 7*/ ' }',
654
/* 8*/ '',
655
/* 9*/ ' if (true) {',
656
/* 10*/ ' return;',
657
/* 11*/ ' }',
658
/* 12*/ ' }',
659
/* 13*/ '}'];
660
661
const textModel = createTextModel(lines.join('\n'));
662
try {
663
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
664
665
const ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ });
666
foldingModel.update(ranges);
667
668
const r1 = r(1, 2, false);
669
const r2 = r(3, 12, false);
670
const r3 = r(4, 11, false);
671
const r4 = r(5, 6, false);
672
const r5 = r(9, 10, false);
673
assertRanges(foldingModel, [r1, r2, r3, r4, r5]);
674
675
setCollapseStateUp(foldingModel, true, [5]);
676
assertFoldedRanges(foldingModel, [r4], '1');
677
678
setCollapseStateUp(foldingModel, true, [5]);
679
assertFoldedRanges(foldingModel, [r3, r4], '2');
680
681
setCollapseStateUp(foldingModel, true, [4]);
682
assertFoldedRanges(foldingModel, [r2, r3, r4], '2');
683
} finally {
684
textModel.dispose();
685
}
686
687
});
688
689
690
test('setCollapseStateForMatchingLines', () => {
691
const lines = [
692
/* 1*/ '/**',
693
/* 2*/ ' * the class',
694
/* 3*/ ' */',
695
/* 4*/ 'class A {',
696
/* 5*/ ' /**',
697
/* 6*/ ' * the foo',
698
/* 7*/ ' */',
699
/* 8*/ ' void foo() {',
700
/* 9*/ ' /*',
701
/* 10*/ ' * the comment',
702
/* 11*/ ' */',
703
/* 12*/ ' }',
704
/* 13*/ '}'];
705
706
const textModel = createTextModel(lines.join('\n'));
707
try {
708
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
709
710
const ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ });
711
foldingModel.update(ranges);
712
713
const r1 = r(1, 3, false);
714
const r2 = r(4, 12, false);
715
const r3 = r(5, 7, false);
716
const r4 = r(8, 11, false);
717
const r5 = r(9, 11, false);
718
assertRanges(foldingModel, [r1, r2, r3, r4, r5]);
719
720
const regExp = new RegExp('^\\s*' + escapeRegExpCharacters('/*'));
721
setCollapseStateForMatchingLines(foldingModel, regExp, true);
722
assertFoldedRanges(foldingModel, [r1, r3, r5], '1');
723
} finally {
724
textModel.dispose();
725
}
726
727
});
728
729
730
test('setCollapseStateForRest', () => {
731
const lines = [
732
/* 1*/ '//#region',
733
/* 2*/ '//#endregion',
734
/* 3*/ 'class A {',
735
/* 4*/ ' void foo() {',
736
/* 5*/ ' if (true) {',
737
/* 6*/ ' return;',
738
/* 7*/ ' }',
739
/* 8*/ '',
740
/* 9*/ ' if (true) {',
741
/* 10*/ ' return;',
742
/* 11*/ ' }',
743
/* 12*/ ' }',
744
/* 13*/ '}'];
745
746
const textModel = createTextModel(lines.join('\n'));
747
try {
748
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
749
750
const ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ });
751
foldingModel.update(ranges);
752
753
const r1 = r(1, 2, false);
754
const r2 = r(3, 12, false);
755
const r3 = r(4, 11, false);
756
const r4 = r(5, 6, false);
757
const r5 = r(9, 10, false);
758
assertRanges(foldingModel, [r1, r2, r3, r4, r5]);
759
760
setCollapseStateForRest(foldingModel, true, [5]);
761
assertFoldedRanges(foldingModel, [r1, r5], '1');
762
763
setCollapseStateForRest(foldingModel, false, [5]);
764
assertFoldedRanges(foldingModel, [], '2');
765
766
setCollapseStateForRest(foldingModel, true, [1]);
767
assertFoldedRanges(foldingModel, [r2, r3, r4, r5], '3');
768
769
setCollapseStateForRest(foldingModel, true, [3]);
770
assertFoldedRanges(foldingModel, [r1, r2, r3, r4, r5], '3');
771
772
} finally {
773
textModel.dispose();
774
}
775
776
});
777
778
779
test('folding decoration', () => {
780
const lines = [
781
/* 1*/ 'class A {',
782
/* 2*/ ' void foo() {',
783
/* 3*/ ' if (true) {',
784
/* 4*/ ' hoo();',
785
/* 5*/ ' }',
786
/* 6*/ ' }',
787
/* 7*/ '}'];
788
789
const textModel = createTextModel(lines.join('\n'));
790
try {
791
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
792
793
const ranges = computeRanges(textModel, false, undefined);
794
foldingModel.update(ranges);
795
796
const r1 = r(1, 6, false);
797
const r2 = r(2, 5, false);
798
const r3 = r(3, 4, false);
799
800
assertRanges(foldingModel, [r1, r2, r3]);
801
assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'expanded'), d(3, 'expanded')]);
802
803
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(2)!]);
804
805
assertRanges(foldingModel, [r1, r(2, 5, true), r3]);
806
assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'collapsed'), d(3, 'hidden')]);
807
808
foldingModel.update(ranges);
809
810
assertRanges(foldingModel, [r1, r(2, 5, true), r3]);
811
assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'collapsed'), d(3, 'hidden')]);
812
813
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!]);
814
815
assertRanges(foldingModel, [r(1, 6, true), r(2, 5, true), r3]);
816
assertDecorations(foldingModel, [d(1, 'collapsed'), d(2, 'hidden'), d(3, 'hidden')]);
817
818
foldingModel.update(ranges);
819
820
assertRanges(foldingModel, [r(1, 6, true), r(2, 5, true), r3]);
821
assertDecorations(foldingModel, [d(1, 'collapsed'), d(2, 'hidden'), d(3, 'hidden')]);
822
823
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!, foldingModel.getRegionAtLine(3)!]);
824
825
assertRanges(foldingModel, [r1, r(2, 5, true), r(3, 4, true)]);
826
assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'collapsed'), d(3, 'hidden')]);
827
828
foldingModel.update(ranges);
829
830
assertRanges(foldingModel, [r1, r(2, 5, true), r(3, 4, true)]);
831
assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'collapsed'), d(3, 'hidden')]);
832
833
textModel.dispose();
834
} finally {
835
textModel.dispose();
836
}
837
838
});
839
840
test('fold jumping', () => {
841
const lines = [
842
/* 1*/ 'class A {',
843
/* 2*/ ' void foo() {',
844
/* 3*/ ' if (1) {',
845
/* 4*/ ' a();',
846
/* 5*/ ' } else if (2) {',
847
/* 6*/ ' if (true) {',
848
/* 7*/ ' b();',
849
/* 8*/ ' }',
850
/* 9*/ ' } else {',
851
/* 10*/ ' c();',
852
/* 11*/ ' }',
853
/* 12*/ ' }',
854
/* 13*/ '}'
855
];
856
857
const textModel = createTextModel(lines.join('\n'));
858
try {
859
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
860
861
const ranges = computeRanges(textModel, false, undefined);
862
foldingModel.update(ranges);
863
864
const r1 = r(1, 12, false);
865
const r2 = r(2, 11, false);
866
const r3 = r(3, 4, false);
867
const r4 = r(5, 8, false);
868
const r5 = r(6, 7, false);
869
const r6 = r(9, 10, false);
870
assertRanges(foldingModel, [r1, r2, r3, r4, r5, r6]);
871
872
// Test jump to parent.
873
assert.strictEqual(getParentFoldLine(7, foldingModel), 6);
874
assert.strictEqual(getParentFoldLine(6, foldingModel), 5);
875
assert.strictEqual(getParentFoldLine(5, foldingModel), 2);
876
assert.strictEqual(getParentFoldLine(2, foldingModel), 1);
877
assert.strictEqual(getParentFoldLine(1, foldingModel), null);
878
879
// Test jump to previous.
880
assert.strictEqual(getPreviousFoldLine(10, foldingModel), 9);
881
assert.strictEqual(getPreviousFoldLine(9, foldingModel), 5);
882
assert.strictEqual(getPreviousFoldLine(5, foldingModel), 3);
883
assert.strictEqual(getPreviousFoldLine(3, foldingModel), null);
884
// Test when not on a folding region start line.
885
assert.strictEqual(getPreviousFoldLine(4, foldingModel), 3);
886
assert.strictEqual(getPreviousFoldLine(7, foldingModel), 6);
887
assert.strictEqual(getPreviousFoldLine(8, foldingModel), 6);
888
889
// Test jump to next.
890
assert.strictEqual(getNextFoldLine(3, foldingModel), 5);
891
assert.strictEqual(getNextFoldLine(5, foldingModel), 9);
892
assert.strictEqual(getNextFoldLine(9, foldingModel), null);
893
// Test when not on a folding region start line.
894
assert.strictEqual(getNextFoldLine(4, foldingModel), 5);
895
assert.strictEqual(getNextFoldLine(7, foldingModel), 9);
896
assert.strictEqual(getNextFoldLine(8, foldingModel), 9);
897
898
} finally {
899
textModel.dispose();
900
}
901
902
});
903
904
test('fold jumping issue #129503', () => {
905
const lines = [
906
/* 1*/ '',
907
/* 2*/ 'if True:',
908
/* 3*/ ' print(1)',
909
/* 4*/ 'if True:',
910
/* 5*/ ' print(1)',
911
/* 6*/ ''
912
];
913
914
const textModel = createTextModel(lines.join('\n'));
915
try {
916
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
917
918
const ranges = computeRanges(textModel, false, undefined);
919
foldingModel.update(ranges);
920
921
const r1 = r(2, 3, false);
922
const r2 = r(4, 6, false);
923
assertRanges(foldingModel, [r1, r2]);
924
925
// Test jump to next.
926
assert.strictEqual(getNextFoldLine(1, foldingModel), 2);
927
assert.strictEqual(getNextFoldLine(2, foldingModel), 4);
928
assert.strictEqual(getNextFoldLine(3, foldingModel), 4);
929
assert.strictEqual(getNextFoldLine(4, foldingModel), null);
930
assert.strictEqual(getNextFoldLine(5, foldingModel), null);
931
assert.strictEqual(getNextFoldLine(6, foldingModel), null);
932
933
// Test jump to previous.
934
assert.strictEqual(getPreviousFoldLine(1, foldingModel), null);
935
assert.strictEqual(getPreviousFoldLine(2, foldingModel), null);
936
assert.strictEqual(getPreviousFoldLine(3, foldingModel), 2);
937
assert.strictEqual(getPreviousFoldLine(4, foldingModel), 2);
938
assert.strictEqual(getPreviousFoldLine(5, foldingModel), 4);
939
assert.strictEqual(getPreviousFoldLine(6, foldingModel), 4);
940
} finally {
941
textModel.dispose();
942
}
943
});
944
});
945
946