Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts
4780 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 assert from 'assert';
7
import { Barrier, timeout } from '../../../../../base/common/async.js';
8
import { CancellationToken } from '../../../../../base/common/cancellation.js';
9
import { Emitter, Event } from '../../../../../base/common/event.js';
10
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
11
import { mock } from '../../../../../base/test/common/mock.js';
12
import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js';
13
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
14
import { Range } from '../../../../common/core/range.js';
15
import { DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend } from '../../../../common/languages.js';
16
import { ILanguageService } from '../../../../common/languages/language.js';
17
import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js';
18
import { ITextModel } from '../../../../common/model.js';
19
import { LanguageFeatureDebounceService } from '../../../../common/services/languageFeatureDebounce.js';
20
import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js';
21
import { LanguageFeaturesService } from '../../../../common/services/languageFeaturesService.js';
22
import { LanguageService } from '../../../../common/services/languageService.js';
23
import { IModelService } from '../../../../common/services/model.js';
24
import { ModelService } from '../../../../common/services/modelService.js';
25
import { SemanticTokensStylingService } from '../../../../common/services/semanticTokensStylingService.js';
26
import { DocumentSemanticTokensFeature } from '../../browser/documentSemanticTokens.js';
27
import { getDocumentSemanticTokens, isSemanticTokens } from '../../common/getSemanticTokens.js';
28
import { TestLanguageConfigurationService } from '../../../../test/common/modes/testLanguageConfigurationService.js';
29
import { TestTextResourcePropertiesService } from '../../../../test/common/services/testTextResourcePropertiesService.js';
30
import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';
31
import { TestDialogService } from '../../../../../platform/dialogs/test/common/testDialogService.js';
32
import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js';
33
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
34
import { NullLogService } from '../../../../../platform/log/common/log.js';
35
import { TestNotificationService } from '../../../../../platform/notification/test/common/testNotificationService.js';
36
import { ColorScheme } from '../../../../../platform/theme/common/theme.js';
37
import { TestColorTheme, TestThemeService } from '../../../../../platform/theme/test/common/testThemeService.js';
38
import { UndoRedoService } from '../../../../../platform/undoRedo/common/undoRedoService.js';
39
import { ITreeSitterLibraryService } from '../../../../common/services/treeSitter/treeSitterLibraryService.js';
40
import { TestTreeSitterLibraryService } from '../../../../test/common/services/testTreeSitterLibraryService.js';
41
42
suite('ModelSemanticColoring', () => {
43
44
const disposables = new DisposableStore();
45
let modelService: IModelService;
46
let languageService: ILanguageService;
47
let languageFeaturesService: ILanguageFeaturesService;
48
49
setup(() => {
50
const configService = new TestConfigurationService({ editor: { semanticHighlighting: true } });
51
const themeService = new TestThemeService();
52
themeService.setTheme(new TestColorTheme({}, ColorScheme.DARK, true));
53
const logService = new NullLogService();
54
languageFeaturesService = new LanguageFeaturesService();
55
languageService = disposables.add(new LanguageService(false));
56
const semanticTokensStylingService = disposables.add(new SemanticTokensStylingService(themeService, logService, languageService));
57
const instantiationService = new TestInstantiationService();
58
instantiationService.set(ILanguageService, languageService);
59
instantiationService.set(ILanguageConfigurationService, new TestLanguageConfigurationService());
60
instantiationService.set(ITreeSitterLibraryService, new TestTreeSitterLibraryService());
61
modelService = disposables.add(new ModelService(
62
configService,
63
new TestTextResourcePropertiesService(configService),
64
new UndoRedoService(new TestDialogService(), new TestNotificationService()),
65
instantiationService
66
));
67
const envService = new class extends mock<IEnvironmentService>() {
68
override isBuilt: boolean = true;
69
override isExtensionDevelopment: boolean = false;
70
};
71
disposables.add(new DocumentSemanticTokensFeature(semanticTokensStylingService, modelService, themeService, configService, new LanguageFeatureDebounceService(logService, envService), languageFeaturesService));
72
});
73
74
teardown(() => {
75
disposables.clear();
76
});
77
78
ensureNoDisposablesAreLeakedInTestSuite();
79
80
test('DocumentSemanticTokens should be fetched when the result is empty if there are pending changes', async () => {
81
await runWithFakedTimers({}, async () => {
82
83
disposables.add(languageService.registerLanguage({ id: 'testMode' }));
84
85
const inFirstCall = new Barrier();
86
const delayFirstResult = new Barrier();
87
const secondResultProvided = new Barrier();
88
let callCount = 0;
89
90
disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode', new class implements DocumentSemanticTokensProvider {
91
getLegend(): SemanticTokensLegend {
92
return { tokenTypes: ['class'], tokenModifiers: [] };
93
}
94
async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise<SemanticTokens | SemanticTokensEdits | null> {
95
callCount++;
96
if (callCount === 1) {
97
assert.ok('called once');
98
inFirstCall.open();
99
await delayFirstResult.wait();
100
await timeout(0); // wait for the simple scheduler to fire to check that we do actually get rescheduled
101
return null;
102
}
103
if (callCount === 2) {
104
assert.ok('called twice');
105
secondResultProvided.open();
106
return null;
107
}
108
assert.fail('Unexpected call');
109
}
110
releaseDocumentSemanticTokens(resultId: string | undefined): void {
111
}
112
}));
113
114
const textModel = disposables.add(modelService.createModel('Hello world', languageService.createById('testMode')));
115
// pretend the text model is attached to an editor (so that semantic tokens are computed)
116
textModel.onBeforeAttached();
117
118
// wait for the provider to be called
119
await inFirstCall.wait();
120
121
// the provider is now in the provide call
122
// change the text buffer while the provider is running
123
textModel.applyEdits([{ range: new Range(1, 1, 1, 1), text: 'x' }]);
124
125
// let the provider finish its first result
126
delayFirstResult.open();
127
128
// we need to check that the provider is called again, even if it returns null
129
await secondResultProvided.wait();
130
131
// assert that it got called twice
132
assert.strictEqual(callCount, 2);
133
});
134
});
135
136
test('issue #149412: VS Code hangs when bad semantic token data is received', async () => {
137
await runWithFakedTimers({}, async () => {
138
139
disposables.add(languageService.registerLanguage({ id: 'testMode' }));
140
141
let lastResult: SemanticTokens | SemanticTokensEdits | null = null;
142
143
disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode', new class implements DocumentSemanticTokensProvider {
144
getLegend(): SemanticTokensLegend {
145
return { tokenTypes: ['class'], tokenModifiers: [] };
146
}
147
async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise<SemanticTokens | SemanticTokensEdits | null> {
148
if (!lastResultId) {
149
// this is the first call
150
lastResult = {
151
resultId: '1',
152
data: new Uint32Array([4294967293, 0, 7, 16, 0, 1, 4, 3, 11, 1])
153
};
154
} else {
155
// this is the second call
156
lastResult = {
157
resultId: '2',
158
edits: [{
159
start: 4294967276,
160
deleteCount: 0,
161
data: new Uint32Array([2, 0, 3, 11, 0])
162
}]
163
};
164
}
165
return lastResult;
166
}
167
releaseDocumentSemanticTokens(resultId: string | undefined): void {
168
}
169
}));
170
171
const textModel = disposables.add(modelService.createModel('', languageService.createById('testMode')));
172
// pretend the text model is attached to an editor (so that semantic tokens are computed)
173
textModel.onBeforeAttached();
174
175
// wait for the semantic tokens to be fetched
176
await Event.toPromise(textModel.onDidChangeTokens);
177
assert.strictEqual(lastResult!.resultId, '1');
178
179
// edit the text
180
textModel.applyEdits([{ range: new Range(1, 1, 1, 1), text: 'foo' }]);
181
182
// wait for the semantic tokens to be fetched again
183
await Event.toPromise(textModel.onDidChangeTokens);
184
assert.strictEqual(lastResult!.resultId, '2');
185
});
186
});
187
188
test('issue #161573: onDidChangeSemanticTokens doesn\'t consistently trigger provideDocumentSemanticTokens', async () => {
189
await runWithFakedTimers({}, async () => {
190
191
disposables.add(languageService.registerLanguage({ id: 'testMode' }));
192
193
const emitter = new Emitter<void>();
194
let requestCount = 0;
195
disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode', new class implements DocumentSemanticTokensProvider {
196
onDidChange = emitter.event;
197
getLegend(): SemanticTokensLegend {
198
return { tokenTypes: ['class'], tokenModifiers: [] };
199
}
200
async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise<SemanticTokens | SemanticTokensEdits | null> {
201
requestCount++;
202
if (requestCount === 1) {
203
await timeout(1000);
204
// send a change event
205
emitter.fire();
206
await timeout(1000);
207
return null;
208
}
209
return null;
210
}
211
releaseDocumentSemanticTokens(resultId: string | undefined): void {
212
}
213
}));
214
215
const textModel = disposables.add(modelService.createModel('', languageService.createById('testMode')));
216
// pretend the text model is attached to an editor (so that semantic tokens are computed)
217
textModel.onBeforeAttached();
218
219
await timeout(5000);
220
assert.deepStrictEqual(requestCount, 2);
221
});
222
});
223
224
test('DocumentSemanticTokens should be pick the token provider with actual items', async () => {
225
await runWithFakedTimers({}, async () => {
226
227
let callCount = 0;
228
disposables.add(languageService.registerLanguage({ id: 'testMode2' }));
229
disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode2', new class implements DocumentSemanticTokensProvider {
230
getLegend(): SemanticTokensLegend {
231
return { tokenTypes: ['class1'], tokenModifiers: [] };
232
}
233
async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise<SemanticTokens | SemanticTokensEdits | null> {
234
callCount++;
235
// For a secondary request return a different value
236
if (lastResultId) {
237
return {
238
data: new Uint32Array([2, 1, 1, 1, 1, 0, 2, 1, 1, 1])
239
};
240
}
241
return {
242
resultId: '1',
243
data: new Uint32Array([0, 1, 1, 1, 1, 0, 2, 1, 1, 1])
244
};
245
}
246
releaseDocumentSemanticTokens(resultId: string | undefined): void {
247
}
248
}));
249
disposables.add(languageFeaturesService.documentSemanticTokensProvider.register('testMode2', new class implements DocumentSemanticTokensProvider {
250
getLegend(): SemanticTokensLegend {
251
return { tokenTypes: ['class2'], tokenModifiers: [] };
252
}
253
async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise<SemanticTokens | SemanticTokensEdits | null> {
254
callCount++;
255
return null;
256
}
257
releaseDocumentSemanticTokens(resultId: string | undefined): void {
258
}
259
}));
260
261
function toArr(arr: Uint32Array): number[] {
262
const result: number[] = [];
263
for (let i = 0; i < arr.length; i++) {
264
result[i] = arr[i];
265
}
266
return result;
267
}
268
269
const textModel = modelService.createModel('Hello world 2', languageService.createById('testMode2'));
270
try {
271
let result = await getDocumentSemanticTokens(languageFeaturesService.documentSemanticTokensProvider, textModel, null, null, CancellationToken.None);
272
assert.ok(result, `We should have tokens (1)`);
273
assert.ok(result.tokens, `Tokens are found from multiple providers (1)`);
274
assert.ok(isSemanticTokens(result.tokens), `Tokens are full (1)`);
275
assert.ok(result.tokens.resultId, `Token result id found from multiple providers (1)`);
276
assert.deepStrictEqual(toArr(result.tokens.data), [0, 1, 1, 1, 1, 0, 2, 1, 1, 1], `Token data returned for multiple providers (1)`);
277
assert.deepStrictEqual(callCount, 2, `Called both token providers (1)`);
278
assert.deepStrictEqual(result.provider.getLegend(), { tokenTypes: ['class1'], tokenModifiers: [] }, `Legend matches the tokens (1)`);
279
280
// Make a second request. Make sure we get the secondary value
281
result = await getDocumentSemanticTokens(languageFeaturesService.documentSemanticTokensProvider, textModel, result.provider, result.tokens.resultId, CancellationToken.None);
282
assert.ok(result, `We should have tokens (2)`);
283
assert.ok(result.tokens, `Tokens are found from multiple providers (2)`);
284
assert.ok(isSemanticTokens(result.tokens), `Tokens are full (2)`);
285
assert.ok(!result.tokens.resultId, `Token result id found from multiple providers (2)`);
286
assert.deepStrictEqual(toArr(result.tokens.data), [2, 1, 1, 1, 1, 0, 2, 1, 1, 1], `Token data returned for multiple providers (2)`);
287
assert.deepStrictEqual(callCount, 4, `Called both token providers (2)`);
288
assert.deepStrictEqual(result.provider.getLegend(), { tokenTypes: ['class1'], tokenModifiers: [] }, `Legend matches the tokens (2)`);
289
} finally {
290
disposables.clear();
291
292
// Wait for scheduler to finish
293
await timeout(0);
294
295
// Now dispose the text model
296
textModel.dispose();
297
}
298
});
299
});
300
});
301
302