Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/test/common/model/textModelWithTokens.test.ts
5237 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 { Position } from '../../../common/core/position.js';
9
import { Range } from '../../../common/core/range.js';
10
import { IFoundBracket } from '../../../common/textModelBracketPairs.js';
11
import { TextModel } from '../../../common/model/textModel.js';
12
import { ITokenizationSupport, TokenizationRegistry, EncodedTokenizationResult } from '../../../common/languages.js';
13
import { StandardTokenType, MetadataConsts } from '../../../common/encodedTokenAttributes.js';
14
import { CharacterPair } from '../../../common/languages/languageConfiguration.js';
15
import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
16
import { NullState } from '../../../common/languages/nullTokenize.js';
17
import { ILanguageService } from '../../../common/languages/language.js';
18
import { TestLineToken } from '../core/testLineToken.js';
19
import { createModelServices, createTextModel, instantiateTextModel } from '../testTextModel.js';
20
import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js';
21
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
22
23
function createTextModelWithBrackets(disposables: DisposableStore, text: string, brackets: CharacterPair[]): TextModel {
24
const languageId = 'bracketMode2';
25
const instantiationService = createModelServices(disposables);
26
const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
27
const languageService = instantiationService.get(ILanguageService);
28
29
disposables.add(languageService.registerLanguage({ id: languageId }));
30
disposables.add(languageConfigurationService.register(languageId, { brackets }));
31
32
return disposables.add(instantiateTextModel(instantiationService, text, languageId));
33
}
34
35
suite('TextModelWithTokens', () => {
36
37
ensureNoDisposablesAreLeakedInTestSuite();
38
39
function testBrackets(contents: string[], brackets: CharacterPair[]): void {
40
const languageId = 'testMode';
41
const disposables = new DisposableStore();
42
const instantiationService = createModelServices(disposables);
43
const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
44
const languageService = instantiationService.get(ILanguageService);
45
disposables.add(languageService.registerLanguage({ id: languageId }));
46
disposables.add(languageConfigurationService.register(languageId, {
47
brackets: brackets
48
}));
49
50
51
function toRelaxedFoundBracket(a: IFoundBracket | null) {
52
if (!a) {
53
return null;
54
}
55
return {
56
range: a.range.toString(),
57
info: a.bracketInfo,
58
};
59
}
60
61
const charIsBracket: { [char: string]: boolean } = {};
62
const charIsOpenBracket: { [char: string]: boolean } = {};
63
const openForChar: { [char: string]: string } = {};
64
const closeForChar: { [char: string]: string } = {};
65
brackets.forEach((b) => {
66
charIsBracket[b[0]] = true;
67
charIsBracket[b[1]] = true;
68
69
charIsOpenBracket[b[0]] = true;
70
charIsOpenBracket[b[1]] = false;
71
72
openForChar[b[0]] = b[0];
73
closeForChar[b[0]] = b[1];
74
75
openForChar[b[1]] = b[0];
76
closeForChar[b[1]] = b[1];
77
});
78
79
const expectedBrackets: IFoundBracket[] = [];
80
for (let lineIndex = 0; lineIndex < contents.length; lineIndex++) {
81
const lineText = contents[lineIndex];
82
83
for (let charIndex = 0; charIndex < lineText.length; charIndex++) {
84
const ch = lineText.charAt(charIndex);
85
if (charIsBracket[ch]) {
86
expectedBrackets.push({
87
bracketInfo: languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew.getBracketInfo(ch)!,
88
range: new Range(lineIndex + 1, charIndex + 1, lineIndex + 1, charIndex + 2)
89
});
90
}
91
}
92
}
93
94
const model = disposables.add(instantiateTextModel(instantiationService, contents.join('\n'), languageId));
95
96
// findPrevBracket
97
{
98
let expectedBracketIndex = expectedBrackets.length - 1;
99
let currentExpectedBracket = expectedBracketIndex >= 0 ? expectedBrackets[expectedBracketIndex] : null;
100
for (let lineNumber = contents.length; lineNumber >= 1; lineNumber--) {
101
const lineText = contents[lineNumber - 1];
102
103
for (let column = lineText.length + 1; column >= 1; column--) {
104
105
if (currentExpectedBracket) {
106
if (lineNumber === currentExpectedBracket.range.startLineNumber && column < currentExpectedBracket.range.endColumn) {
107
expectedBracketIndex--;
108
currentExpectedBracket = expectedBracketIndex >= 0 ? expectedBrackets[expectedBracketIndex] : null;
109
}
110
}
111
112
const actual = model.bracketPairs.findPrevBracket({
113
lineNumber: lineNumber,
114
column: column
115
});
116
117
assert.deepStrictEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findPrevBracket of ' + lineNumber + ', ' + column);
118
}
119
}
120
}
121
122
// findNextBracket
123
{
124
let expectedBracketIndex = 0;
125
let currentExpectedBracket = expectedBracketIndex < expectedBrackets.length ? expectedBrackets[expectedBracketIndex] : null;
126
for (let lineNumber = 1; lineNumber <= contents.length; lineNumber++) {
127
const lineText = contents[lineNumber - 1];
128
129
for (let column = 1; column <= lineText.length + 1; column++) {
130
131
if (currentExpectedBracket) {
132
if (lineNumber === currentExpectedBracket.range.startLineNumber && column > currentExpectedBracket.range.startColumn) {
133
expectedBracketIndex++;
134
currentExpectedBracket = expectedBracketIndex < expectedBrackets.length ? expectedBrackets[expectedBracketIndex] : null;
135
}
136
}
137
138
const actual = model.bracketPairs.findNextBracket({
139
lineNumber: lineNumber,
140
column: column
141
});
142
143
assert.deepStrictEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findNextBracket of ' + lineNumber + ', ' + column);
144
}
145
}
146
}
147
148
disposables.dispose();
149
}
150
151
test('brackets1', () => {
152
testBrackets([
153
'if (a == 3) { return (7 * (a + 5)); }'
154
], [
155
['{', '}'],
156
['[', ']'],
157
['(', ')']
158
]);
159
});
160
});
161
162
function assertIsNotBracket(model: TextModel, lineNumber: number, column: number) {
163
const match = model.bracketPairs.matchBracket(new Position(lineNumber, column));
164
assert.strictEqual(match, null, 'is not matching brackets at ' + lineNumber + ', ' + column);
165
}
166
167
function assertIsBracket(model: TextModel, testPosition: Position, expected: [Range, Range]): void {
168
expected.sort(Range.compareRangesUsingStarts);
169
const actual = model.bracketPairs.matchBracket(testPosition);
170
actual?.sort(Range.compareRangesUsingStarts);
171
assert.deepStrictEqual(actual, expected, 'matches brackets at ' + testPosition);
172
}
173
174
suite('TextModelWithTokens - bracket matching', () => {
175
176
const languageId = 'bracketMode1';
177
let disposables: DisposableStore;
178
let instantiationService: TestInstantiationService;
179
let languageConfigurationService: ILanguageConfigurationService;
180
let languageService: ILanguageService;
181
182
setup(() => {
183
disposables = new DisposableStore();
184
instantiationService = createModelServices(disposables);
185
languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
186
languageService = instantiationService.get(ILanguageService);
187
disposables.add(languageService.registerLanguage({ id: languageId }));
188
disposables.add(languageConfigurationService.register(languageId, {
189
brackets: [
190
['{', '}'],
191
['[', ']'],
192
['(', ')'],
193
]
194
}));
195
});
196
197
teardown(() => {
198
disposables.dispose();
199
});
200
201
ensureNoDisposablesAreLeakedInTestSuite();
202
203
test('bracket matching 1', () => {
204
const text =
205
')]}{[(' + '\n' +
206
')]}{[(';
207
const model = disposables.add(instantiateTextModel(instantiationService, text, languageId));
208
209
assertIsNotBracket(model, 1, 1);
210
assertIsNotBracket(model, 1, 2);
211
assertIsNotBracket(model, 1, 3);
212
assertIsBracket(model, new Position(1, 4), [new Range(1, 4, 1, 5), new Range(2, 3, 2, 4)]);
213
assertIsBracket(model, new Position(1, 5), [new Range(1, 5, 1, 6), new Range(2, 2, 2, 3)]);
214
assertIsBracket(model, new Position(1, 6), [new Range(1, 6, 1, 7), new Range(2, 1, 2, 2)]);
215
assertIsBracket(model, new Position(1, 7), [new Range(1, 6, 1, 7), new Range(2, 1, 2, 2)]);
216
217
assertIsBracket(model, new Position(2, 1), [new Range(2, 1, 2, 2), new Range(1, 6, 1, 7)]);
218
assertIsBracket(model, new Position(2, 2), [new Range(2, 2, 2, 3), new Range(1, 5, 1, 6)]);
219
assertIsBracket(model, new Position(2, 3), [new Range(2, 3, 2, 4), new Range(1, 4, 1, 5)]);
220
assertIsBracket(model, new Position(2, 4), [new Range(2, 3, 2, 4), new Range(1, 4, 1, 5)]);
221
assertIsNotBracket(model, 2, 5);
222
assertIsNotBracket(model, 2, 6);
223
assertIsNotBracket(model, 2, 7);
224
});
225
226
test('bracket matching 2', () => {
227
const text =
228
'var bar = {' + '\n' +
229
'foo: {' + '\n' +
230
'}, bar: {hallo: [{' + '\n' +
231
'}, {' + '\n' +
232
'}]}}';
233
const model = disposables.add(instantiateTextModel(instantiationService, text, languageId));
234
235
const brackets: [Position, Range, Range][] = [
236
[new Position(1, 11), new Range(1, 11, 1, 12), new Range(5, 4, 5, 5)],
237
[new Position(1, 12), new Range(1, 11, 1, 12), new Range(5, 4, 5, 5)],
238
239
[new Position(2, 6), new Range(2, 6, 2, 7), new Range(3, 1, 3, 2)],
240
[new Position(2, 7), new Range(2, 6, 2, 7), new Range(3, 1, 3, 2)],
241
242
[new Position(3, 1), new Range(3, 1, 3, 2), new Range(2, 6, 2, 7)],
243
[new Position(3, 2), new Range(3, 1, 3, 2), new Range(2, 6, 2, 7)],
244
[new Position(3, 9), new Range(3, 9, 3, 10), new Range(5, 3, 5, 4)],
245
[new Position(3, 10), new Range(3, 9, 3, 10), new Range(5, 3, 5, 4)],
246
[new Position(3, 17), new Range(3, 17, 3, 18), new Range(5, 2, 5, 3)],
247
[new Position(3, 18), new Range(3, 18, 3, 19), new Range(4, 1, 4, 2)],
248
[new Position(3, 19), new Range(3, 18, 3, 19), new Range(4, 1, 4, 2)],
249
250
[new Position(4, 1), new Range(4, 1, 4, 2), new Range(3, 18, 3, 19)],
251
[new Position(4, 2), new Range(4, 1, 4, 2), new Range(3, 18, 3, 19)],
252
[new Position(4, 4), new Range(4, 4, 4, 5), new Range(5, 1, 5, 2)],
253
[new Position(4, 5), new Range(4, 4, 4, 5), new Range(5, 1, 5, 2)],
254
255
[new Position(5, 1), new Range(5, 1, 5, 2), new Range(4, 4, 4, 5)],
256
[new Position(5, 2), new Range(5, 2, 5, 3), new Range(3, 17, 3, 18)],
257
[new Position(5, 3), new Range(5, 3, 5, 4), new Range(3, 9, 3, 10)],
258
[new Position(5, 4), new Range(5, 4, 5, 5), new Range(1, 11, 1, 12)],
259
[new Position(5, 5), new Range(5, 4, 5, 5), new Range(1, 11, 1, 12)],
260
];
261
262
const isABracket: { [lineNumber: number]: { [col: number]: boolean } } = { 1: {}, 2: {}, 3: {}, 4: {}, 5: {} };
263
for (let i = 0, len = brackets.length; i < len; i++) {
264
const [testPos, b1, b2] = brackets[i];
265
assertIsBracket(model, testPos, [b1, b2]);
266
isABracket[testPos.lineNumber][testPos.column] = true;
267
}
268
269
for (let i = 1, len = model.getLineCount(); i <= len; i++) {
270
const line = model.getLineContent(i);
271
for (let j = 1, lenJ = line.length + 1; j <= lenJ; j++) {
272
// eslint-disable-next-line local/code-no-any-casts
273
if (!isABracket[i].hasOwnProperty(<any>j)) {
274
assertIsNotBracket(model, i, j);
275
}
276
}
277
}
278
});
279
});
280
281
suite('TextModelWithTokens 2', () => {
282
283
ensureNoDisposablesAreLeakedInTestSuite();
284
285
test('bracket matching 3', () => {
286
const text = [
287
'begin',
288
' loop',
289
' if then',
290
' end if;',
291
' end loop;',
292
'end;',
293
'',
294
'begin',
295
' loop',
296
' if then',
297
' end ifa;',
298
' end loop;',
299
'end;',
300
].join('\n');
301
302
const disposables = new DisposableStore();
303
const model = createTextModelWithBrackets(disposables, text, [
304
['if', 'end if'],
305
['loop', 'end loop'],
306
['begin', 'end']
307
]);
308
309
// <if> ... <end ifa> is not matched
310
assertIsNotBracket(model, 10, 9);
311
312
// <if> ... <end if> is matched
313
assertIsBracket(model, new Position(3, 9), [new Range(3, 9, 3, 11), new Range(4, 9, 4, 15)]);
314
assertIsBracket(model, new Position(4, 9), [new Range(4, 9, 4, 15), new Range(3, 9, 3, 11)]);
315
316
// <loop> ... <end loop> is matched
317
assertIsBracket(model, new Position(2, 5), [new Range(2, 5, 2, 9), new Range(5, 5, 5, 13)]);
318
assertIsBracket(model, new Position(5, 5), [new Range(5, 5, 5, 13), new Range(2, 5, 2, 9)]);
319
320
// <begin> ... <end> is matched
321
assertIsBracket(model, new Position(1, 1), [new Range(1, 1, 1, 6), new Range(6, 1, 6, 4)]);
322
assertIsBracket(model, new Position(6, 1), [new Range(6, 1, 6, 4), new Range(1, 1, 1, 6)]);
323
324
disposables.dispose();
325
});
326
327
test('bracket matching 4', () => {
328
const text = [
329
'recordbegin',
330
' simplerecordbegin',
331
' endrecord',
332
'endrecord',
333
].join('\n');
334
335
const disposables = new DisposableStore();
336
const model = createTextModelWithBrackets(disposables, text, [
337
['recordbegin', 'endrecord'],
338
['simplerecordbegin', 'endrecord'],
339
]);
340
341
// <recordbegin> ... <endrecord> is matched
342
assertIsBracket(model, new Position(1, 1), [new Range(1, 1, 1, 12), new Range(4, 1, 4, 10)]);
343
assertIsBracket(model, new Position(4, 1), [new Range(4, 1, 4, 10), new Range(1, 1, 1, 12)]);
344
345
// <simplerecordbegin> ... <endrecord> is matched
346
assertIsBracket(model, new Position(2, 3), [new Range(2, 3, 2, 20), new Range(3, 3, 3, 12)]);
347
assertIsBracket(model, new Position(3, 3), [new Range(3, 3, 3, 12), new Range(2, 3, 2, 20)]);
348
349
disposables.dispose();
350
});
351
352
test('issue #95843: Highlighting of closing braces is indicating wrong brace when cursor is behind opening brace', () => {
353
const disposables = new DisposableStore();
354
const instantiationService = createModelServices(disposables);
355
const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
356
const languageService = instantiationService.get(ILanguageService);
357
const mode1 = 'testMode1';
358
const mode2 = 'testMode2';
359
360
const languageIdCodec = languageService.languageIdCodec;
361
362
disposables.add(languageService.registerLanguage({ id: mode1 }));
363
disposables.add(languageService.registerLanguage({ id: mode2 }));
364
const encodedMode1 = languageIdCodec.encodeLanguageId(mode1);
365
const encodedMode2 = languageIdCodec.encodeLanguageId(mode2);
366
367
const otherMetadata1 = (
368
(encodedMode1 << MetadataConsts.LANGUAGEID_OFFSET)
369
| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)
370
| (MetadataConsts.BALANCED_BRACKETS_MASK)
371
) >>> 0;
372
const otherMetadata2 = (
373
(encodedMode2 << MetadataConsts.LANGUAGEID_OFFSET)
374
| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)
375
| (MetadataConsts.BALANCED_BRACKETS_MASK)
376
) >>> 0;
377
378
const tokenizationSupport: ITokenizationSupport = {
379
getInitialState: () => NullState,
380
tokenize: undefined!,
381
tokenizeEncoded: (line, hasEOL, state) => {
382
switch (line) {
383
case 'function f() {': {
384
const tokens = new Uint32Array([
385
0, otherMetadata1,
386
8, otherMetadata1,
387
9, otherMetadata1,
388
10, otherMetadata1,
389
11, otherMetadata1,
390
12, otherMetadata1,
391
13, otherMetadata1,
392
]);
393
return new EncodedTokenizationResult(tokens, [], state);
394
}
395
case ' return <p>{true}</p>;': {
396
const tokens = new Uint32Array([
397
0, otherMetadata1,
398
2, otherMetadata1,
399
8, otherMetadata1,
400
9, otherMetadata2,
401
10, otherMetadata2,
402
11, otherMetadata2,
403
12, otherMetadata2,
404
13, otherMetadata1,
405
17, otherMetadata2,
406
18, otherMetadata2,
407
20, otherMetadata2,
408
21, otherMetadata2,
409
22, otherMetadata2,
410
]);
411
return new EncodedTokenizationResult(tokens, [], state);
412
}
413
case '}': {
414
const tokens = new Uint32Array([
415
0, otherMetadata1
416
]);
417
return new EncodedTokenizationResult(tokens, [], state);
418
}
419
}
420
throw new Error(`Unexpected`);
421
}
422
};
423
424
disposables.add(TokenizationRegistry.register(mode1, tokenizationSupport));
425
disposables.add(languageConfigurationService.register(mode1, {
426
brackets: [
427
['{', '}'],
428
['[', ']'],
429
['(', ')']
430
],
431
}));
432
disposables.add(languageConfigurationService.register(mode2, {
433
brackets: [
434
['{', '}'],
435
['[', ']'],
436
['(', ')']
437
],
438
}));
439
440
const model = disposables.add(instantiateTextModel(
441
instantiationService,
442
[
443
'function f() {',
444
' return <p>{true}</p>;',
445
'}',
446
].join('\n'),
447
mode1
448
));
449
450
model.tokenization.forceTokenization(1);
451
model.tokenization.forceTokenization(2);
452
model.tokenization.forceTokenization(3);
453
454
assert.deepStrictEqual(
455
model.bracketPairs.matchBracket(new Position(2, 14)),
456
[new Range(2, 13, 2, 14), new Range(2, 18, 2, 19)]
457
);
458
459
disposables.dispose();
460
});
461
462
test('issue #88075: TypeScript brace matching is incorrect in `${}` strings', () => {
463
const disposables = new DisposableStore();
464
const instantiationService = createModelServices(disposables);
465
const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
466
const mode = 'testMode';
467
468
const languageIdCodec = instantiationService.get(ILanguageService).languageIdCodec;
469
470
const encodedMode = languageIdCodec.encodeLanguageId(mode);
471
472
const otherMetadata = (
473
(encodedMode << MetadataConsts.LANGUAGEID_OFFSET)
474
| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)
475
) >>> 0;
476
const stringMetadata = (
477
(encodedMode << MetadataConsts.LANGUAGEID_OFFSET)
478
| (StandardTokenType.String << MetadataConsts.TOKEN_TYPE_OFFSET)
479
) >>> 0;
480
481
const tokenizationSupport: ITokenizationSupport = {
482
getInitialState: () => NullState,
483
tokenize: undefined!,
484
tokenizeEncoded: (line, hasEOL, state) => {
485
switch (line) {
486
case 'function hello() {': {
487
const tokens = new Uint32Array([
488
0, otherMetadata
489
]);
490
return new EncodedTokenizationResult(tokens, [], state);
491
}
492
case ' console.log(`${100}`);': {
493
const tokens = new Uint32Array([
494
0, otherMetadata,
495
16, stringMetadata,
496
19, otherMetadata,
497
22, stringMetadata,
498
24, otherMetadata,
499
]);
500
return new EncodedTokenizationResult(tokens, [], state);
501
}
502
case '}': {
503
const tokens = new Uint32Array([
504
0, otherMetadata
505
]);
506
return new EncodedTokenizationResult(tokens, [], state);
507
}
508
}
509
throw new Error(`Unexpected`);
510
}
511
};
512
513
disposables.add(TokenizationRegistry.register(mode, tokenizationSupport));
514
disposables.add(languageConfigurationService.register(mode, {
515
brackets: [
516
['{', '}'],
517
['[', ']'],
518
['(', ')']
519
],
520
}));
521
522
const model = disposables.add(instantiateTextModel(
523
instantiationService,
524
[
525
'function hello() {',
526
' console.log(`${100}`);',
527
'}'
528
].join('\n'),
529
mode
530
));
531
532
model.tokenization.forceTokenization(1);
533
model.tokenization.forceTokenization(2);
534
model.tokenization.forceTokenization(3);
535
536
assert.deepStrictEqual(model.bracketPairs.matchBracket(new Position(2, 23)), null);
537
assert.deepStrictEqual(model.bracketPairs.matchBracket(new Position(2, 20)), null);
538
539
disposables.dispose();
540
});
541
});
542
543
544
suite('TextModelWithTokens regression tests', () => {
545
546
ensureNoDisposablesAreLeakedInTestSuite();
547
548
test('microsoft/monaco-editor#122: Unhandled Exception: TypeError: Unable to get property \'replace\' of undefined or null reference', () => {
549
function assertViewLineTokens(model: TextModel, lineNumber: number, forceTokenization: boolean, expected: TestLineToken[]): void {
550
if (forceTokenization) {
551
model.tokenization.forceTokenization(lineNumber);
552
}
553
const _actual = model.tokenization.getLineTokens(lineNumber).inflate();
554
interface ISimpleViewToken {
555
endIndex: number;
556
foreground: number;
557
}
558
const actual: ISimpleViewToken[] = [];
559
for (let i = 0, len = _actual.getCount(); i < len; i++) {
560
actual[i] = {
561
endIndex: _actual.getEndOffset(i),
562
foreground: _actual.getForeground(i)
563
};
564
}
565
const decode = (token: TestLineToken) => {
566
return {
567
endIndex: token.endIndex,
568
foreground: token.getForeground()
569
};
570
};
571
assert.deepStrictEqual(actual, expected.map(decode));
572
}
573
574
let _tokenId = 10;
575
const LANG_ID1 = 'indicisiveMode1';
576
const LANG_ID2 = 'indicisiveMode2';
577
578
const tokenizationSupport: ITokenizationSupport = {
579
getInitialState: () => NullState,
580
tokenize: undefined!,
581
tokenizeEncoded: (line, hasEOL, state) => {
582
const myId = ++_tokenId;
583
const tokens = new Uint32Array(2);
584
tokens[0] = 0;
585
tokens[1] = (
586
myId << MetadataConsts.FOREGROUND_OFFSET
587
) >>> 0;
588
return new EncodedTokenizationResult(tokens, [], state);
589
}
590
};
591
592
const registration1 = TokenizationRegistry.register(LANG_ID1, tokenizationSupport);
593
const registration2 = TokenizationRegistry.register(LANG_ID2, tokenizationSupport);
594
595
const model = createTextModel('A model with\ntwo lines');
596
597
assertViewLineTokens(model, 1, true, [createViewLineToken(12, 1)]);
598
assertViewLineTokens(model, 2, true, [createViewLineToken(9, 1)]);
599
600
model.setLanguage(LANG_ID1);
601
602
assertViewLineTokens(model, 1, true, [createViewLineToken(12, 11)]);
603
assertViewLineTokens(model, 2, true, [createViewLineToken(9, 12)]);
604
605
model.setLanguage(LANG_ID2);
606
607
assertViewLineTokens(model, 1, false, [createViewLineToken(12, 1)]);
608
assertViewLineTokens(model, 2, false, [createViewLineToken(9, 1)]);
609
610
model.dispose();
611
registration1.dispose();
612
registration2.dispose();
613
614
function createViewLineToken(endIndex: number, foreground: number): TestLineToken {
615
const metadata = (
616
(foreground << MetadataConsts.FOREGROUND_OFFSET)
617
) >>> 0;
618
return new TestLineToken(endIndex, metadata);
619
}
620
});
621
622
623
test('microsoft/monaco-editor#133: Error: Cannot read property \'modeId\' of undefined', () => {
624
625
const disposables = new DisposableStore();
626
const model = createTextModelWithBrackets(
627
disposables,
628
[
629
'Imports System',
630
'Imports System.Collections.Generic',
631
'',
632
'Module m1',
633
'',
634
'\tSub Main()',
635
'\tEnd Sub',
636
'',
637
'End Module',
638
].join('\n'),
639
[
640
['module', 'end module'],
641
['sub', 'end sub']
642
]
643
);
644
645
const actual = model.bracketPairs.matchBracket(new Position(4, 1));
646
assert.deepStrictEqual(actual, [new Range(4, 1, 4, 7), new Range(9, 1, 9, 11)]);
647
648
disposables.dispose();
649
});
650
651
test('issue #11856: Bracket matching does not work as expected if the opening brace symbol is contained in the closing brace symbol', () => {
652
653
const disposables = new DisposableStore();
654
const model = createTextModelWithBrackets(
655
disposables,
656
[
657
'sequence "outer"',
658
' sequence "inner"',
659
' endsequence',
660
'endsequence',
661
].join('\n'),
662
[
663
['sequence', 'endsequence'],
664
['feature', 'endfeature']
665
]
666
);
667
668
const actual = model.bracketPairs.matchBracket(new Position(3, 9));
669
assert.deepStrictEqual(actual, [new Range(2, 6, 2, 14), new Range(3, 6, 3, 17)]);
670
671
disposables.dispose();
672
});
673
674
test('issue #63822: Wrong embedded language detected for empty lines', () => {
675
const disposables = new DisposableStore();
676
const instantiationService = createModelServices(disposables);
677
const languageService = instantiationService.get(ILanguageService);
678
679
const outerMode = 'outerMode';
680
const innerMode = 'innerMode';
681
682
disposables.add(languageService.registerLanguage({ id: outerMode }));
683
disposables.add(languageService.registerLanguage({ id: innerMode }));
684
685
const languageIdCodec = instantiationService.get(ILanguageService).languageIdCodec;
686
const encodedInnerMode = languageIdCodec.encodeLanguageId(innerMode);
687
688
const tokenizationSupport: ITokenizationSupport = {
689
getInitialState: () => NullState,
690
tokenize: undefined!,
691
tokenizeEncoded: (line, hasEOL, state) => {
692
const tokens = new Uint32Array(2);
693
tokens[0] = 0;
694
tokens[1] = (
695
encodedInnerMode << MetadataConsts.LANGUAGEID_OFFSET
696
) >>> 0;
697
return new EncodedTokenizationResult(tokens, [], state);
698
}
699
};
700
701
disposables.add(TokenizationRegistry.register(outerMode, tokenizationSupport));
702
703
const model = disposables.add(instantiateTextModel(instantiationService, 'A model with one line', outerMode));
704
705
model.tokenization.forceTokenization(1);
706
assert.strictEqual(model.getLanguageIdAtPosition(1, 1), innerMode);
707
708
disposables.dispose();
709
});
710
});
711
712
suite('TextModel.getLineIndentGuide', () => {
713
714
ensureNoDisposablesAreLeakedInTestSuite();
715
716
function assertIndentGuides(lines: [number, number, number, number, string][], indentSize: number): void {
717
const languageId = 'testLang';
718
const disposables = new DisposableStore();
719
const instantiationService = createModelServices(disposables);
720
const languageService = instantiationService.get(ILanguageService);
721
disposables.add(languageService.registerLanguage({ id: languageId }));
722
723
const text = lines.map(l => l[4]).join('\n');
724
const model = disposables.add(instantiateTextModel(instantiationService, text, languageId));
725
model.updateOptions({ indentSize: indentSize });
726
727
const actualIndents = model.guides.getLinesIndentGuides(1, model.getLineCount());
728
729
const actual: [number, number, number, number, string][] = [];
730
for (let line = 1; line <= model.getLineCount(); line++) {
731
const activeIndentGuide = model.guides.getActiveIndentGuide(line, 1, model.getLineCount());
732
actual[line - 1] = [actualIndents[line - 1], activeIndentGuide.startLineNumber, activeIndentGuide.endLineNumber, activeIndentGuide.indent, model.getLineContent(line)];
733
}
734
735
assert.deepStrictEqual(actual, lines);
736
737
disposables.dispose();
738
}
739
740
test('getLineIndentGuide one level 2', () => {
741
assertIndentGuides([
742
[0, 2, 4, 1, 'A'],
743
[1, 2, 4, 1, ' A'],
744
[1, 2, 4, 1, ' A'],
745
[1, 2, 4, 1, ' A'],
746
], 2);
747
});
748
749
test('getLineIndentGuide two levels', () => {
750
assertIndentGuides([
751
[0, 2, 5, 1, 'A'],
752
[1, 2, 5, 1, ' A'],
753
[1, 4, 5, 2, ' A'],
754
[2, 4, 5, 2, ' A'],
755
[2, 4, 5, 2, ' A'],
756
], 2);
757
});
758
759
test('getLineIndentGuide three levels', () => {
760
assertIndentGuides([
761
[0, 2, 4, 1, 'A'],
762
[1, 3, 4, 2, ' A'],
763
[2, 4, 4, 3, ' A'],
764
[3, 4, 4, 3, ' A'],
765
[0, 5, 5, 0, 'A'],
766
], 2);
767
});
768
769
test('getLineIndentGuide decreasing indent', () => {
770
assertIndentGuides([
771
[2, 1, 1, 2, ' A'],
772
[1, 1, 1, 2, ' A'],
773
[0, 1, 2, 1, 'A'],
774
], 2);
775
});
776
777
test('getLineIndentGuide Java', () => {
778
assertIndentGuides([
779
/* 1*/[0, 2, 9, 1, 'class A {'],
780
/* 2*/[1, 3, 4, 2, ' void foo() {'],
781
/* 3*/[2, 3, 4, 2, ' console.log(1);'],
782
/* 4*/[2, 3, 4, 2, ' console.log(2);'],
783
/* 5*/[1, 3, 4, 2, ' }'],
784
/* 6*/[1, 2, 9, 1, ''],
785
/* 7*/[1, 8, 8, 2, ' void bar() {'],
786
/* 8*/[2, 8, 8, 2, ' console.log(3);'],
787
/* 9*/[1, 8, 8, 2, ' }'],
788
/*10*/[0, 2, 9, 1, '}'],
789
/*11*/[0, 12, 12, 1, 'interface B {'],
790
/*12*/[1, 12, 12, 1, ' void bar();'],
791
/*13*/[0, 12, 12, 1, '}'],
792
], 2);
793
});
794
795
test('getLineIndentGuide Javadoc', () => {
796
assertIndentGuides([
797
[0, 2, 3, 1, '/**'],
798
[1, 2, 3, 1, ' * Comment'],
799
[1, 2, 3, 1, ' */'],
800
[0, 5, 6, 1, 'class A {'],
801
[1, 5, 6, 1, ' void foo() {'],
802
[1, 5, 6, 1, ' }'],
803
[0, 5, 6, 1, '}'],
804
], 2);
805
});
806
807
test('getLineIndentGuide Whitespace', () => {
808
assertIndentGuides([
809
[0, 2, 7, 1, 'class A {'],
810
[1, 2, 7, 1, ''],
811
[1, 4, 5, 2, ' void foo() {'],
812
[2, 4, 5, 2, ' '],
813
[2, 4, 5, 2, ' return 1;'],
814
[1, 4, 5, 2, ' }'],
815
[1, 2, 7, 1, ' '],
816
[0, 2, 7, 1, '}']
817
], 2);
818
});
819
820
test('getLineIndentGuide Tabs', () => {
821
assertIndentGuides([
822
[0, 2, 7, 1, 'class A {'],
823
[1, 2, 7, 1, '\t\t'],
824
[1, 4, 5, 2, '\tvoid foo() {'],
825
[2, 4, 5, 2, '\t \t//hello'],
826
[2, 4, 5, 2, '\t return 2;'],
827
[1, 4, 5, 2, ' \t}'],
828
[1, 2, 7, 1, ' '],
829
[0, 2, 7, 1, '}']
830
], 4);
831
});
832
833
test('getLineIndentGuide checker.ts', () => {
834
assertIndentGuides([
835
/* 1*/[0, 1, 1, 0, '/// <reference path="binder.ts"/>'],
836
/* 2*/[0, 2, 2, 0, ''],
837
/* 3*/[0, 3, 3, 0, '/* @internal */'],
838
/* 4*/[0, 5, 16, 1, 'namespace ts {'],
839
/* 5*/[1, 5, 16, 1, ' let nextSymbolId = 1;'],
840
/* 6*/[1, 5, 16, 1, ' let nextNodeId = 1;'],
841
/* 7*/[1, 5, 16, 1, ' let nextMergeId = 1;'],
842
/* 8*/[1, 5, 16, 1, ' let nextFlowId = 1;'],
843
/* 9*/[1, 5, 16, 1, ''],
844
/*10*/[1, 11, 15, 2, ' export function getNodeId(node: Node): number {'],
845
/*11*/[2, 12, 13, 3, ' if (!node.id) {'],
846
/*12*/[3, 12, 13, 3, ' node.id = nextNodeId;'],
847
/*13*/[3, 12, 13, 3, ' nextNodeId++;'],
848
/*14*/[2, 12, 13, 3, ' }'],
849
/*15*/[2, 11, 15, 2, ' return node.id;'],
850
/*16*/[1, 11, 15, 2, ' }'],
851
/*17*/[0, 5, 16, 1, '}']
852
], 4);
853
});
854
855
test('issue #8425 - Missing indentation lines for first level indentation', () => {
856
assertIndentGuides([
857
[1, 2, 3, 2, '\tindent1'],
858
[2, 2, 3, 2, '\t\tindent2'],
859
[2, 2, 3, 2, '\t\tindent2'],
860
[1, 2, 3, 2, '\tindent1']
861
], 4);
862
});
863
864
test('issue #8952 - Indentation guide lines going through text on .yml file', () => {
865
assertIndentGuides([
866
[0, 2, 5, 1, 'properties:'],
867
[1, 3, 5, 2, ' emailAddress:'],
868
[2, 3, 5, 2, ' - bla'],
869
[2, 5, 5, 3, ' - length:'],
870
[3, 5, 5, 3, ' max: 255'],
871
[0, 6, 6, 0, 'getters:']
872
], 4);
873
});
874
875
test('issue #11892 - Indent guides look funny', () => {
876
assertIndentGuides([
877
[0, 2, 7, 1, 'function test(base) {'],
878
[1, 3, 6, 2, '\tswitch (base) {'],
879
[2, 4, 4, 3, '\t\tcase 1:'],
880
[3, 4, 4, 3, '\t\t\treturn 1;'],
881
[2, 6, 6, 3, '\t\tcase 2:'],
882
[3, 6, 6, 3, '\t\t\treturn 2;'],
883
[1, 2, 7, 1, '\t}'],
884
[0, 2, 7, 1, '}']
885
], 4);
886
});
887
888
test('issue #12398 - Problem in indent guidelines', () => {
889
assertIndentGuides([
890
[2, 2, 2, 3, '\t\t.bla'],
891
[3, 2, 2, 3, '\t\t\tlabel(for)'],
892
[0, 3, 3, 0, 'include script']
893
], 4);
894
});
895
896
test('issue #49173', () => {
897
const model = createTextModel([
898
'class A {',
899
' public m1(): void {',
900
' }',
901
' public m2(): void {',
902
' }',
903
' public m3(): void {',
904
' }',
905
' public m4(): void {',
906
' }',
907
' public m5(): void {',
908
' }',
909
'}',
910
].join('\n'));
911
912
const actual = model.guides.getActiveIndentGuide(2, 4, 9);
913
assert.deepStrictEqual(actual, { startLineNumber: 2, endLineNumber: 9, indent: 1 });
914
model.dispose();
915
});
916
917
test('tweaks - no active', () => {
918
assertIndentGuides([
919
[0, 1, 1, 0, 'A'],
920
[0, 2, 2, 0, 'A']
921
], 2);
922
});
923
924
test('tweaks - inside scope', () => {
925
assertIndentGuides([
926
[0, 2, 2, 1, 'A'],
927
[1, 2, 2, 1, ' A']
928
], 2);
929
});
930
931
test('tweaks - scope start', () => {
932
assertIndentGuides([
933
[0, 2, 2, 1, 'A'],
934
[1, 2, 2, 1, ' A'],
935
[0, 2, 2, 1, 'A']
936
], 2);
937
});
938
939
test('tweaks - empty line', () => {
940
assertIndentGuides([
941
[0, 2, 4, 1, 'A'],
942
[1, 2, 4, 1, ' A'],
943
[1, 2, 4, 1, ''],
944
[1, 2, 4, 1, ' A'],
945
[0, 2, 4, 1, 'A']
946
], 2);
947
});
948
});
949
950