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
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 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
if (!isABracket[i].hasOwnProperty(<any>j)) {
273
assertIsNotBracket(model, i, j);
274
}
275
}
276
}
277
});
278
});
279
280
suite('TextModelWithTokens 2', () => {
281
282
ensureNoDisposablesAreLeakedInTestSuite();
283
284
test('bracket matching 3', () => {
285
const text = [
286
'begin',
287
' loop',
288
' if then',
289
' end if;',
290
' end loop;',
291
'end;',
292
'',
293
'begin',
294
' loop',
295
' if then',
296
' end ifa;',
297
' end loop;',
298
'end;',
299
].join('\n');
300
301
const disposables = new DisposableStore();
302
const model = createTextModelWithBrackets(disposables, text, [
303
['if', 'end if'],
304
['loop', 'end loop'],
305
['begin', 'end']
306
]);
307
308
// <if> ... <end ifa> is not matched
309
assertIsNotBracket(model, 10, 9);
310
311
// <if> ... <end if> is matched
312
assertIsBracket(model, new Position(3, 9), [new Range(3, 9, 3, 11), new Range(4, 9, 4, 15)]);
313
assertIsBracket(model, new Position(4, 9), [new Range(4, 9, 4, 15), new Range(3, 9, 3, 11)]);
314
315
// <loop> ... <end loop> is matched
316
assertIsBracket(model, new Position(2, 5), [new Range(2, 5, 2, 9), new Range(5, 5, 5, 13)]);
317
assertIsBracket(model, new Position(5, 5), [new Range(5, 5, 5, 13), new Range(2, 5, 2, 9)]);
318
319
// <begin> ... <end> is matched
320
assertIsBracket(model, new Position(1, 1), [new Range(1, 1, 1, 6), new Range(6, 1, 6, 4)]);
321
assertIsBracket(model, new Position(6, 1), [new Range(6, 1, 6, 4), new Range(1, 1, 1, 6)]);
322
323
disposables.dispose();
324
});
325
326
test('bracket matching 4', () => {
327
const text = [
328
'recordbegin',
329
' simplerecordbegin',
330
' endrecord',
331
'endrecord',
332
].join('\n');
333
334
const disposables = new DisposableStore();
335
const model = createTextModelWithBrackets(disposables, text, [
336
['recordbegin', 'endrecord'],
337
['simplerecordbegin', 'endrecord'],
338
]);
339
340
// <recordbegin> ... <endrecord> is matched
341
assertIsBracket(model, new Position(1, 1), [new Range(1, 1, 1, 12), new Range(4, 1, 4, 10)]);
342
assertIsBracket(model, new Position(4, 1), [new Range(4, 1, 4, 10), new Range(1, 1, 1, 12)]);
343
344
// <simplerecordbegin> ... <endrecord> is matched
345
assertIsBracket(model, new Position(2, 3), [new Range(2, 3, 2, 20), new Range(3, 3, 3, 12)]);
346
assertIsBracket(model, new Position(3, 3), [new Range(3, 3, 3, 12), new Range(2, 3, 2, 20)]);
347
348
disposables.dispose();
349
});
350
351
test('issue #95843: Highlighting of closing braces is indicating wrong brace when cursor is behind opening brace', () => {
352
const disposables = new DisposableStore();
353
const instantiationService = createModelServices(disposables);
354
const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
355
const languageService = instantiationService.get(ILanguageService);
356
const mode1 = 'testMode1';
357
const mode2 = 'testMode2';
358
359
const languageIdCodec = languageService.languageIdCodec;
360
361
disposables.add(languageService.registerLanguage({ id: mode1 }));
362
disposables.add(languageService.registerLanguage({ id: mode2 }));
363
const encodedMode1 = languageIdCodec.encodeLanguageId(mode1);
364
const encodedMode2 = languageIdCodec.encodeLanguageId(mode2);
365
366
const otherMetadata1 = (
367
(encodedMode1 << MetadataConsts.LANGUAGEID_OFFSET)
368
| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)
369
| (MetadataConsts.BALANCED_BRACKETS_MASK)
370
) >>> 0;
371
const otherMetadata2 = (
372
(encodedMode2 << MetadataConsts.LANGUAGEID_OFFSET)
373
| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)
374
| (MetadataConsts.BALANCED_BRACKETS_MASK)
375
) >>> 0;
376
377
const tokenizationSupport: ITokenizationSupport = {
378
getInitialState: () => NullState,
379
tokenize: undefined!,
380
tokenizeEncoded: (line, hasEOL, state) => {
381
switch (line) {
382
case 'function f() {': {
383
const tokens = new Uint32Array([
384
0, otherMetadata1,
385
8, otherMetadata1,
386
9, otherMetadata1,
387
10, otherMetadata1,
388
11, otherMetadata1,
389
12, otherMetadata1,
390
13, otherMetadata1,
391
]);
392
return new EncodedTokenizationResult(tokens, state);
393
}
394
case ' return <p>{true}</p>;': {
395
const tokens = new Uint32Array([
396
0, otherMetadata1,
397
2, otherMetadata1,
398
8, otherMetadata1,
399
9, otherMetadata2,
400
10, otherMetadata2,
401
11, otherMetadata2,
402
12, otherMetadata2,
403
13, otherMetadata1,
404
17, otherMetadata2,
405
18, otherMetadata2,
406
20, otherMetadata2,
407
21, otherMetadata2,
408
22, otherMetadata2,
409
]);
410
return new EncodedTokenizationResult(tokens, state);
411
}
412
case '}': {
413
const tokens = new Uint32Array([
414
0, otherMetadata1
415
]);
416
return new EncodedTokenizationResult(tokens, state);
417
}
418
}
419
throw new Error(`Unexpected`);
420
}
421
};
422
423
disposables.add(TokenizationRegistry.register(mode1, tokenizationSupport));
424
disposables.add(languageConfigurationService.register(mode1, {
425
brackets: [
426
['{', '}'],
427
['[', ']'],
428
['(', ')']
429
],
430
}));
431
disposables.add(languageConfigurationService.register(mode2, {
432
brackets: [
433
['{', '}'],
434
['[', ']'],
435
['(', ')']
436
],
437
}));
438
439
const model = disposables.add(instantiateTextModel(
440
instantiationService,
441
[
442
'function f() {',
443
' return <p>{true}</p>;',
444
'}',
445
].join('\n'),
446
mode1
447
));
448
449
model.tokenization.forceTokenization(1);
450
model.tokenization.forceTokenization(2);
451
model.tokenization.forceTokenization(3);
452
453
assert.deepStrictEqual(
454
model.bracketPairs.matchBracket(new Position(2, 14)),
455
[new Range(2, 13, 2, 14), new Range(2, 18, 2, 19)]
456
);
457
458
disposables.dispose();
459
});
460
461
test('issue #88075: TypeScript brace matching is incorrect in `${}` strings', () => {
462
const disposables = new DisposableStore();
463
const instantiationService = createModelServices(disposables);
464
const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
465
const mode = 'testMode';
466
467
const languageIdCodec = instantiationService.get(ILanguageService).languageIdCodec;
468
469
const encodedMode = languageIdCodec.encodeLanguageId(mode);
470
471
const otherMetadata = (
472
(encodedMode << MetadataConsts.LANGUAGEID_OFFSET)
473
| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)
474
) >>> 0;
475
const stringMetadata = (
476
(encodedMode << MetadataConsts.LANGUAGEID_OFFSET)
477
| (StandardTokenType.String << MetadataConsts.TOKEN_TYPE_OFFSET)
478
) >>> 0;
479
480
const tokenizationSupport: ITokenizationSupport = {
481
getInitialState: () => NullState,
482
tokenize: undefined!,
483
tokenizeEncoded: (line, hasEOL, state) => {
484
switch (line) {
485
case 'function hello() {': {
486
const tokens = new Uint32Array([
487
0, otherMetadata
488
]);
489
return new EncodedTokenizationResult(tokens, state);
490
}
491
case ' console.log(`${100}`);': {
492
const tokens = new Uint32Array([
493
0, otherMetadata,
494
16, stringMetadata,
495
19, otherMetadata,
496
22, stringMetadata,
497
24, otherMetadata,
498
]);
499
return new EncodedTokenizationResult(tokens, state);
500
}
501
case '}': {
502
const tokens = new Uint32Array([
503
0, otherMetadata
504
]);
505
return new EncodedTokenizationResult(tokens, state);
506
}
507
}
508
throw new Error(`Unexpected`);
509
}
510
};
511
512
disposables.add(TokenizationRegistry.register(mode, tokenizationSupport));
513
disposables.add(languageConfigurationService.register(mode, {
514
brackets: [
515
['{', '}'],
516
['[', ']'],
517
['(', ')']
518
],
519
}));
520
521
const model = disposables.add(instantiateTextModel(
522
instantiationService,
523
[
524
'function hello() {',
525
' console.log(`${100}`);',
526
'}'
527
].join('\n'),
528
mode
529
));
530
531
model.tokenization.forceTokenization(1);
532
model.tokenization.forceTokenization(2);
533
model.tokenization.forceTokenization(3);
534
535
assert.deepStrictEqual(model.bracketPairs.matchBracket(new Position(2, 23)), null);
536
assert.deepStrictEqual(model.bracketPairs.matchBracket(new Position(2, 20)), null);
537
538
disposables.dispose();
539
});
540
});
541
542
543
suite('TextModelWithTokens regression tests', () => {
544
545
ensureNoDisposablesAreLeakedInTestSuite();
546
547
test('microsoft/monaco-editor#122: Unhandled Exception: TypeError: Unable to get property \'replace\' of undefined or null reference', () => {
548
function assertViewLineTokens(model: TextModel, lineNumber: number, forceTokenization: boolean, expected: TestLineToken[]): void {
549
if (forceTokenization) {
550
model.tokenization.forceTokenization(lineNumber);
551
}
552
const _actual = model.tokenization.getLineTokens(lineNumber).inflate();
553
interface ISimpleViewToken {
554
endIndex: number;
555
foreground: number;
556
}
557
const actual: ISimpleViewToken[] = [];
558
for (let i = 0, len = _actual.getCount(); i < len; i++) {
559
actual[i] = {
560
endIndex: _actual.getEndOffset(i),
561
foreground: _actual.getForeground(i)
562
};
563
}
564
const decode = (token: TestLineToken) => {
565
return {
566
endIndex: token.endIndex,
567
foreground: token.getForeground()
568
};
569
};
570
assert.deepStrictEqual(actual, expected.map(decode));
571
}
572
573
let _tokenId = 10;
574
const LANG_ID1 = 'indicisiveMode1';
575
const LANG_ID2 = 'indicisiveMode2';
576
577
const tokenizationSupport: ITokenizationSupport = {
578
getInitialState: () => NullState,
579
tokenize: undefined!,
580
tokenizeEncoded: (line, hasEOL, state) => {
581
const myId = ++_tokenId;
582
const tokens = new Uint32Array(2);
583
tokens[0] = 0;
584
tokens[1] = (
585
myId << MetadataConsts.FOREGROUND_OFFSET
586
) >>> 0;
587
return new EncodedTokenizationResult(tokens, state);
588
}
589
};
590
591
const registration1 = TokenizationRegistry.register(LANG_ID1, tokenizationSupport);
592
const registration2 = TokenizationRegistry.register(LANG_ID2, tokenizationSupport);
593
594
const model = createTextModel('A model with\ntwo lines');
595
596
assertViewLineTokens(model, 1, true, [createViewLineToken(12, 1)]);
597
assertViewLineTokens(model, 2, true, [createViewLineToken(9, 1)]);
598
599
model.setLanguage(LANG_ID1);
600
601
assertViewLineTokens(model, 1, true, [createViewLineToken(12, 11)]);
602
assertViewLineTokens(model, 2, true, [createViewLineToken(9, 12)]);
603
604
model.setLanguage(LANG_ID2);
605
606
assertViewLineTokens(model, 1, false, [createViewLineToken(12, 1)]);
607
assertViewLineTokens(model, 2, false, [createViewLineToken(9, 1)]);
608
609
model.dispose();
610
registration1.dispose();
611
registration2.dispose();
612
613
function createViewLineToken(endIndex: number, foreground: number): TestLineToken {
614
const metadata = (
615
(foreground << MetadataConsts.FOREGROUND_OFFSET)
616
) >>> 0;
617
return new TestLineToken(endIndex, metadata);
618
}
619
});
620
621
622
test('microsoft/monaco-editor#133: Error: Cannot read property \'modeId\' of undefined', () => {
623
624
const disposables = new DisposableStore();
625
const model = createTextModelWithBrackets(
626
disposables,
627
[
628
'Imports System',
629
'Imports System.Collections.Generic',
630
'',
631
'Module m1',
632
'',
633
'\tSub Main()',
634
'\tEnd Sub',
635
'',
636
'End Module',
637
].join('\n'),
638
[
639
['module', 'end module'],
640
['sub', 'end sub']
641
]
642
);
643
644
const actual = model.bracketPairs.matchBracket(new Position(4, 1));
645
assert.deepStrictEqual(actual, [new Range(4, 1, 4, 7), new Range(9, 1, 9, 11)]);
646
647
disposables.dispose();
648
});
649
650
test('issue #11856: Bracket matching does not work as expected if the opening brace symbol is contained in the closing brace symbol', () => {
651
652
const disposables = new DisposableStore();
653
const model = createTextModelWithBrackets(
654
disposables,
655
[
656
'sequence "outer"',
657
' sequence "inner"',
658
' endsequence',
659
'endsequence',
660
].join('\n'),
661
[
662
['sequence', 'endsequence'],
663
['feature', 'endfeature']
664
]
665
);
666
667
const actual = model.bracketPairs.matchBracket(new Position(3, 9));
668
assert.deepStrictEqual(actual, [new Range(2, 6, 2, 14), new Range(3, 6, 3, 17)]);
669
670
disposables.dispose();
671
});
672
673
test('issue #63822: Wrong embedded language detected for empty lines', () => {
674
const disposables = new DisposableStore();
675
const instantiationService = createModelServices(disposables);
676
const languageService = instantiationService.get(ILanguageService);
677
678
const outerMode = 'outerMode';
679
const innerMode = 'innerMode';
680
681
disposables.add(languageService.registerLanguage({ id: outerMode }));
682
disposables.add(languageService.registerLanguage({ id: innerMode }));
683
684
const languageIdCodec = instantiationService.get(ILanguageService).languageIdCodec;
685
const encodedInnerMode = languageIdCodec.encodeLanguageId(innerMode);
686
687
const tokenizationSupport: ITokenizationSupport = {
688
getInitialState: () => NullState,
689
tokenize: undefined!,
690
tokenizeEncoded: (line, hasEOL, state) => {
691
const tokens = new Uint32Array(2);
692
tokens[0] = 0;
693
tokens[1] = (
694
encodedInnerMode << MetadataConsts.LANGUAGEID_OFFSET
695
) >>> 0;
696
return new EncodedTokenizationResult(tokens, state);
697
}
698
};
699
700
disposables.add(TokenizationRegistry.register(outerMode, tokenizationSupport));
701
702
const model = disposables.add(instantiateTextModel(instantiationService, 'A model with one line', outerMode));
703
704
model.tokenization.forceTokenization(1);
705
assert.strictEqual(model.getLanguageIdAtPosition(1, 1), innerMode);
706
707
disposables.dispose();
708
});
709
});
710
711
suite('TextModel.getLineIndentGuide', () => {
712
713
ensureNoDisposablesAreLeakedInTestSuite();
714
715
function assertIndentGuides(lines: [number, number, number, number, string][], indentSize: number): void {
716
const languageId = 'testLang';
717
const disposables = new DisposableStore();
718
const instantiationService = createModelServices(disposables);
719
const languageService = instantiationService.get(ILanguageService);
720
disposables.add(languageService.registerLanguage({ id: languageId }));
721
722
const text = lines.map(l => l[4]).join('\n');
723
const model = disposables.add(instantiateTextModel(instantiationService, text, languageId));
724
model.updateOptions({ indentSize: indentSize });
725
726
const actualIndents = model.guides.getLinesIndentGuides(1, model.getLineCount());
727
728
const actual: [number, number, number, number, string][] = [];
729
for (let line = 1; line <= model.getLineCount(); line++) {
730
const activeIndentGuide = model.guides.getActiveIndentGuide(line, 1, model.getLineCount());
731
actual[line - 1] = [actualIndents[line - 1], activeIndentGuide.startLineNumber, activeIndentGuide.endLineNumber, activeIndentGuide.indent, model.getLineContent(line)];
732
}
733
734
assert.deepStrictEqual(actual, lines);
735
736
disposables.dispose();
737
}
738
739
test('getLineIndentGuide one level 2', () => {
740
assertIndentGuides([
741
[0, 2, 4, 1, 'A'],
742
[1, 2, 4, 1, ' A'],
743
[1, 2, 4, 1, ' A'],
744
[1, 2, 4, 1, ' A'],
745
], 2);
746
});
747
748
test('getLineIndentGuide two levels', () => {
749
assertIndentGuides([
750
[0, 2, 5, 1, 'A'],
751
[1, 2, 5, 1, ' A'],
752
[1, 4, 5, 2, ' A'],
753
[2, 4, 5, 2, ' A'],
754
[2, 4, 5, 2, ' A'],
755
], 2);
756
});
757
758
test('getLineIndentGuide three levels', () => {
759
assertIndentGuides([
760
[0, 2, 4, 1, 'A'],
761
[1, 3, 4, 2, ' A'],
762
[2, 4, 4, 3, ' A'],
763
[3, 4, 4, 3, ' A'],
764
[0, 5, 5, 0, 'A'],
765
], 2);
766
});
767
768
test('getLineIndentGuide decreasing indent', () => {
769
assertIndentGuides([
770
[2, 1, 1, 2, ' A'],
771
[1, 1, 1, 2, ' A'],
772
[0, 1, 2, 1, 'A'],
773
], 2);
774
});
775
776
test('getLineIndentGuide Java', () => {
777
assertIndentGuides([
778
/* 1*/[0, 2, 9, 1, 'class A {'],
779
/* 2*/[1, 3, 4, 2, ' void foo() {'],
780
/* 3*/[2, 3, 4, 2, ' console.log(1);'],
781
/* 4*/[2, 3, 4, 2, ' console.log(2);'],
782
/* 5*/[1, 3, 4, 2, ' }'],
783
/* 6*/[1, 2, 9, 1, ''],
784
/* 7*/[1, 8, 8, 2, ' void bar() {'],
785
/* 8*/[2, 8, 8, 2, ' console.log(3);'],
786
/* 9*/[1, 8, 8, 2, ' }'],
787
/*10*/[0, 2, 9, 1, '}'],
788
/*11*/[0, 12, 12, 1, 'interface B {'],
789
/*12*/[1, 12, 12, 1, ' void bar();'],
790
/*13*/[0, 12, 12, 1, '}'],
791
], 2);
792
});
793
794
test('getLineIndentGuide Javadoc', () => {
795
assertIndentGuides([
796
[0, 2, 3, 1, '/**'],
797
[1, 2, 3, 1, ' * Comment'],
798
[1, 2, 3, 1, ' */'],
799
[0, 5, 6, 1, 'class A {'],
800
[1, 5, 6, 1, ' void foo() {'],
801
[1, 5, 6, 1, ' }'],
802
[0, 5, 6, 1, '}'],
803
], 2);
804
});
805
806
test('getLineIndentGuide Whitespace', () => {
807
assertIndentGuides([
808
[0, 2, 7, 1, 'class A {'],
809
[1, 2, 7, 1, ''],
810
[1, 4, 5, 2, ' void foo() {'],
811
[2, 4, 5, 2, ' '],
812
[2, 4, 5, 2, ' return 1;'],
813
[1, 4, 5, 2, ' }'],
814
[1, 2, 7, 1, ' '],
815
[0, 2, 7, 1, '}']
816
], 2);
817
});
818
819
test('getLineIndentGuide Tabs', () => {
820
assertIndentGuides([
821
[0, 2, 7, 1, 'class A {'],
822
[1, 2, 7, 1, '\t\t'],
823
[1, 4, 5, 2, '\tvoid foo() {'],
824
[2, 4, 5, 2, '\t \t//hello'],
825
[2, 4, 5, 2, '\t return 2;'],
826
[1, 4, 5, 2, ' \t}'],
827
[1, 2, 7, 1, ' '],
828
[0, 2, 7, 1, '}']
829
], 4);
830
});
831
832
test('getLineIndentGuide checker.ts', () => {
833
assertIndentGuides([
834
/* 1*/[0, 1, 1, 0, '/// <reference path="binder.ts"/>'],
835
/* 2*/[0, 2, 2, 0, ''],
836
/* 3*/[0, 3, 3, 0, '/* @internal */'],
837
/* 4*/[0, 5, 16, 1, 'namespace ts {'],
838
/* 5*/[1, 5, 16, 1, ' let nextSymbolId = 1;'],
839
/* 6*/[1, 5, 16, 1, ' let nextNodeId = 1;'],
840
/* 7*/[1, 5, 16, 1, ' let nextMergeId = 1;'],
841
/* 8*/[1, 5, 16, 1, ' let nextFlowId = 1;'],
842
/* 9*/[1, 5, 16, 1, ''],
843
/*10*/[1, 11, 15, 2, ' export function getNodeId(node: Node): number {'],
844
/*11*/[2, 12, 13, 3, ' if (!node.id) {'],
845
/*12*/[3, 12, 13, 3, ' node.id = nextNodeId;'],
846
/*13*/[3, 12, 13, 3, ' nextNodeId++;'],
847
/*14*/[2, 12, 13, 3, ' }'],
848
/*15*/[2, 11, 15, 2, ' return node.id;'],
849
/*16*/[1, 11, 15, 2, ' }'],
850
/*17*/[0, 5, 16, 1, '}']
851
], 4);
852
});
853
854
test('issue #8425 - Missing indentation lines for first level indentation', () => {
855
assertIndentGuides([
856
[1, 2, 3, 2, '\tindent1'],
857
[2, 2, 3, 2, '\t\tindent2'],
858
[2, 2, 3, 2, '\t\tindent2'],
859
[1, 2, 3, 2, '\tindent1']
860
], 4);
861
});
862
863
test('issue #8952 - Indentation guide lines going through text on .yml file', () => {
864
assertIndentGuides([
865
[0, 2, 5, 1, 'properties:'],
866
[1, 3, 5, 2, ' emailAddress:'],
867
[2, 3, 5, 2, ' - bla'],
868
[2, 5, 5, 3, ' - length:'],
869
[3, 5, 5, 3, ' max: 255'],
870
[0, 6, 6, 0, 'getters:']
871
], 4);
872
});
873
874
test('issue #11892 - Indent guides look funny', () => {
875
assertIndentGuides([
876
[0, 2, 7, 1, 'function test(base) {'],
877
[1, 3, 6, 2, '\tswitch (base) {'],
878
[2, 4, 4, 3, '\t\tcase 1:'],
879
[3, 4, 4, 3, '\t\t\treturn 1;'],
880
[2, 6, 6, 3, '\t\tcase 2:'],
881
[3, 6, 6, 3, '\t\t\treturn 2;'],
882
[1, 2, 7, 1, '\t}'],
883
[0, 2, 7, 1, '}']
884
], 4);
885
});
886
887
test('issue #12398 - Problem in indent guidelines', () => {
888
assertIndentGuides([
889
[2, 2, 2, 3, '\t\t.bla'],
890
[3, 2, 2, 3, '\t\t\tlabel(for)'],
891
[0, 3, 3, 0, 'include script']
892
], 4);
893
});
894
895
test('issue #49173', () => {
896
const model = createTextModel([
897
'class A {',
898
' public m1(): void {',
899
' }',
900
' public m2(): void {',
901
' }',
902
' public m3(): void {',
903
' }',
904
' public m4(): void {',
905
' }',
906
' public m5(): void {',
907
' }',
908
'}',
909
].join('\n'));
910
911
const actual = model.guides.getActiveIndentGuide(2, 4, 9);
912
assert.deepStrictEqual(actual, { startLineNumber: 2, endLineNumber: 9, indent: 1 });
913
model.dispose();
914
});
915
916
test('tweaks - no active', () => {
917
assertIndentGuides([
918
[0, 1, 1, 0, 'A'],
919
[0, 2, 2, 0, 'A']
920
], 2);
921
});
922
923
test('tweaks - inside scope', () => {
924
assertIndentGuides([
925
[0, 2, 2, 1, 'A'],
926
[1, 2, 2, 1, ' A']
927
], 2);
928
});
929
930
test('tweaks - scope start', () => {
931
assertIndentGuides([
932
[0, 2, 2, 1, 'A'],
933
[1, 2, 2, 1, ' A'],
934
[0, 2, 2, 1, 'A']
935
], 2);
936
});
937
938
test('tweaks - empty line', () => {
939
assertIndentGuides([
940
[0, 2, 4, 1, 'A'],
941
[1, 2, 4, 1, ' A'],
942
[1, 2, 4, 1, ''],
943
[1, 2, 4, 1, ' A'],
944
[0, 2, 4, 1, 'A']
945
], 2);
946
});
947
});
948
949