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.summarized.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
for (let i = lengths.length - 1; i >= 0; i--) {}
191
} else if (node.kind === AstNodeKind.UnexpectedClosingBracket) {} else if (node.kind === AstNodeKind.Bracket) {}
192
return null;
193
}
194
195
function getFirstBracketAfter(node: AstNode, nodeOffsetStart: Length, nodeOffsetEnd: Length, position: Length): IFoundBracket | null {
196
if (node.kind === AstNodeKind.List || node.kind === AstNodeKind.Pair) {} else if (node.kind === AstNodeKind.UnexpectedClosingBracket) {} else if (node.kind === AstNodeKind.Bracket) {}
197
}
198
199
class CollectBracketPairsContext {
200
constructor(
201
public readonly push: (item: BracketPairWithMinIndentationInfo) => boolean,
202
public readonly includeMinIndentation: boolean,
203
public readonly textModel: ITextModel,
204
) {
205
}
206
}
207
208
209