Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/model/tokens/tokenizerSyntaxTokenBackend.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 { onUnexpectedError } from '../../../../base/common/errors.js';
7
import { Emitter, Event } from '../../../../base/common/event.js';
8
import { MutableDisposable, DisposableMap } from '../../../../base/common/lifecycle.js';
9
import { countEOL } from '../../core/misc/eolCounter.js';
10
import { Position } from '../../core/position.js';
11
import { LineRange } from '../../core/ranges/lineRange.js';
12
import { StandardTokenType } from '../../encodedTokenAttributes.js';
13
import { IBackgroundTokenizer, IState, ILanguageIdCodec, TokenizationRegistry, ITokenizationSupport, IBackgroundTokenizationStore } from '../../languages.js';
14
import { IAttachedView } from '../../model.js';
15
import { IModelContentChangedEvent } from '../../textModelEvents.js';
16
import { BackgroundTokenizationState } from '../../tokenizationTextModelPart.js';
17
import { ContiguousMultilineTokens } from '../../tokens/contiguousMultilineTokens.js';
18
import { ContiguousMultilineTokensBuilder } from '../../tokens/contiguousMultilineTokensBuilder.js';
19
import { ContiguousTokensStore } from '../../tokens/contiguousTokensStore.js';
20
import { LineTokens } from '../../tokens/lineTokens.js';
21
import { TextModel } from '../textModel.js';
22
import { TokenizerWithStateStoreAndTextModel, DefaultBackgroundTokenizer, TrackingTokenizationStateStore } from '../textModelTokens.js';
23
import { AbstractSyntaxTokenBackend, AttachedViewHandler, AttachedViews } from './abstractSyntaxTokenBackend.js';
24
25
/** For TextMate */
26
export class TokenizerSyntaxTokenBackend extends AbstractSyntaxTokenBackend {
27
private _tokenizer: TokenizerWithStateStoreAndTextModel | null = null;
28
protected _backgroundTokenizationState: BackgroundTokenizationState = BackgroundTokenizationState.InProgress;
29
protected readonly _onDidChangeBackgroundTokenizationState: Emitter<void> = this._register(new Emitter<void>());
30
public readonly onDidChangeBackgroundTokenizationState: Event<void> = this._onDidChangeBackgroundTokenizationState.event;
31
32
private _defaultBackgroundTokenizer: DefaultBackgroundTokenizer | null = null;
33
private readonly _backgroundTokenizer = this._register(new MutableDisposable<IBackgroundTokenizer>());
34
35
private readonly _tokens = new ContiguousTokensStore(this._languageIdCodec);
36
private _debugBackgroundTokens: ContiguousTokensStore | undefined;
37
private _debugBackgroundStates: TrackingTokenizationStateStore<IState> | undefined;
38
39
private readonly _debugBackgroundTokenizer = this._register(new MutableDisposable<IBackgroundTokenizer>());
40
41
private readonly _attachedViewStates = this._register(new DisposableMap<IAttachedView, AttachedViewHandler>());
42
43
constructor(
44
languageIdCodec: ILanguageIdCodec,
45
textModel: TextModel,
46
private readonly getLanguageId: () => string,
47
attachedViews: AttachedViews,
48
) {
49
super(languageIdCodec, textModel);
50
51
this._register(TokenizationRegistry.onDidChange((e) => {
52
const languageId = this.getLanguageId();
53
if (e.changedLanguages.indexOf(languageId) === -1) {
54
return;
55
}
56
this.todo_resetTokenization();
57
}));
58
59
this.todo_resetTokenization();
60
61
this._register(attachedViews.onDidChangeVisibleRanges(({ view, state }) => {
62
if (state) {
63
let existing = this._attachedViewStates.get(view);
64
if (!existing) {
65
existing = new AttachedViewHandler(() => this.refreshRanges(existing!.lineRanges));
66
this._attachedViewStates.set(view, existing);
67
}
68
existing.handleStateChange(state);
69
} else {
70
this._attachedViewStates.deleteAndDispose(view);
71
}
72
}));
73
}
74
75
public todo_resetTokenization(fireTokenChangeEvent: boolean = true): void {
76
this._tokens.flush();
77
this._debugBackgroundTokens?.flush();
78
if (this._debugBackgroundStates) {
79
this._debugBackgroundStates = new TrackingTokenizationStateStore(this._textModel.getLineCount());
80
}
81
if (fireTokenChangeEvent) {
82
this._onDidChangeTokens.fire({
83
semanticTokensApplied: false,
84
ranges: [
85
{
86
fromLineNumber: 1,
87
toLineNumber: this._textModel.getLineCount(),
88
},
89
],
90
});
91
}
92
93
const initializeTokenization = (): [ITokenizationSupport, IState] | [null, null] => {
94
if (this._textModel.isTooLargeForTokenization()) {
95
return [null, null];
96
}
97
const tokenizationSupport = TokenizationRegistry.get(this.getLanguageId());
98
if (!tokenizationSupport) {
99
return [null, null];
100
}
101
let initialState: IState;
102
try {
103
initialState = tokenizationSupport.getInitialState();
104
} catch (e) {
105
onUnexpectedError(e);
106
return [null, null];
107
}
108
return [tokenizationSupport, initialState];
109
};
110
111
const [tokenizationSupport, initialState] = initializeTokenization();
112
if (tokenizationSupport && initialState) {
113
this._tokenizer = new TokenizerWithStateStoreAndTextModel(this._textModel.getLineCount(), tokenizationSupport, this._textModel, this._languageIdCodec);
114
} else {
115
this._tokenizer = null;
116
}
117
118
this._backgroundTokenizer.clear();
119
120
this._defaultBackgroundTokenizer = null;
121
if (this._tokenizer) {
122
const b: IBackgroundTokenizationStore = {
123
setTokens: (tokens) => {
124
this.setTokens(tokens);
125
},
126
backgroundTokenizationFinished: () => {
127
if (this._backgroundTokenizationState === BackgroundTokenizationState.Completed) {
128
// We already did a full tokenization and don't go back to progressing.
129
return;
130
}
131
const newState = BackgroundTokenizationState.Completed;
132
this._backgroundTokenizationState = newState;
133
this._onDidChangeBackgroundTokenizationState.fire();
134
},
135
setEndState: (lineNumber, state) => {
136
if (!this._tokenizer) { return; }
137
const firstInvalidEndStateLineNumber = this._tokenizer.store.getFirstInvalidEndStateLineNumber();
138
// Don't accept states for definitely valid states, the renderer is ahead of the worker!
139
if (firstInvalidEndStateLineNumber !== null && lineNumber >= firstInvalidEndStateLineNumber) {
140
this._tokenizer?.store.setEndState(lineNumber, state);
141
}
142
},
143
};
144
145
if (tokenizationSupport && tokenizationSupport.createBackgroundTokenizer && !tokenizationSupport.backgroundTokenizerShouldOnlyVerifyTokens) {
146
this._backgroundTokenizer.value = tokenizationSupport.createBackgroundTokenizer(this._textModel, b);
147
}
148
if (!this._backgroundTokenizer.value && !this._textModel.isTooLargeForTokenization()) {
149
this._backgroundTokenizer.value = this._defaultBackgroundTokenizer =
150
new DefaultBackgroundTokenizer(this._tokenizer, b);
151
this._defaultBackgroundTokenizer.handleChanges();
152
}
153
154
if (tokenizationSupport?.backgroundTokenizerShouldOnlyVerifyTokens && tokenizationSupport.createBackgroundTokenizer) {
155
this._debugBackgroundTokens = new ContiguousTokensStore(this._languageIdCodec);
156
this._debugBackgroundStates = new TrackingTokenizationStateStore(this._textModel.getLineCount());
157
this._debugBackgroundTokenizer.clear();
158
this._debugBackgroundTokenizer.value = tokenizationSupport.createBackgroundTokenizer(this._textModel, {
159
setTokens: (tokens) => {
160
this._debugBackgroundTokens?.setMultilineTokens(tokens, this._textModel);
161
},
162
backgroundTokenizationFinished() {
163
// NO OP
164
},
165
setEndState: (lineNumber, state) => {
166
this._debugBackgroundStates?.setEndState(lineNumber, state);
167
},
168
});
169
} else {
170
this._debugBackgroundTokens = undefined;
171
this._debugBackgroundStates = undefined;
172
this._debugBackgroundTokenizer.value = undefined;
173
}
174
}
175
176
this.refreshAllVisibleLineTokens();
177
}
178
179
public handleDidChangeAttached() {
180
this._defaultBackgroundTokenizer?.handleChanges();
181
}
182
183
public handleDidChangeContent(e: IModelContentChangedEvent): void {
184
if (e.isFlush) {
185
// Don't fire the event, as the view might not have got the text change event yet
186
this.todo_resetTokenization(false);
187
} else if (!e.isEolChange) { // We don't have to do anything on an EOL change
188
for (const c of e.changes) {
189
const [eolCount, firstLineLength] = countEOL(c.text);
190
191
this._tokens.acceptEdit(c.range, eolCount, firstLineLength);
192
this._debugBackgroundTokens?.acceptEdit(c.range, eolCount, firstLineLength);
193
}
194
this._debugBackgroundStates?.acceptChanges(e.changes);
195
196
if (this._tokenizer) {
197
this._tokenizer.store.acceptChanges(e.changes);
198
}
199
this._defaultBackgroundTokenizer?.handleChanges();
200
}
201
}
202
203
private setTokens(tokens: ContiguousMultilineTokens[]): { changes: { fromLineNumber: number; toLineNumber: number }[] } {
204
const { changes } = this._tokens.setMultilineTokens(tokens, this._textModel);
205
206
if (changes.length > 0) {
207
this._onDidChangeTokens.fire({ semanticTokensApplied: false, ranges: changes, });
208
}
209
210
return { changes: changes };
211
}
212
213
private refreshAllVisibleLineTokens(): void {
214
const ranges = LineRange.joinMany([...this._attachedViewStates].map(([_, s]) => s.lineRanges));
215
this.refreshRanges(ranges);
216
}
217
218
private refreshRanges(ranges: readonly LineRange[]): void {
219
for (const range of ranges) {
220
this.refreshRange(range.startLineNumber, range.endLineNumberExclusive - 1);
221
}
222
}
223
224
private refreshRange(startLineNumber: number, endLineNumber: number): void {
225
if (!this._tokenizer) {
226
return;
227
}
228
229
startLineNumber = Math.max(1, Math.min(this._textModel.getLineCount(), startLineNumber));
230
endLineNumber = Math.min(this._textModel.getLineCount(), endLineNumber);
231
232
const builder = new ContiguousMultilineTokensBuilder();
233
const { heuristicTokens } = this._tokenizer.tokenizeHeuristically(builder, startLineNumber, endLineNumber);
234
const changedTokens = this.setTokens(builder.finalize());
235
236
if (heuristicTokens) {
237
// We overrode tokens with heuristically computed ones.
238
// Because old states might get reused (thus stopping invalidation),
239
// we have to explicitly request the tokens for the changed ranges again.
240
for (const c of changedTokens.changes) {
241
this._backgroundTokenizer.value?.requestTokens(c.fromLineNumber, c.toLineNumber + 1);
242
}
243
}
244
245
this._defaultBackgroundTokenizer?.checkFinished();
246
}
247
248
public forceTokenization(lineNumber: number): void {
249
const builder = new ContiguousMultilineTokensBuilder();
250
this._tokenizer?.updateTokensUntilLine(builder, lineNumber);
251
this.setTokens(builder.finalize());
252
this._defaultBackgroundTokenizer?.checkFinished();
253
}
254
255
public hasAccurateTokensForLine(lineNumber: number): boolean {
256
if (!this._tokenizer) {
257
return true;
258
}
259
return this._tokenizer.hasAccurateTokensForLine(lineNumber);
260
}
261
262
public isCheapToTokenize(lineNumber: number): boolean {
263
if (!this._tokenizer) {
264
return true;
265
}
266
return this._tokenizer.isCheapToTokenize(lineNumber);
267
}
268
269
public getLineTokens(lineNumber: number): LineTokens {
270
const lineText = this._textModel.getLineContent(lineNumber);
271
const result = this._tokens.getTokens(
272
this._textModel.getLanguageId(),
273
lineNumber - 1,
274
lineText
275
);
276
if (this._debugBackgroundTokens && this._debugBackgroundStates && this._tokenizer) {
277
if (this._debugBackgroundStates.getFirstInvalidEndStateLineNumberOrMax() > lineNumber && this._tokenizer.store.getFirstInvalidEndStateLineNumberOrMax() > lineNumber) {
278
const backgroundResult = this._debugBackgroundTokens.getTokens(
279
this._textModel.getLanguageId(),
280
lineNumber - 1,
281
lineText
282
);
283
if (!result.equals(backgroundResult) && this._debugBackgroundTokenizer.value?.reportMismatchingTokens) {
284
this._debugBackgroundTokenizer.value.reportMismatchingTokens(lineNumber);
285
}
286
}
287
}
288
return result;
289
}
290
291
public getTokenTypeIfInsertingCharacter(lineNumber: number, column: number, character: string): StandardTokenType {
292
if (!this._tokenizer) {
293
return StandardTokenType.Other;
294
}
295
296
const position = this._textModel.validatePosition(new Position(lineNumber, column));
297
this.forceTokenization(position.lineNumber);
298
return this._tokenizer.getTokenTypeIfInsertingCharacter(position, character);
299
}
300
301
302
public tokenizeLinesAt(lineNumber: number, lines: string[]): LineTokens[] | null {
303
if (!this._tokenizer) {
304
return null;
305
}
306
this.forceTokenization(lineNumber);
307
return this._tokenizer.tokenizeLinesAt(lineNumber, lines);
308
}
309
310
public get hasTokens(): boolean {
311
return this._tokens.hasTokens;
312
}
313
}
314
315