Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/folding/browser/indentRangeProvider.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 { CancellationToken } from '../../../../base/common/cancellation.js';
7
import { ITextModel } from '../../../common/model.js';
8
import { computeIndentLevel } from '../../../common/model/utils.js';
9
import { FoldingMarkers } from '../../../common/languages/languageConfiguration.js';
10
import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
11
import { FoldingRegions, MAX_LINE_NUMBER } from './foldingRanges.js';
12
import { FoldingLimitReporter, RangeProvider } from './folding.js';
13
14
const MAX_FOLDING_REGIONS_FOR_INDENT_DEFAULT = 5000;
15
16
const ID_INDENT_PROVIDER = 'indent';
17
18
export class IndentRangeProvider implements RangeProvider {
19
readonly id = ID_INDENT_PROVIDER;
20
21
constructor(
22
private readonly editorModel: ITextModel,
23
private readonly languageConfigurationService: ILanguageConfigurationService,
24
private readonly foldingRangesLimit: FoldingLimitReporter
25
) { }
26
27
dispose() { }
28
29
compute(cancelationToken: CancellationToken,): Promise<FoldingRegions> {
30
const foldingRules = this.languageConfigurationService.getLanguageConfiguration(this.editorModel.getLanguageId()).foldingRules;
31
const offSide = foldingRules && !!foldingRules.offSide;
32
const markers = foldingRules && foldingRules.markers;
33
return Promise.resolve(computeRanges(this.editorModel, offSide, markers, this.foldingRangesLimit));
34
}
35
}
36
37
// public only for testing
38
export class RangesCollector {
39
private readonly _startIndexes: number[];
40
private readonly _endIndexes: number[];
41
private readonly _indentOccurrences: number[];
42
private _length: number;
43
private readonly _foldingRangesLimit: FoldingLimitReporter;
44
45
constructor(foldingRangesLimit: FoldingLimitReporter) {
46
this._startIndexes = [];
47
this._endIndexes = [];
48
this._indentOccurrences = [];
49
this._length = 0;
50
this._foldingRangesLimit = foldingRangesLimit;
51
}
52
53
public insertFirst(startLineNumber: number, endLineNumber: number, indent: number) {
54
if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) {
55
return;
56
}
57
const index = this._length;
58
this._startIndexes[index] = startLineNumber;
59
this._endIndexes[index] = endLineNumber;
60
this._length++;
61
if (indent < 1000) {
62
this._indentOccurrences[indent] = (this._indentOccurrences[indent] || 0) + 1;
63
}
64
}
65
66
public toIndentRanges(model: ITextModel) {
67
const limit = this._foldingRangesLimit.limit;
68
if (this._length <= limit) {
69
this._foldingRangesLimit.update(this._length, false);
70
71
// reverse and create arrays of the exact length
72
const startIndexes = new Uint32Array(this._length);
73
const endIndexes = new Uint32Array(this._length);
74
for (let i = this._length - 1, k = 0; i >= 0; i--, k++) {
75
startIndexes[k] = this._startIndexes[i];
76
endIndexes[k] = this._endIndexes[i];
77
}
78
return new FoldingRegions(startIndexes, endIndexes);
79
} else {
80
this._foldingRangesLimit.update(this._length, limit);
81
82
let entries = 0;
83
let maxIndent = this._indentOccurrences.length;
84
for (let i = 0; i < this._indentOccurrences.length; i++) {
85
const n = this._indentOccurrences[i];
86
if (n) {
87
if (n + entries > limit) {
88
maxIndent = i;
89
break;
90
}
91
entries += n;
92
}
93
}
94
const tabSize = model.getOptions().tabSize;
95
// reverse and create arrays of the exact length
96
const startIndexes = new Uint32Array(limit);
97
const endIndexes = new Uint32Array(limit);
98
for (let i = this._length - 1, k = 0; i >= 0; i--) {
99
const startIndex = this._startIndexes[i];
100
const lineContent = model.getLineContent(startIndex);
101
const indent = computeIndentLevel(lineContent, tabSize);
102
if (indent < maxIndent || (indent === maxIndent && entries++ < limit)) {
103
startIndexes[k] = startIndex;
104
endIndexes[k] = this._endIndexes[i];
105
k++;
106
}
107
}
108
return new FoldingRegions(startIndexes, endIndexes);
109
}
110
111
}
112
}
113
114
115
interface PreviousRegion {
116
indent: number; // indent or -2 if a marker
117
endAbove: number; // end line number for the region above
118
line: number; // start line of the region. Only used for marker regions.
119
}
120
121
const foldingRangesLimitDefault: FoldingLimitReporter = {
122
limit: MAX_FOLDING_REGIONS_FOR_INDENT_DEFAULT,
123
update: () => { }
124
};
125
126
export function computeRanges(model: ITextModel, offSide: boolean, markers?: FoldingMarkers, foldingRangesLimit: FoldingLimitReporter = foldingRangesLimitDefault): FoldingRegions {
127
const tabSize = model.getOptions().tabSize;
128
const result = new RangesCollector(foldingRangesLimit);
129
130
let pattern: RegExp | undefined = undefined;
131
if (markers) {
132
pattern = new RegExp(`(${markers.start.source})|(?:${markers.end.source})`);
133
}
134
135
const previousRegions: PreviousRegion[] = [];
136
const line = model.getLineCount() + 1;
137
previousRegions.push({ indent: -1, endAbove: line, line }); // sentinel, to make sure there's at least one entry
138
139
for (let line = model.getLineCount(); line > 0; line--) {
140
const lineContent = model.getLineContent(line);
141
const indent = computeIndentLevel(lineContent, tabSize);
142
let previous = previousRegions[previousRegions.length - 1];
143
if (indent === -1) {
144
if (offSide) {
145
// for offSide languages, empty lines are associated to the previous block
146
// note: the next block is already written to the results, so this only
147
// impacts the end position of the block before
148
previous.endAbove = line;
149
}
150
continue; // only whitespace
151
}
152
let m;
153
if (pattern && (m = lineContent.match(pattern))) {
154
// folding pattern match
155
if (m[1]) { // start pattern match
156
// discard all regions until the folding pattern
157
let i = previousRegions.length - 1;
158
while (i > 0 && previousRegions[i].indent !== -2) {
159
i--;
160
}
161
if (i > 0) {
162
previousRegions.length = i + 1;
163
previous = previousRegions[i];
164
165
// new folding range from pattern, includes the end line
166
result.insertFirst(line, previous.line, indent);
167
previous.line = line;
168
previous.indent = indent;
169
previous.endAbove = line;
170
continue;
171
} else {
172
// no end marker found, treat line as a regular line
173
}
174
} else { // end pattern match
175
previousRegions.push({ indent: -2, endAbove: line, line });
176
continue;
177
}
178
}
179
if (previous.indent > indent) {
180
// discard all regions with larger indent
181
do {
182
previousRegions.pop();
183
previous = previousRegions[previousRegions.length - 1];
184
} while (previous.indent > indent);
185
186
// new folding range
187
const endLineNumber = previous.endAbove - 1;
188
if (endLineNumber - line >= 1) { // needs at east size 1
189
result.insertFirst(line, endLineNumber, indent);
190
}
191
}
192
if (previous.indent === indent) {
193
previous.endAbove = line;
194
} else { // previous.indent < indent
195
// new region with a bigger indent
196
previousRegions.push({ indent, endAbove: line, line });
197
}
198
}
199
return result.toIndentRanges(model);
200
}
201
202