Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/folding/browser/syntaxRangeProvider.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 { onUnexpectedExternalError } from '../../../../base/common/errors.js';
8
import { DisposableStore } from '../../../../base/common/lifecycle.js';
9
import { ITextModel } from '../../../common/model.js';
10
import { FoldingContext, FoldingRange, FoldingRangeProvider } from '../../../common/languages.js';
11
import { FoldingLimitReporter, RangeProvider } from './folding.js';
12
import { FoldingRegions, MAX_LINE_NUMBER } from './foldingRanges.js';
13
14
export interface IFoldingRangeData extends FoldingRange {
15
rank: number;
16
}
17
18
const foldingContext: FoldingContext = {
19
};
20
21
const ID_SYNTAX_PROVIDER = 'syntax';
22
23
export class SyntaxRangeProvider implements RangeProvider {
24
25
readonly id = ID_SYNTAX_PROVIDER;
26
27
readonly disposables: DisposableStore;
28
29
constructor(
30
private readonly editorModel: ITextModel,
31
private readonly providers: FoldingRangeProvider[],
32
readonly handleFoldingRangesChange: () => void,
33
private readonly foldingRangesLimit: FoldingLimitReporter,
34
private readonly fallbackRangeProvider: RangeProvider | undefined // used when all providers return null
35
) {
36
this.disposables = new DisposableStore();
37
if (fallbackRangeProvider) {
38
this.disposables.add(fallbackRangeProvider);
39
}
40
41
for (const provider of providers) {
42
if (typeof provider.onDidChange === 'function') {
43
this.disposables.add(provider.onDidChange(handleFoldingRangesChange));
44
}
45
}
46
}
47
48
compute(cancellationToken: CancellationToken): Promise<FoldingRegions | null> {
49
return collectSyntaxRanges(this.providers, this.editorModel, cancellationToken).then(ranges => {
50
if (this.editorModel.isDisposed()) {
51
return null;
52
}
53
if (ranges) {
54
const res = sanitizeRanges(ranges, this.foldingRangesLimit);
55
return res;
56
}
57
return this.fallbackRangeProvider?.compute(cancellationToken) ?? null;
58
});
59
}
60
61
dispose() {
62
this.disposables.dispose();
63
}
64
}
65
66
function collectSyntaxRanges(providers: FoldingRangeProvider[], model: ITextModel, cancellationToken: CancellationToken): Promise<IFoldingRangeData[] | null> {
67
let rangeData: IFoldingRangeData[] | null = null;
68
const promises = providers.map((provider, i) => {
69
return Promise.resolve(provider.provideFoldingRanges(model, foldingContext, cancellationToken)).then(ranges => {
70
if (cancellationToken.isCancellationRequested) {
71
return;
72
}
73
if (Array.isArray(ranges)) {
74
if (!Array.isArray(rangeData)) {
75
rangeData = [];
76
}
77
const nLines = model.getLineCount();
78
for (const r of ranges) {
79
if (r.start > 0 && r.end > r.start && r.end <= nLines) {
80
rangeData.push({ start: r.start, end: r.end, rank: i, kind: r.kind });
81
}
82
}
83
}
84
}, onUnexpectedExternalError);
85
});
86
return Promise.all(promises).then(_ => {
87
return rangeData;
88
});
89
}
90
91
class RangesCollector {
92
private readonly _startIndexes: number[];
93
private readonly _endIndexes: number[];
94
private readonly _nestingLevels: number[];
95
private readonly _nestingLevelCounts: number[];
96
private readonly _types: Array<string | undefined>;
97
private _length: number;
98
private readonly _foldingRangesLimit: FoldingLimitReporter;
99
100
constructor(foldingRangesLimit: FoldingLimitReporter) {
101
this._startIndexes = [];
102
this._endIndexes = [];
103
this._nestingLevels = [];
104
this._nestingLevelCounts = [];
105
this._types = [];
106
this._length = 0;
107
this._foldingRangesLimit = foldingRangesLimit;
108
}
109
110
public add(startLineNumber: number, endLineNumber: number, type: string | undefined, nestingLevel: number) {
111
if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) {
112
return;
113
}
114
const index = this._length;
115
this._startIndexes[index] = startLineNumber;
116
this._endIndexes[index] = endLineNumber;
117
this._nestingLevels[index] = nestingLevel;
118
this._types[index] = type;
119
this._length++;
120
if (nestingLevel < 30) {
121
this._nestingLevelCounts[nestingLevel] = (this._nestingLevelCounts[nestingLevel] || 0) + 1;
122
}
123
}
124
125
public toIndentRanges() {
126
const limit = this._foldingRangesLimit.limit;
127
if (this._length <= limit) {
128
this._foldingRangesLimit.update(this._length, false);
129
130
const startIndexes = new Uint32Array(this._length);
131
const endIndexes = new Uint32Array(this._length);
132
for (let i = 0; i < this._length; i++) {
133
startIndexes[i] = this._startIndexes[i];
134
endIndexes[i] = this._endIndexes[i];
135
}
136
return new FoldingRegions(startIndexes, endIndexes, this._types);
137
} else {
138
this._foldingRangesLimit.update(this._length, limit);
139
140
let entries = 0;
141
let maxLevel = this._nestingLevelCounts.length;
142
for (let i = 0; i < this._nestingLevelCounts.length; i++) {
143
const n = this._nestingLevelCounts[i];
144
if (n) {
145
if (n + entries > limit) {
146
maxLevel = i;
147
break;
148
}
149
entries += n;
150
}
151
}
152
153
const startIndexes = new Uint32Array(limit);
154
const endIndexes = new Uint32Array(limit);
155
const types: Array<string | undefined> = [];
156
for (let i = 0, k = 0; i < this._length; i++) {
157
const level = this._nestingLevels[i];
158
if (level < maxLevel || (level === maxLevel && entries++ < limit)) {
159
startIndexes[k] = this._startIndexes[i];
160
endIndexes[k] = this._endIndexes[i];
161
types[k] = this._types[i];
162
k++;
163
}
164
}
165
return new FoldingRegions(startIndexes, endIndexes, types);
166
}
167
168
}
169
170
}
171
172
export function sanitizeRanges(rangeData: IFoldingRangeData[], foldingRangesLimit: FoldingLimitReporter): FoldingRegions {
173
const sorted = rangeData.sort((d1, d2) => {
174
let diff = d1.start - d2.start;
175
if (diff === 0) {
176
diff = d1.rank - d2.rank;
177
}
178
return diff;
179
});
180
const collector = new RangesCollector(foldingRangesLimit);
181
182
let top: IFoldingRangeData | undefined = undefined;
183
const previous: IFoldingRangeData[] = [];
184
for (const entry of sorted) {
185
if (!top) {
186
top = entry;
187
collector.add(entry.start, entry.end, entry.kind && entry.kind.value, previous.length);
188
} else {
189
if (entry.start > top.start) {
190
if (entry.end <= top.end) {
191
previous.push(top);
192
top = entry;
193
collector.add(entry.start, entry.end, entry.kind && entry.kind.value, previous.length);
194
} else {
195
if (entry.start > top.end) {
196
do {
197
top = previous.pop();
198
} while (top && entry.start > top.end);
199
if (top) {
200
previous.push(top);
201
}
202
top = entry;
203
}
204
collector.add(entry.start, entry.end, entry.kind && entry.kind.value, previous.length);
205
}
206
}
207
}
208
}
209
return collector.toIndentRanges();
210
}
211
212