Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/test/fixtures/bracketPairsTree.ts
13406 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 } from 'vs/base/common/event';
7
import { Disposable } from 'vs/base/common/lifecycle';
8
import { Range } from 'vs/editor/common/core/range';
9
import { ITextModel } from 'vs/editor/common/model';
10
import { BracketInfo, BracketPairWithMinIndentationInfo, IFoundBracket } from 'vs/editor/common/textModelBracketPairs';
11
import { TextModel } from 'vs/editor/common/model/textModel';
12
import { IModelContentChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents';
13
import { ResolvedLanguageConfiguration } from 'vs/editor/common/languages/languageConfigurationRegistry';
14
import { AstNode, AstNodeKind } from './ast';
15
import { TextEditInfo } from './beforeEditPositionMapper';
16
import { LanguageAgnosticBracketTokens } from './brackets';
17
import { Length, lengthAdd, lengthGreaterThanEqual, lengthLessThan, lengthLessThanEqual, lengthsToRange, lengthZero, positionToLength, toLength } from './length';
18
import { parseDocument } from './parser';
19
import { DenseKeyProvider } from './smallImmutableSet';
20
import { FastTokenizer, TextBufferTokenizer } from './tokenizer';
21
import { BackgroundTokenizationState } from 'vs/editor/common/tokenizationTextModelPart';
22
import { Position } from 'vs/editor/common/core/position';
23
import { CallbackIterable } from 'vs/base/common/arrays';
24
import { combineTextEditInfos } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos';
25
import { ClosingBracketKind, OpeningBracketKind } from 'vs/editor/common/languages/supports/languageBracketsConfiguration';
26
27
export class BracketPairsTree extends Disposable {
28
private readonly didChangeEmitter = new Emitter<void>();
29
30
/*
31
There are two trees:
32
* The initial tree that has no token information and is used for performant initial bracket colorization.
33
* The tree that used token information to detect bracket pairs.
34
35
To prevent flickering, we only switch from the initial tree to tree with token information
36
when tokenization completes.
37
Since the text can be edited while background tokenization is in progress, we need to update both trees.
38
*/
39
private initialAstWithoutTokens: AstNode | undefined;
40
private astWithTokens: AstNode | undefined;
41
42
private readonly denseKeyProvider = new DenseKeyProvider<string>();
43
private readonly brackets = new LanguageAgnosticBracketTokens(this.denseKeyProvider, this.getLanguageConfiguration);
44
45
public didLanguageChange(languageId: string): boolean {
46
return this.brackets.didLanguageChange(languageId);
47
}
48
49
public readonly onDidChange = this.didChangeEmitter.event;
50
private queuedTextEditsForInitialAstWithoutTokens: TextEditInfo[] = [];
51
private queuedTextEdits: TextEditInfo[] = [];
52
53
public constructor(
54
private readonly textModel: TextModel,
55
private readonly getLanguageConfiguration: (languageId: string) => ResolvedLanguageConfiguration
56
) {
57
super();
58
59
if (!textModel.tokenization.hasTokens) {
60
const brackets = this.brackets.getSingleLanguageBracketTokens(this.textModel.getLanguageId());
61
const tokenizer = new FastTokenizer(this.textModel.getValue(), brackets);
62
this.initialAstWithoutTokens = parseDocument(tokenizer, [], undefined, true);
63
this.astWithTokens = this.initialAstWithoutTokens;
64
} else if (textModel.tokenization.backgroundTokenizationState === BackgroundTokenizationState.Completed) {
65
// Skip the initial ast, as there is no flickering.
66
// Directly create the tree with token information.
67
this.initialAstWithoutTokens = undefined;
68
this.astWithTokens = this.parseDocumentFromTextBuffer([], undefined, false);
69
} else {
70
// We missed some token changes already, so we cannot use the fast tokenizer + delta increments
71
this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer([], undefined, true);
72
this.astWithTokens = this.initialAstWithoutTokens;
73
}
74
}
75
76
//#region TextModel events
77
78
public handleDidChangeBackgroundTokenizationState(): void {
79
if (this.textModel.tokenization.backgroundTokenizationState === BackgroundTokenizationState.Completed) {
80
const wasUndefined = this.initialAstWithoutTokens === undefined;
81
// Clear the initial tree as we can use the tree with token information now.
82
this.initialAstWithoutTokens = undefined;
83
if (!wasUndefined) {
84
this.didChangeEmitter.fire();
85
}
86
}
87
}
88
89
public handleDidChangeTokens({ ranges }: IModelTokensChangedEvent): void {
90
const edits = ranges.map(r =>
91
new TextEditInfo(
92
toLength(r.fromLineNumber - 1, 0),
93
toLength(r.toLineNumber, 0),
94
toLength(r.toLineNumber - r.fromLineNumber + 1, 0)
95
)
96
);
97
98
this.handleEdits(edits, true);
99
100
if (!this.initialAstWithoutTokens) {
101
this.didChangeEmitter.fire();
102
}
103
}
104
105
public handleContentChanged(change: IModelContentChangedEvent) {
106
const edits = TextEditInfo.fromModelContentChanges(change.changes);
107
this.handleEdits(edits, false);
108
}
109
110
private handleEdits(edits: TextEditInfo[], tokenChange: boolean): void {
111
// Lazily queue the edits and only apply them when the tree is accessed.
112
const result = combineTextEditInfos(this.queuedTextEdits, edits);
113
114
this.queuedTextEdits = result;
115
if (this.initialAstWithoutTokens && !tokenChange) {
116
this.queuedTextEditsForInitialAstWithoutTokens = combineTextEditInfos(this.queuedTextEditsForInitialAstWithoutTokens, edits);
117
}
118
}
119
120
//#endregion
121
122
private flushQueue() {
123
if (this.queuedTextEdits.length > 0) {
124
this.astWithTokens = this.parseDocumentFromTextBuffer(this.queuedTextEdits, this.astWithTokens, false);
125
this.queuedTextEdits = [];
126
}
127
if (this.queuedTextEditsForInitialAstWithoutTokens.length > 0) {
128
if (this.initialAstWithoutTokens) {
129
this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer(this.queuedTextEditsForInitialAstWithoutTokens, this.initialAstWithoutTokens, false);
130
}
131
this.queuedTextEditsForInitialAstWithoutTokens = [];
132
}
133
}
134
135
/**
136
* @pure (only if isPure = true)
137
*/
138
private parseDocumentFromTextBuffer(edits: TextEditInfo[], previousAst: AstNode | undefined, immutable: boolean): AstNode {
139
// Is much faster if `isPure = false`.
140
const isPure = false;
141
const previousAstClone = isPure ? previousAst?.deepClone() : previousAst;
142
const tokenizer = new TextBufferTokenizer(this.textModel, this.brackets);
143
const result = parseDocument(tokenizer, edits, previousAstClone, immutable);
144
return result;
145
}
146
147
public getBracketsInRange(range: Range, onlyColorizedBrackets: boolean): CallbackIterable<BracketInfo> {
148
this.flushQueue();
149
150
const startOffset = toLength(range.startLineNumber - 1, range.startColumn - 1);
151
const endOffset = toLength(range.endLineNumber - 1, range.endColumn - 1);
152
return new CallbackIterable(cb => {
153
const node = this.initialAstWithoutTokens || this.astWithTokens!;
154
collectBrackets(node, lengthZero, node.length, startOffset, endOffset, cb, 0, 0, new Map(), onlyColorizedBrackets);
155
});
156
}
157
158
public getBracketPairsInRange(range: Range, includeMinIndentation: boolean): CallbackIterable<BracketPairWithMinIndentationInfo> {
159
this.flushQueue();
160
161
const startLength = positionToLength(range.getStartPosition());
162
const endLength = positionToLength(range.getEndPosition());
163
164
return new CallbackIterable(cb => {
165
const node = this.initialAstWithoutTokens || this.astWithTokens!;
166
const context = new CollectBracketPairsContext(cb, includeMinIndentation, this.textModel);
167
collectBracketPairs(node, lengthZero, node.length, startLength, endLength, context, 0, new Map());
168
});
169
}
170
171
public getFirstBracketAfter(position: Position): IFoundBracket | null {
172
this.flushQueue();
173
174
const node = this.initialAstWithoutTokens || this.astWithTokens!;
175
return getFirstBracketAfter(node, lengthZero, node.length, positionToLength(position));
176
}
177
178
public getFirstBracketBefore(position: Position): IFoundBracket | null {
179
this.flushQueue();
180
181
const node = this.initialAstWithoutTokens || this.astWithTokens!;
182
return getFirstBracketBefore(node, lengthZero, node.length, positionToLength(position));
183
}
184
}
185
186
function getFirstBracketBefore(node: AstNode, nodeOffsetStart: Length, nodeOffsetEnd: Length, position: Length): IFoundBracket | null {
187
if (node.kind === AstNodeKind.List || node.kind === AstNodeKind.Pair) {
188
const lengths: { nodeOffsetStart: Length; nodeOffsetEnd: Length }[] = [];
189
for (const child of node.children) {
190
nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
191
lengths.push({ nodeOffsetStart, nodeOffsetEnd });
192
nodeOffsetStart = nodeOffsetEnd;
193
}
194
for (let i = lengths.length - 1; i >= 0; i--) {
195
const { nodeOffsetStart, nodeOffsetEnd } = lengths[i];
196
if (lengthLessThan(nodeOffsetStart, position)) {
197
const result = getFirstBracketBefore(node.children[i], nodeOffsetStart, nodeOffsetEnd, position);
198
if (result) {
199
return result;
200
}
201
}
202
}
203
return null;
204
} else if (node.kind === AstNodeKind.UnexpectedClosingBracket) {
205
return null;
206
} else if (node.kind === AstNodeKind.Bracket) {
207
const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);
208
return {
209
bracketInfo: node.bracketInfo,
210
range
211
};
212
}
213
return null;
214
}
215
216
function getFirstBracketAfter(node: AstNode, nodeOffsetStart: Length, nodeOffsetEnd: Length, position: Length): IFoundBracket | null {
217
if (node.kind === AstNodeKind.List || node.kind === AstNodeKind.Pair) {
218
for (const child of node.children) {
219
nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
220
if (lengthLessThan(position, nodeOffsetEnd)) {
221
const result = getFirstBracketAfter(child, nodeOffsetStart, nodeOffsetEnd, position);
222
if (result) {
223
return result;
224
}
225
}
226
nodeOffsetStart = nodeOffsetEnd;
227
}
228
return null;
229
} else if (node.kind === AstNodeKind.UnexpectedClosingBracket) {
230
return null;
231
} else if (node.kind === AstNodeKind.Bracket) {
232
const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);
233
return {
234
bracketInfo: node.bracketInfo,
235
range
236
};
237
}
238
return null;
239
}
240
241
function collectBrackets(
242
node: AstNode,
243
nodeOffsetStart: Length,
244
nodeOffsetEnd: Length,
245
startOffset: Length,
246
endOffset: Length,
247
push: (item: BracketInfo) => boolean,
248
level: number,
249
nestingLevelOfEqualBracketType: number,
250
levelPerBracketType: Map<string, number>,
251
onlyColorizedBrackets: boolean,
252
parentPairIsIncomplete: boolean = false,
253
): boolean {
254
if (level > 200) {
255
return true;
256
}
257
258
whileLoop:
259
while (true) {
260
switch (node.kind) {
261
case AstNodeKind.List: {
262
const childCount = node.childrenLength;
263
for (let i = 0; i < childCount; i++) {
264
const child = node.getChild(i);
265
if (!child) {
266
continue;
267
}
268
nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
269
if (
270
lengthLessThanEqual(nodeOffsetStart, endOffset) &&
271
lengthGreaterThanEqual(nodeOffsetEnd, startOffset)
272
) {
273
const childEndsAfterEnd = lengthGreaterThanEqual(nodeOffsetEnd, endOffset);
274
if (childEndsAfterEnd) {
275
// No child after this child in the requested window, don't recurse
276
node = child;
277
continue whileLoop;
278
}
279
280
const shouldContinue = collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, push, level, 0, levelPerBracketType, onlyColorizedBrackets);
281
if (!shouldContinue) {
282
return false;
283
}
284
}
285
nodeOffsetStart = nodeOffsetEnd;
286
}
287
return true;
288
}
289
case AstNodeKind.Pair: {
290
const colorize = !onlyColorizedBrackets || !node.closingBracket || (node.closingBracket.bracketInfo as ClosingBracketKind).closesColorized(node.openingBracket.bracketInfo as OpeningBracketKind);
291
292
let levelPerBracket = 0;
293
if (levelPerBracketType) {
294
let existing = levelPerBracketType.get(node.openingBracket.text);
295
if (existing === undefined) {
296
existing = 0;
297
}
298
levelPerBracket = existing;
299
if (colorize) {
300
existing++;
301
levelPerBracketType.set(node.openingBracket.text, existing);
302
}
303
}
304
305
const childCount = node.childrenLength;
306
for (let i = 0; i < childCount; i++) {
307
const child = node.getChild(i);
308
if (!child) {
309
continue;
310
}
311
nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
312
if (
313
lengthLessThanEqual(nodeOffsetStart, endOffset) &&
314
lengthGreaterThanEqual(nodeOffsetEnd, startOffset)
315
) {
316
const childEndsAfterEnd = lengthGreaterThanEqual(nodeOffsetEnd, endOffset);
317
if (childEndsAfterEnd && child.kind !== AstNodeKind.Bracket) {
318
// No child after this child in the requested window, don't recurse
319
// Don't do this for brackets because of unclosed/unopened brackets
320
node = child;
321
if (colorize) {
322
level++;
323
nestingLevelOfEqualBracketType = levelPerBracket + 1;
324
} else {
325
nestingLevelOfEqualBracketType = levelPerBracket;
326
}
327
continue whileLoop;
328
}
329
330
if (colorize || child.kind !== AstNodeKind.Bracket || !node.closingBracket) {
331
const shouldContinue = collectBrackets(
332
child,
333
nodeOffsetStart,
334
nodeOffsetEnd,
335
startOffset,
336
endOffset,
337
push,
338
colorize ? level + 1 : level,
339
colorize ? levelPerBracket + 1 : levelPerBracket,
340
levelPerBracketType,
341
onlyColorizedBrackets,
342
!node.closingBracket,
343
);
344
if (!shouldContinue) {
345
return false;
346
}
347
}
348
}
349
nodeOffsetStart = nodeOffsetEnd;
350
}
351
352
levelPerBracketType?.set(node.openingBracket.text, levelPerBracket);
353
354
return true;
355
}
356
case AstNodeKind.UnexpectedClosingBracket: {
357
const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);
358
return push(new BracketInfo(range, level - 1, 0, true));
359
}
360
case AstNodeKind.Bracket: {
361
const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);
362
return push(new BracketInfo(range, level - 1, nestingLevelOfEqualBracketType - 1, parentPairIsIncomplete));
363
}
364
case AstNodeKind.Text:
365
return true;
366
}
367
}
368
}
369
370
class CollectBracketPairsContext {
371
constructor(
372
public readonly push: (item: BracketPairWithMinIndentationInfo) => boolean,
373
public readonly includeMinIndentation: boolean,
374
public readonly textModel: ITextModel,
375
) {
376
}
377
}
378
379
function collectBracketPairs(
380
node: AstNode,
381
nodeOffsetStart: Length,
382
nodeOffsetEnd: Length,
383
startOffset: Length,
384
endOffset: Length,
385
context: CollectBracketPairsContext,
386
level: number,
387
levelPerBracketType: Map<string, number>
388
): boolean {
389
if (level > 200) {
390
return true;
391
}
392
393
let shouldContinue = true;
394
395
if (node.kind === AstNodeKind.Pair) {
396
let levelPerBracket = 0;
397
if (levelPerBracketType) {
398
let existing = levelPerBracketType.get(node.openingBracket.text);
399
if (existing === undefined) {
400
existing = 0;
401
}
402
levelPerBracket = existing;
403
existing++;
404
levelPerBracketType.set(node.openingBracket.text, existing);
405
}
406
407
const openingBracketEnd = lengthAdd(nodeOffsetStart, node.openingBracket.length);
408
let minIndentation = -1;
409
if (context.includeMinIndentation) {
410
minIndentation = node.computeMinIndentation(
411
nodeOffsetStart,
412
context.textModel
413
);
414
}
415
416
shouldContinue = context.push(
417
new BracketPairWithMinIndentationInfo(
418
lengthsToRange(nodeOffsetStart, nodeOffsetEnd),
419
lengthsToRange(nodeOffsetStart, openingBracketEnd),
420
node.closingBracket
421
? lengthsToRange(
422
lengthAdd(openingBracketEnd, node.child?.length || lengthZero),
423
nodeOffsetEnd
424
)
425
: undefined,
426
level,
427
levelPerBracket,
428
node,
429
minIndentation
430
)
431
);
432
433
nodeOffsetStart = openingBracketEnd;
434
if (shouldContinue && node.child) {
435
const child = node.child;
436
nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length);
437
if (
438
lengthLessThanEqual(nodeOffsetStart, endOffset) &&
439
lengthGreaterThanEqual(nodeOffsetEnd, startOffset)
440
) {
441
shouldContinue = collectBracketPairs(
442
child,
443
nodeOffsetStart,
444
nodeOffsetEnd,
445
startOffset,
446
endOffset,
447
context,
448
level + 1,
449
levelPerBracketType
450
);
451
if (!shouldContinue) {
452
return false;
453
}
454
}
455
}
456
457
levelPerBracketType?.set(node.openingBracket.text, levelPerBracket);
458
} else {
459
let curOffset = nodeOffsetStart;
460
for (const child of node.children) {
461
const childOffset = curOffset;
462
curOffset = lengthAdd(curOffset, child.length);
463
464
if (
465
lengthLessThanEqual(childOffset, endOffset) &&
466
lengthLessThanEqual(startOffset, curOffset)
467
) {
468
shouldContinue = collectBracketPairs(
469
child,
470
childOffset,
471
curOffset,
472
startOffset,
473
endOffset,
474
context,
475
level,
476
levelPerBracketType
477
);
478
if (!shouldContinue) {
479
return false;
480
}
481
}
482
}
483
}
484
return shouldContinue;
485
}
486
487