Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/test/common/model/tokensStore.test.ts
5251 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 { DisposableStore } from '../../../../base/common/lifecycle.js';
8
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
9
import { ISingleEditOperation } from '../../../common/core/editOperation.js';
10
import { Position } from '../../../common/core/position.js';
11
import { Range } from '../../../common/core/range.js';
12
import { ColorId, FontStyle, MetadataConsts, TokenMetadata } from '../../../common/encodedTokenAttributes.js';
13
import { ILanguageConfigurationService, LanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
14
import { TextModel } from '../../../common/model/textModel.js';
15
import { LanguageIdCodec } from '../../../common/services/languagesRegistry.js';
16
import { LineTokens } from '../../../common/tokens/lineTokens.js';
17
import { SparseMultilineTokens } from '../../../common/tokens/sparseMultilineTokens.js';
18
import { SparseTokensStore } from '../../../common/tokens/sparseTokensStore.js';
19
import { createModelServices, createTextModel, instantiateTextModel } from '../testTextModel.js';
20
21
suite('TokensStore', () => {
22
23
ensureNoDisposablesAreLeakedInTestSuite();
24
25
const SEMANTIC_COLOR = 5 as ColorId;
26
27
function parseTokensState(state: string[]): { text: string; tokens: SparseMultilineTokens } {
28
const text: string[] = [];
29
const tokens: number[] = [];
30
let baseLine = 1;
31
for (let i = 0; i < state.length; i++) {
32
const line = state[i];
33
34
let startOffset = 0;
35
let lineText = '';
36
while (true) {
37
const firstPipeOffset = line.indexOf('|', startOffset);
38
if (firstPipeOffset === -1) {
39
break;
40
}
41
const secondPipeOffset = line.indexOf('|', firstPipeOffset + 1);
42
if (secondPipeOffset === -1) {
43
break;
44
}
45
if (firstPipeOffset + 1 === secondPipeOffset) {
46
// skip ||
47
lineText += line.substring(startOffset, secondPipeOffset + 1);
48
startOffset = secondPipeOffset + 1;
49
continue;
50
}
51
52
lineText += line.substring(startOffset, firstPipeOffset);
53
const tokenStartCharacter = lineText.length;
54
const tokenLength = secondPipeOffset - firstPipeOffset - 1;
55
const metadata = (
56
SEMANTIC_COLOR << MetadataConsts.FOREGROUND_OFFSET
57
| MetadataConsts.SEMANTIC_USE_FOREGROUND
58
);
59
60
if (tokens.length === 0) {
61
baseLine = i + 1;
62
}
63
tokens.push(i + 1 - baseLine, tokenStartCharacter, tokenStartCharacter + tokenLength, metadata);
64
65
lineText += line.substr(firstPipeOffset + 1, tokenLength);
66
startOffset = secondPipeOffset + 1;
67
}
68
69
lineText += line.substring(startOffset);
70
71
text.push(lineText);
72
}
73
74
return {
75
text: text.join('\n'),
76
tokens: SparseMultilineTokens.create(baseLine, new Uint32Array(tokens))
77
};
78
}
79
80
function extractState(model: TextModel): string[] {
81
const result: string[] = [];
82
for (let lineNumber = 1; lineNumber <= model.getLineCount(); lineNumber++) {
83
const lineTokens = model.tokenization.getLineTokens(lineNumber);
84
const lineContent = model.getLineContent(lineNumber);
85
86
let lineText = '';
87
for (let i = 0; i < lineTokens.getCount(); i++) {
88
const tokenStartCharacter = lineTokens.getStartOffset(i);
89
const tokenEndCharacter = lineTokens.getEndOffset(i);
90
const metadata = lineTokens.getMetadata(i);
91
const color = TokenMetadata.getForeground(metadata);
92
const tokenText = lineContent.substring(tokenStartCharacter, tokenEndCharacter);
93
if (color === SEMANTIC_COLOR) {
94
lineText += `|${tokenText}|`;
95
} else {
96
lineText += tokenText;
97
}
98
}
99
100
result.push(lineText);
101
}
102
return result;
103
}
104
105
function testTokensAdjustment(rawInitialState: string[], edits: ISingleEditOperation[], rawFinalState: string[]) {
106
const initialState = parseTokensState(rawInitialState);
107
const model = createTextModel(initialState.text);
108
model.tokenization.setSemanticTokens([initialState.tokens], true);
109
110
model.applyEdits(edits);
111
112
const actualState = extractState(model);
113
assert.deepStrictEqual(actualState, rawFinalState);
114
115
model.dispose();
116
}
117
118
test('issue #86303 - color shifting between different tokens', () => {
119
testTokensAdjustment(
120
[
121
`import { |URI| } from 'vs/base/common/uri';`,
122
`const foo = |URI|.parse('hey');`
123
],
124
[
125
{ range: new Range(2, 9, 2, 10), text: '' }
126
],
127
[
128
`import { |URI| } from 'vs/base/common/uri';`,
129
`const fo = |URI|.parse('hey');`
130
]
131
);
132
});
133
134
test('deleting a newline', () => {
135
testTokensAdjustment(
136
[
137
`import { |URI| } from 'vs/base/common/uri';`,
138
`const foo = |URI|.parse('hey');`
139
],
140
[
141
{ range: new Range(1, 42, 2, 1), text: '' }
142
],
143
[
144
`import { |URI| } from 'vs/base/common/uri';const foo = |URI|.parse('hey');`
145
]
146
);
147
});
148
149
test('inserting a newline', () => {
150
testTokensAdjustment(
151
[
152
`import { |URI| } from 'vs/base/common/uri';const foo = |URI|.parse('hey');`
153
],
154
[
155
{ range: new Range(1, 42, 1, 42), text: '\n' }
156
],
157
[
158
`import { |URI| } from 'vs/base/common/uri';`,
159
`const foo = |URI|.parse('hey');`
160
]
161
);
162
});
163
164
test('deleting a newline 2', () => {
165
testTokensAdjustment(
166
[
167
`import { `,
168
` |URI| } from 'vs/base/common/uri';const foo = |URI|.parse('hey');`
169
],
170
[
171
{ range: new Range(1, 10, 2, 5), text: '' }
172
],
173
[
174
`import { |URI| } from 'vs/base/common/uri';const foo = |URI|.parse('hey');`
175
]
176
);
177
});
178
179
test('issue #179268: a complex edit', () => {
180
testTokensAdjustment(
181
[
182
`|export| |'interior_material_selector.dart'|;`,
183
`|export| |'mileage_selector.dart'|;`,
184
`|export| |'owners_selector.dart'|;`,
185
`|export| |'price_selector.dart'|;`,
186
`|export| |'seat_count_selector.dart'|;`,
187
`|export| |'year_selector.dart'|;`,
188
`|export| |'winter_options_selector.dart'|;|export| |'camera_selector.dart'|;`
189
],
190
[
191
{ range: new Range(1, 9, 1, 9), text: `camera_selector.dart';\nexport '` },
192
{ range: new Range(6, 9, 7, 9), text: `` },
193
{ range: new Range(7, 39, 7, 39), text: `\n` },
194
{ range: new Range(7, 47, 7, 48), text: `ye` },
195
{ range: new Range(7, 49, 7, 51), text: `` },
196
{ range: new Range(7, 52, 7, 53), text: `` },
197
],
198
[
199
`|export| |'|camera_selector.dart';`,
200
`export 'interior_material_selector.dart';`,
201
`|export| |'mileage_selector.dart'|;`,
202
`|export| |'owners_selector.dart'|;`,
203
`|export| |'price_selector.dart'|;`,
204
`|export| |'seat_count_selector.dart'|;`,
205
`|export| |'||winter_options_selector.dart'|;`,
206
`|export| |'year_selector.dart'|;`
207
]
208
);
209
});
210
211
test('issue #91936: Semantic token color highlighting fails on line with selected text', () => {
212
const model = createTextModel(' else if ($s = 08) then \'\\b\'');
213
model.tokenization.setSemanticTokens([
214
SparseMultilineTokens.create(1, new Uint32Array([
215
0, 20, 24, 0b01111000000000010000,
216
0, 25, 27, 0b01111000000000010000,
217
0, 28, 29, 0b00001000000000010000,
218
0, 29, 31, 0b10000000000000010000,
219
0, 32, 33, 0b00001000000000010000,
220
0, 34, 36, 0b00110000000000010000,
221
0, 36, 37, 0b00001000000000010000,
222
0, 38, 42, 0b01111000000000010000,
223
0, 43, 47, 0b01011000000000010000,
224
]))
225
], true);
226
const lineTokens = model.tokenization.getLineTokens(1);
227
const decodedTokens: number[] = [];
228
for (let i = 0, len = lineTokens.getCount(); i < len; i++) {
229
decodedTokens.push(lineTokens.getEndOffset(i), lineTokens.getMetadata(i));
230
}
231
232
assert.deepStrictEqual(decodedTokens, [
233
20, 0b10000000001000010000000001,
234
24, 0b10000001111000010000000001,
235
25, 0b10000000001000010000000001,
236
27, 0b10000001111000010000000001,
237
28, 0b10000000001000010000000001,
238
29, 0b10000000001000010000000001,
239
31, 0b10000010000000010000000001,
240
32, 0b10000000001000010000000001,
241
33, 0b10000000001000010000000001,
242
34, 0b10000000001000010000000001,
243
36, 0b10000000110000010000000001,
244
37, 0b10000000001000010000000001,
245
38, 0b10000000001000010000000001,
246
42, 0b10000001111000010000000001,
247
43, 0b10000000001000010000000001,
248
47, 0b10000001011000010000000001
249
]);
250
251
model.dispose();
252
});
253
254
test('issue #147944: Language id "vs.editor.nullLanguage" is not configured nor known', () => {
255
const disposables = new DisposableStore();
256
const instantiationService = createModelServices(disposables, [
257
[ILanguageConfigurationService, LanguageConfigurationService]
258
]);
259
const model = disposables.add(instantiateTextModel(instantiationService, '--[[\n\n]]'));
260
model.tokenization.setSemanticTokens([
261
SparseMultilineTokens.create(1, new Uint32Array([
262
0, 2, 4, 0b100000000000010000,
263
1, 0, 0, 0b100000000000010000,
264
2, 0, 2, 0b100000000000010000,
265
]))
266
], true);
267
assert.strictEqual(model.getWordAtPosition(new Position(2, 1)), null);
268
disposables.dispose();
269
});
270
271
test('partial tokens 1', () => {
272
const codec = new LanguageIdCodec();
273
const store = new SparseTokensStore(codec);
274
275
// setPartial: [1,1 -> 31,2], [(5,5-10),(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10)]
276
store.setPartial(new Range(1, 1, 31, 2), [
277
SparseMultilineTokens.create(5, new Uint32Array([
278
0, 5, 10, 1,
279
5, 5, 10, 2,
280
10, 5, 10, 3,
281
15, 5, 10, 4,
282
20, 5, 10, 5,
283
25, 5, 10, 6,
284
]))
285
]);
286
287
// setPartial: [18,1 -> 42,1], [(20,5-10),(25,5-10),(30,5-10),(35,5-10),(40,5-10)]
288
store.setPartial(new Range(18, 1, 42, 1), [
289
SparseMultilineTokens.create(20, new Uint32Array([
290
0, 5, 10, 4,
291
5, 5, 10, 5,
292
10, 5, 10, 6,
293
15, 5, 10, 7,
294
20, 5, 10, 8,
295
]))
296
]);
297
298
// setPartial: [1,1 -> 31,2], [(5,5-10),(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10)]
299
store.setPartial(new Range(1, 1, 31, 2), [
300
SparseMultilineTokens.create(5, new Uint32Array([
301
0, 5, 10, 1,
302
5, 5, 10, 2,
303
10, 5, 10, 3,
304
15, 5, 10, 4,
305
20, 5, 10, 5,
306
25, 5, 10, 6,
307
]))
308
]);
309
310
const lineTokens = store.addSparseTokens(10, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`, codec));
311
assert.strictEqual(lineTokens.getCount(), 3);
312
});
313
314
test('partial tokens 2', () => {
315
const codec = new LanguageIdCodec();
316
const store = new SparseTokensStore(codec);
317
318
// setPartial: [1,1 -> 31,2], [(5,5-10),(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10)]
319
store.setPartial(new Range(1, 1, 31, 2), [
320
SparseMultilineTokens.create(5, new Uint32Array([
321
0, 5, 10, 1,
322
5, 5, 10, 2,
323
10, 5, 10, 3,
324
15, 5, 10, 4,
325
20, 5, 10, 5,
326
25, 5, 10, 6,
327
]))
328
]);
329
330
// setPartial: [6,1 -> 36,2], [(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10),(35,5-10)]
331
store.setPartial(new Range(6, 1, 36, 2), [
332
SparseMultilineTokens.create(10, new Uint32Array([
333
0, 5, 10, 2,
334
5, 5, 10, 3,
335
10, 5, 10, 4,
336
15, 5, 10, 5,
337
20, 5, 10, 6,
338
]))
339
]);
340
341
// setPartial: [17,1 -> 42,1], [(20,5-10),(25,5-10),(30,5-10),(35,5-10),(40,5-10)]
342
store.setPartial(new Range(17, 1, 42, 1), [
343
SparseMultilineTokens.create(20, new Uint32Array([
344
0, 5, 10, 4,
345
5, 5, 10, 5,
346
10, 5, 10, 6,
347
15, 5, 10, 7,
348
20, 5, 10, 8,
349
]))
350
]);
351
352
const lineTokens = store.addSparseTokens(20, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`, codec));
353
assert.strictEqual(lineTokens.getCount(), 3);
354
});
355
356
test('partial tokens 3', () => {
357
const codec = new LanguageIdCodec();
358
const store = new SparseTokensStore(codec);
359
360
// setPartial: [1,1 -> 31,2], [(5,5-10),(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10)]
361
store.setPartial(new Range(1, 1, 31, 2), [
362
SparseMultilineTokens.create(5, new Uint32Array([
363
0, 5, 10, 1,
364
5, 5, 10, 2,
365
10, 5, 10, 3,
366
15, 5, 10, 4,
367
20, 5, 10, 5,
368
25, 5, 10, 6,
369
]))
370
]);
371
372
// setPartial: [11,1 -> 16,2], [(15,5-10),(20,5-10)]
373
store.setPartial(new Range(11, 1, 16, 2), [
374
SparseMultilineTokens.create(10, new Uint32Array([
375
0, 5, 10, 3,
376
5, 5, 10, 4,
377
]))
378
]);
379
380
const lineTokens = store.addSparseTokens(5, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`, codec));
381
assert.strictEqual(lineTokens.getCount(), 3);
382
});
383
384
test('issue #94133: Semantic colors stick around when using (only) range provider', () => {
385
const codec = new LanguageIdCodec();
386
const store = new SparseTokensStore(codec);
387
388
// setPartial: [1,1 -> 1,20] [(1,9-11)]
389
store.setPartial(new Range(1, 1, 1, 20), [
390
SparseMultilineTokens.create(1, new Uint32Array([
391
0, 9, 11, 1,
392
]))
393
]);
394
395
// setPartial: [1,1 -> 1,20], []
396
store.setPartial(new Range(1, 1, 1, 20), []);
397
398
const lineTokens = store.addSparseTokens(1, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`, codec));
399
assert.strictEqual(lineTokens.getCount(), 1);
400
});
401
402
test('bug', () => {
403
function createTokens(str: string): SparseMultilineTokens {
404
str = str.replace(/^\[\(/, '');
405
str = str.replace(/\)\]$/, '');
406
const strTokens = str.split('),(');
407
const result: number[] = [];
408
let firstLineNumber = 0;
409
for (const strToken of strTokens) {
410
const pieces = strToken.split(',');
411
const chars = pieces[1].split('-');
412
const lineNumber = parseInt(pieces[0], 10);
413
const startChar = parseInt(chars[0], 10);
414
const endChar = parseInt(chars[1], 10);
415
if (firstLineNumber === 0) {
416
// this is the first line
417
firstLineNumber = lineNumber;
418
}
419
result.push(lineNumber - firstLineNumber, startChar, endChar, (lineNumber + startChar) % 13);
420
}
421
return SparseMultilineTokens.create(firstLineNumber, new Uint32Array(result));
422
}
423
424
const codec = new LanguageIdCodec();
425
const store = new SparseTokensStore(codec);
426
// setPartial [36446,1 -> 36475,115] [(36448,24-29),(36448,33-46),(36448,47-54),(36450,25-35),(36450,36-50),(36451,28-33),(36451,36-49),(36451,50-57),(36452,35-53),(36452,54-62),(36454,33-38),(36454,41-54),(36454,55-60),(36455,35-53),(36455,54-62),(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62),(36466,33-71),(36466,72-76),(36467,35-53),(36467,54-62),(36469,24-29),(36469,33-46),(36469,47-54),(36470,24-35),(36470,38-46),(36473,25-35),(36473,36-51),(36474,28-33),(36474,36-49),(36474,50-58),(36475,35-53),(36475,54-62)]
427
store.setPartial(
428
new Range(36446, 1, 36475, 115),
429
[createTokens('[(36448,24-29),(36448,33-46),(36448,47-54),(36450,25-35),(36450,36-50),(36451,28-33),(36451,36-49),(36451,50-57),(36452,35-53),(36452,54-62),(36454,33-38),(36454,41-54),(36454,55-60),(36455,35-53),(36455,54-62),(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62),(36466,33-71),(36466,72-76),(36467,35-53),(36467,54-62),(36469,24-29),(36469,33-46),(36469,47-54),(36470,24-35),(36470,38-46),(36473,25-35),(36473,36-51),(36474,28-33),(36474,36-49),(36474,50-58),(36475,35-53),(36475,54-62)]')]
430
);
431
// setPartial [36436,1 -> 36464,142] [(36437,33-37),(36437,38-42),(36437,47-57),(36437,58-67),(36438,35-53),(36438,54-62),(36440,24-29),(36440,33-46),(36440,47-53),(36442,25-35),(36442,36-50),(36443,30-39),(36443,42-46),(36443,47-53),(36443,54-58),(36443,63-73),(36443,74-84),(36443,87-91),(36443,92-98),(36443,101-105),(36443,106-112),(36443,113-119),(36444,28-37),(36444,38-42),(36444,47-57),(36444,58-75),(36444,80-95),(36444,96-105),(36445,35-53),(36445,54-62),(36448,24-29),(36448,33-46),(36448,47-54),(36450,25-35),(36450,36-50),(36451,28-33),(36451,36-49),(36451,50-57),(36452,35-53),(36452,54-62),(36454,33-38),(36454,41-54),(36454,55-60),(36455,35-53),(36455,54-62),(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62)]
432
store.setPartial(
433
new Range(36436, 1, 36464, 142),
434
[createTokens('[(36437,33-37),(36437,38-42),(36437,47-57),(36437,58-67),(36438,35-53),(36438,54-62),(36440,24-29),(36440,33-46),(36440,47-53),(36442,25-35),(36442,36-50),(36443,30-39),(36443,42-46),(36443,47-53),(36443,54-58),(36443,63-73),(36443,74-84),(36443,87-91),(36443,92-98),(36443,101-105),(36443,106-112),(36443,113-119),(36444,28-37),(36444,38-42),(36444,47-57),(36444,58-75),(36444,80-95),(36444,96-105),(36445,35-53),(36445,54-62),(36448,24-29),(36448,33-46),(36448,47-54),(36450,25-35),(36450,36-50),(36451,28-33),(36451,36-49),(36451,50-57),(36452,35-53),(36452,54-62),(36454,33-38),(36454,41-54),(36454,55-60),(36455,35-53),(36455,54-62),(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62)]')]
435
);
436
// setPartial [36457,1 -> 36485,140] [(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62),(36466,33-71),(36466,72-76),(36467,35-53),(36467,54-62),(36469,24-29),(36469,33-46),(36469,47-54),(36470,24-35),(36470,38-46),(36473,25-35),(36473,36-51),(36474,28-33),(36474,36-49),(36474,50-58),(36475,35-53),(36475,54-62),(36477,28-32),(36477,33-37),(36477,42-52),(36477,53-69),(36478,32-36),(36478,37-41),(36478,46-56),(36478,57-74),(36479,32-36),(36479,37-41),(36479,46-56),(36479,57-76),(36480,32-36),(36480,37-41),(36480,46-56),(36480,57-68),(36481,32-36),(36481,37-41),(36481,46-56),(36481,57-68),(36482,39-57),(36482,58-66),(36484,34-38),(36484,39-45),(36484,46-50),(36484,55-65),(36484,66-82),(36484,86-97),(36484,98-102),(36484,103-109),(36484,111-124),(36484,125-133),(36485,39-57),(36485,58-66)]
437
store.setPartial(
438
new Range(36457, 1, 36485, 140),
439
[createTokens('[(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62),(36466,33-71),(36466,72-76),(36467,35-53),(36467,54-62),(36469,24-29),(36469,33-46),(36469,47-54),(36470,24-35),(36470,38-46),(36473,25-35),(36473,36-51),(36474,28-33),(36474,36-49),(36474,50-58),(36475,35-53),(36475,54-62),(36477,28-32),(36477,33-37),(36477,42-52),(36477,53-69),(36478,32-36),(36478,37-41),(36478,46-56),(36478,57-74),(36479,32-36),(36479,37-41),(36479,46-56),(36479,57-76),(36480,32-36),(36480,37-41),(36480,46-56),(36480,57-68),(36481,32-36),(36481,37-41),(36481,46-56),(36481,57-68),(36482,39-57),(36482,58-66),(36484,34-38),(36484,39-45),(36484,46-50),(36484,55-65),(36484,66-82),(36484,86-97),(36484,98-102),(36484,103-109),(36484,111-124),(36484,125-133),(36485,39-57),(36485,58-66)]')]
440
);
441
// setPartial [36441,1 -> 36469,56] [(36442,25-35),(36442,36-50),(36443,30-39),(36443,42-46),(36443,47-53),(36443,54-58),(36443,63-73),(36443,74-84),(36443,87-91),(36443,92-98),(36443,101-105),(36443,106-112),(36443,113-119),(36444,28-37),(36444,38-42),(36444,47-57),(36444,58-75),(36444,80-95),(36444,96-105),(36445,35-53),(36445,54-62),(36448,24-29),(36448,33-46),(36448,47-54),(36450,25-35),(36450,36-50),(36451,28-33),(36451,36-49),(36451,50-57),(36452,35-53),(36452,54-62),(36454,33-38),(36454,41-54),(36454,55-60),(36455,35-53),(36455,54-62),(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62),(36466,33-71),(36466,72-76),(36467,35-53),(36467,54-62),(36469,24-29),(36469,33-46),(36469,47-54),(36470,24-35)]
442
store.setPartial(
443
new Range(36441, 1, 36469, 56),
444
[createTokens('[(36442,25-35),(36442,36-50),(36443,30-39),(36443,42-46),(36443,47-53),(36443,54-58),(36443,63-73),(36443,74-84),(36443,87-91),(36443,92-98),(36443,101-105),(36443,106-112),(36443,113-119),(36444,28-37),(36444,38-42),(36444,47-57),(36444,58-75),(36444,80-95),(36444,96-105),(36445,35-53),(36445,54-62),(36448,24-29),(36448,33-46),(36448,47-54),(36450,25-35),(36450,36-50),(36451,28-33),(36451,36-49),(36451,50-57),(36452,35-53),(36452,54-62),(36454,33-38),(36454,41-54),(36454,55-60),(36455,35-53),(36455,54-62),(36457,33-44),(36457,45-49),(36457,50-56),(36457,62-83),(36457,84-88),(36458,35-53),(36458,54-62),(36460,33-37),(36460,38-42),(36460,47-57),(36460,58-67),(36461,35-53),(36461,54-62),(36463,34-38),(36463,39-45),(36463,46-51),(36463,54-63),(36463,64-71),(36463,76-80),(36463,81-87),(36463,88-92),(36463,97-107),(36463,108-119),(36464,35-53),(36464,54-62),(36466,33-71),(36466,72-76),(36467,35-53),(36467,54-62),(36469,24-29),(36469,33-46),(36469,47-54),(36470,24-35)]')]
445
);
446
447
const lineTokens = store.addSparseTokens(36451, new LineTokens(new Uint32Array([60, 1]), ` if (flags & ModifierFlags.Ambient) {`, codec));
448
assert.strictEqual(lineTokens.getCount(), 7);
449
});
450
451
452
test('issue #95949: Identifiers are colored in bold when targetting keywords', () => {
453
454
function createTMMetadata(foreground: number, fontStyle: number, languageId: number): number {
455
return (
456
(languageId << MetadataConsts.LANGUAGEID_OFFSET)
457
| (fontStyle << MetadataConsts.FONT_STYLE_OFFSET)
458
| (foreground << MetadataConsts.FOREGROUND_OFFSET)
459
) >>> 0;
460
}
461
462
function toArr(lineTokens: LineTokens): number[] {
463
const r: number[] = [];
464
for (let i = 0; i < lineTokens.getCount(); i++) {
465
r.push(lineTokens.getEndOffset(i));
466
r.push(lineTokens.getMetadata(i));
467
}
468
return r;
469
}
470
471
const codec = new LanguageIdCodec();
472
const store = new SparseTokensStore(codec);
473
474
store.set([
475
SparseMultilineTokens.create(1, new Uint32Array([
476
0, 6, 11, (1 << MetadataConsts.FOREGROUND_OFFSET) | MetadataConsts.SEMANTIC_USE_FOREGROUND,
477
]))
478
], true);
479
480
const lineTokens = store.addSparseTokens(1, new LineTokens(new Uint32Array([
481
5, createTMMetadata(5, FontStyle.Bold, 53),
482
14, createTMMetadata(1, FontStyle.None, 53),
483
17, createTMMetadata(6, FontStyle.None, 53),
484
18, createTMMetadata(1, FontStyle.None, 53),
485
]), `const hello = 123;`, codec));
486
487
const actual = toArr(lineTokens);
488
assert.deepStrictEqual(actual, [
489
5, createTMMetadata(5, FontStyle.Bold, 53),
490
6, createTMMetadata(1, FontStyle.None, 53),
491
11, createTMMetadata(1, FontStyle.None, 53),
492
14, createTMMetadata(1, FontStyle.None, 53),
493
17, createTMMetadata(6, FontStyle.None, 53),
494
18, createTMMetadata(1, FontStyle.None, 53)
495
]);
496
});
497
498
499
test('BUG: setPartial with startLineNumber > 1 and token removal creates invalid state', () => {
500
/**
501
* The bug is the same regardless of the starting line number.
502
* If a piece starts at line 5 and all tokens are removed via setPartial:
503
* - startLineNumber stays at 5
504
* - endLineNumber becomes 5 + (-1) = 4
505
*/
506
const codec = new LanguageIdCodec();
507
const store = new SparseTokensStore(codec);
508
509
// Set initial tokens on line 5
510
store.set([
511
SparseMultilineTokens.create(5, new Uint32Array([
512
0, 5, 10, 1, // line 5, chars 5-10
513
]))
514
], false);
515
516
assert.strictEqual(store.isEmpty(), false);
517
518
// Remove all tokens via setPartial
519
store.setPartial(new Range(5, 1, 5, 20), []);
520
521
// BUG: During processing, pieces can have invalid line numbers
522
// The store should remove empty pieces and remain valid
523
assert.strictEqual(store.isEmpty(), true,
524
'Store should be empty after setPartial removes all tokens');
525
});
526
527
test('BUG: setPartial with split that creates empty first piece with invalid line numbers', () => {
528
const codec = new LanguageIdCodec();
529
const store = new SparseTokensStore(codec);
530
531
// Set initial tokens - token is on line 11
532
store.set([
533
SparseMultilineTokens.create(1, new Uint32Array([
534
10, 5, 10, 1, // line 11 (deltaLine=10 from startLineNumber=1), chars 5-10
535
]))
536
], false);
537
538
// setPartial with a range [1,1 -> 5,1] that will cause a split where the first piece is empty
539
store.setPartial(new Range(1, 1, 5, 1), []);
540
541
assert.strictEqual(store.isEmpty(), false, 'Store should still have the token on line 11');
542
543
// The token at line 11 should be retrievable after the split
544
const lineTokens = store.addSparseTokens(11, new LineTokens(new Uint32Array([22, 1]), ` test line text `, codec));
545
assert.strictEqual(lineTokens.getCount(), 3, 'Should have 3 tokens: base token start + semantic token from line 11 + base token end');
546
assert.strictEqual(lineTokens.getStartOffset(1), 5, 'Semantic token should start at offset 5');
547
assert.strictEqual(lineTokens.getEndOffset(1), 10, 'Semantic token should end at offset 10');
548
});
549
550
test('piece with startLineNumber 0 and endLineNumber -1 after encompassing deletion', () => {
551
const codec = new LanguageIdCodec();
552
const store = new SparseTokensStore(codec);
553
554
// Set initial tokens on lines 5-10
555
const piece = SparseMultilineTokens.create(5, new Uint32Array([
556
0, 0, 5, 1, // line 5, chars 0-5
557
5, 0, 5, 2, // line 10, chars 0-5
558
]));
559
560
store.set([piece], false);
561
562
// Verify initial state
563
assert.strictEqual(piece.startLineNumber, 5);
564
assert.strictEqual(piece.endLineNumber, 10);
565
assert.strictEqual(piece.isEmpty(), false);
566
567
// Perform an edit that completely encompasses the token range
568
// Delete from line 1 to line 20 (encompasses lines 5-10)
569
// This triggers the case in _acceptDeleteRange where:
570
// if (firstLineIndex < 0 && lastLineIndex >= tokenMaxDeltaLine + 1)
571
// Which sets this._startLineNumber = 0 and calls this._tokens.clear()
572
store.acceptEdit(
573
{ startLineNumber: 1, startColumn: 1, endLineNumber: 20, endColumn: 1 },
574
0, // eolCount - no new lines inserted
575
0, // firstLineLength
576
0, // lastLineLength
577
0 // firstCharCode
578
);
579
580
// After an encompassing deletion, the piece should be empty
581
assert.strictEqual(piece.isEmpty(), true, 'Piece should be empty after encompassing deletion');
582
583
// EXPECTED BEHAVIOR: The store should be empty (no pieces with invalid line numbers)
584
// Currently fails because the piece remains with startLineNumber=0, endLineNumber=-1
585
assert.strictEqual(store.isEmpty(), true, 'Store should be empty after all tokens are deleted by encompassing edit');
586
});
587
});
588
589
590