Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts
5240 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 { CharCode } from '../../../../base/common/charCode.js';
8
import * as strings from '../../../../base/common/strings.js';
9
import { assertSnapshot } from '../../../../base/test/common/snapshot.js';
10
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
11
import { OffsetRange } from '../../../common/core/ranges/offsetRange.js';
12
import { MetadataConsts } from '../../../common/encodedTokenAttributes.js';
13
import { IViewLineTokens } from '../../../common/tokens/lineTokens.js';
14
import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js';
15
import { CharacterMapping, DomPosition, IRenderLineInputOptions, RenderLineInput, RenderLineOutput2, renderViewLine2 as renderViewLine } from '../../../common/viewLayout/viewLineRenderer.js';
16
import { InlineDecorationType } from '../../../common/viewModel/inlineDecorations.js';
17
import { TestLineToken, TestLineTokens } from '../core/testLineToken.js';
18
19
const HTML_EXTENSION = { extension: 'html' };
20
21
function createViewLineTokens(viewLineTokens: TestLineToken[]): IViewLineTokens {
22
return new TestLineTokens(viewLineTokens);
23
}
24
25
function createPart(endIndex: number, foreground: number): TestLineToken {
26
return new TestLineToken(endIndex, (
27
foreground << MetadataConsts.FOREGROUND_OFFSET
28
) >>> 0);
29
}
30
31
function inflateRenderLineOutput(renderLineOutput: RenderLineOutput2) {
32
// remove encompassing <span> to simplify test writing.
33
let html = renderLineOutput.html;
34
if (html.startsWith('<span>')) {
35
html = html.replace(/^<span>/, '');
36
}
37
html = html.replace(/<\/span>$/, '');
38
const spans: string[] = [];
39
let lastIndex = 0;
40
do {
41
const newIndex = html.indexOf('<span', lastIndex + 1);
42
if (newIndex === -1) {
43
break;
44
}
45
spans.push(html.substring(lastIndex, newIndex));
46
lastIndex = newIndex;
47
} while (true);
48
spans.push(html.substring(lastIndex));
49
50
return {
51
html: spans,
52
mapping: renderLineOutput.characterMapping.inflate(),
53
};
54
}
55
56
type IRelaxedRenderLineInputOptions = Partial<IRenderLineInputOptions>;
57
58
const defaultRenderLineInputOptions: IRenderLineInputOptions = {
59
useMonospaceOptimizations: false,
60
canUseHalfwidthRightwardsArrow: true,
61
lineContent: '',
62
continuesWithWrappedLine: false,
63
isBasicASCII: true,
64
containsRTL: false,
65
fauxIndentLength: 0,
66
lineTokens: createViewLineTokens([]),
67
lineDecorations: [],
68
tabSize: 4,
69
startVisibleColumn: 0,
70
spaceWidth: 10,
71
middotWidth: 10,
72
wsmiddotWidth: 10,
73
stopRenderingLineAfter: -1,
74
renderWhitespace: 'none',
75
renderControlCharacters: false,
76
fontLigatures: false,
77
selectionsOnLine: null,
78
textDirection: null,
79
verticalScrollbarSize: 14,
80
renderNewLineWhenEmpty: false
81
};
82
83
function createRenderLineInputOptions(opts: IRelaxedRenderLineInputOptions): IRenderLineInputOptions {
84
return {
85
...defaultRenderLineInputOptions,
86
...opts
87
};
88
}
89
90
function createRenderLineInput(opts: IRelaxedRenderLineInputOptions): RenderLineInput {
91
const options = createRenderLineInputOptions(opts);
92
return new RenderLineInput(
93
options.useMonospaceOptimizations,
94
options.canUseHalfwidthRightwardsArrow,
95
options.lineContent,
96
options.continuesWithWrappedLine,
97
options.isBasicASCII,
98
options.containsRTL,
99
options.fauxIndentLength,
100
options.lineTokens,
101
options.lineDecorations,
102
options.tabSize,
103
options.startVisibleColumn,
104
options.spaceWidth,
105
options.middotWidth,
106
options.wsmiddotWidth,
107
options.stopRenderingLineAfter,
108
options.renderWhitespace,
109
options.renderControlCharacters,
110
options.fontLigatures,
111
options.selectionsOnLine,
112
options.textDirection,
113
options.verticalScrollbarSize,
114
options.renderNewLineWhenEmpty
115
);
116
}
117
118
suite('renderViewLine', () => {
119
120
ensureNoDisposablesAreLeakedInTestSuite();
121
122
function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[]): void {
123
const _actual = renderViewLine(createRenderLineInput({
124
lineContent,
125
isBasicASCII: strings.isBasicASCII(lineContent),
126
lineTokens: createViewLineTokens([new TestLineToken(lineContent.length, 0)]),
127
tabSize,
128
spaceWidth: 0,
129
middotWidth: 0,
130
wsmiddotWidth: 0
131
}));
132
133
assert.strictEqual(_actual.html, '<span><span class="mtk0">' + expected + '</span></span>');
134
const info = expectedCharOffsetInPart.map<CharacterMappingInfo>((absoluteOffset) => [absoluteOffset, [0, absoluteOffset]]);
135
assertCharacterMapping3(_actual.characterMapping, info);
136
}
137
138
test('replaces spaces', () => {
139
assertCharacterReplacement(' ', 4, '\u00a0', [0, 1]);
140
assertCharacterReplacement(' ', 4, '\u00a0\u00a0', [0, 1, 2]);
141
assertCharacterReplacement('a b', 4, 'a\u00a0\u00a0b', [0, 1, 2, 3, 4]);
142
});
143
144
test('escapes HTML markup', () => {
145
assertCharacterReplacement('a<b', 4, 'a&lt;b', [0, 1, 2, 3]);
146
assertCharacterReplacement('a>b', 4, 'a&gt;b', [0, 1, 2, 3]);
147
assertCharacterReplacement('a&b', 4, 'a&amp;b', [0, 1, 2, 3]);
148
});
149
150
test('replaces some bad characters', () => {
151
assertCharacterReplacement('a\0b', 4, 'a&#00;b', [0, 1, 2, 3]);
152
assertCharacterReplacement('a' + String.fromCharCode(CharCode.UTF8_BOM) + 'b', 4, 'a\ufffdb', [0, 1, 2, 3]);
153
assertCharacterReplacement('a\u2028b', 4, 'a\ufffdb', [0, 1, 2, 3]);
154
});
155
156
test('handles tabs', () => {
157
assertCharacterReplacement('\t', 4, '\u00a0\u00a0\u00a0\u00a0', [0, 4]);
158
assertCharacterReplacement('x\t', 4, 'x\u00a0\u00a0\u00a0', [0, 1, 4]);
159
assertCharacterReplacement('xx\t', 4, 'xx\u00a0\u00a0', [0, 1, 2, 4]);
160
assertCharacterReplacement('xxx\t', 4, 'xxx\u00a0', [0, 1, 2, 3, 4]);
161
assertCharacterReplacement('xxxx\t', 4, 'xxxx\u00a0\u00a0\u00a0\u00a0', [0, 1, 2, 3, 4, 8]);
162
});
163
164
function assertParts(lineContent: string, tabSize: number, parts: TestLineToken[], expected: string, info: CharacterMappingInfo[]): void {
165
const _actual = renderViewLine(createRenderLineInput({
166
lineContent,
167
lineTokens: createViewLineTokens(parts),
168
tabSize,
169
spaceWidth: 0,
170
middotWidth: 0,
171
wsmiddotWidth: 0
172
}));
173
174
assert.strictEqual(_actual.html, '<span>' + expected + '</span>');
175
assertCharacterMapping3(_actual.characterMapping, info);
176
}
177
178
test('empty line', () => {
179
assertParts('', 4, [], '<span></span>', []);
180
});
181
182
test('uses part type', () => {
183
assertParts('x', 4, [createPart(1, 10)], '<span class="mtk10">x</span>', [[0, [0, 0]], [1, [0, 1]]]);
184
assertParts('x', 4, [createPart(1, 20)], '<span class="mtk20">x</span>', [[0, [0, 0]], [1, [0, 1]]]);
185
assertParts('x', 4, [createPart(1, 30)], '<span class="mtk30">x</span>', [[0, [0, 0]], [1, [0, 1]]]);
186
});
187
188
test('two parts', () => {
189
assertParts('xy', 4, [createPart(1, 1), createPart(2, 2)], '<span class="mtk1">x</span><span class="mtk2">y</span>', [[0, [0, 0]], [1, [1, 0]], [2, [1, 1]]]);
190
assertParts('xyz', 4, [createPart(1, 1), createPart(3, 2)], '<span class="mtk1">x</span><span class="mtk2">yz</span>', [[0, [0, 0]], [1, [1, 0]], [2, [1, 1]], [3, [1, 2]]]);
191
assertParts('xyz', 4, [createPart(2, 1), createPart(3, 2)], '<span class="mtk1">xy</span><span class="mtk2">z</span>', [[0, [0, 0]], [1, [0, 1]], [2, [1, 0]], [3, [1, 1]]]);
192
});
193
194
// overflow
195
test('overflow', async () => {
196
const _actual = renderViewLine(createRenderLineInput({
197
lineContent: 'Hello world!',
198
lineTokens: createViewLineTokens([
199
createPart(1, 0),
200
createPart(2, 1),
201
createPart(3, 2),
202
createPart(4, 3),
203
createPart(5, 4),
204
createPart(6, 5),
205
createPart(7, 6),
206
createPart(8, 7),
207
createPart(9, 8),
208
createPart(10, 9),
209
createPart(11, 10),
210
createPart(12, 11),
211
]),
212
stopRenderingLineAfter: 6,
213
renderWhitespace: 'boundary'
214
}));
215
216
const inflated = inflateRenderLineOutput(_actual);
217
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
218
await assertSnapshot(inflated.mapping);
219
});
220
221
// typical line
222
test('typical', async () => {
223
const lineContent = '\t export class Game { // http://test.com ';
224
const lineTokens = createViewLineTokens([
225
createPart(5, 1),
226
createPart(11, 2),
227
createPart(12, 3),
228
createPart(17, 4),
229
createPart(18, 5),
230
createPart(22, 6),
231
createPart(23, 7),
232
createPart(24, 8),
233
createPart(25, 9),
234
createPart(28, 10),
235
createPart(43, 11),
236
createPart(48, 12),
237
]);
238
const _actual = renderViewLine(createRenderLineInput({
239
lineContent,
240
lineTokens,
241
renderWhitespace: 'boundary'
242
}));
243
244
const inflated = inflateRenderLineOutput(_actual);
245
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
246
await assertSnapshot(inflated.mapping);
247
});
248
249
// issue #2255: Weird line rendering part 1
250
test('issue-2255-1', async () => {
251
const lineContent = '\t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),';
252
const lineTokens = createViewLineTokens([
253
createPart(3, 1), // 3 chars
254
createPart(15, 2), // 12 chars
255
createPart(21, 3), // 6 chars
256
createPart(22, 4), // 1 char
257
createPart(43, 5), // 21 chars
258
createPart(45, 6), // 2 chars
259
createPart(46, 7), // 1 char
260
createPart(66, 8), // 20 chars
261
createPart(67, 9), // 1 char
262
createPart(68, 10), // 2 chars
263
]);
264
const _actual = renderViewLine(createRenderLineInput({
265
lineContent,
266
lineTokens
267
}));
268
269
const inflated = inflateRenderLineOutput(_actual);
270
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
271
await assertSnapshot(inflated.mapping);
272
});
273
274
// issue #2255: Weird line rendering part 2
275
test('issue-2255-2', async () => {
276
const lineContent = ' \t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),';
277
278
const lineTokens = createViewLineTokens([
279
createPart(4, 1), // 4 chars
280
createPart(16, 2), // 12 chars
281
createPart(22, 3), // 6 chars
282
createPart(23, 4), // 1 char
283
createPart(44, 5), // 21 chars
284
createPart(46, 6), // 2 chars
285
createPart(47, 7), // 1 char
286
createPart(67, 8), // 20 chars
287
createPart(68, 9), // 1 char
288
createPart(69, 10), // 2 chars
289
]);
290
const _actual = renderViewLine(createRenderLineInput({
291
lineContent,
292
lineTokens
293
}));
294
295
const inflated = inflateRenderLineOutput(_actual);
296
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
297
await assertSnapshot(inflated.mapping);
298
});
299
300
// issue #91178: after decoration type shown before cursor
301
test('issue-91178', async () => {
302
const lineContent = '//just a comment';
303
const lineTokens = createViewLineTokens([
304
createPart(16, 1)
305
]);
306
const actual = renderViewLine(createRenderLineInput({
307
useMonospaceOptimizations: true,
308
canUseHalfwidthRightwardsArrow: false,
309
lineContent,
310
lineTokens,
311
lineDecorations: [
312
new LineDecoration(13, 13, 'dec1', InlineDecorationType.After),
313
new LineDecoration(13, 13, 'dec2', InlineDecorationType.Before),
314
]
315
}));
316
317
const inflated = inflateRenderLineOutput(actual);
318
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
319
await assertSnapshot(inflated.mapping);
320
});
321
322
// issue microsoft/monaco-editor#280: Improved source code rendering for RTL languages
323
test('monaco-280', async () => {
324
const lineContent = 'var קודמות = \"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם\";';
325
const lineTokens = createViewLineTokens([
326
createPart(3, 6),
327
createPart(13, 1),
328
createPart(66, 20),
329
createPart(67, 1),
330
]);
331
const _actual = renderViewLine(createRenderLineInput({
332
lineContent,
333
isBasicASCII: false,
334
containsRTL: true,
335
lineTokens
336
}));
337
338
const inflated = inflateRenderLineOutput(_actual);
339
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
340
await assertSnapshot(inflated.mapping);
341
});
342
343
// issue #137036: Issue in RTL languages in recent versions
344
test('issue-137036', async () => {
345
const lineContent = '<option value=\"العربية\">العربية</option>';
346
const lineTokens = createViewLineTokens([
347
createPart(1, 2),
348
createPart(7, 3),
349
createPart(8, 4),
350
createPart(13, 5),
351
createPart(14, 4),
352
createPart(23, 6),
353
createPart(24, 2),
354
createPart(31, 4),
355
createPart(33, 2),
356
createPart(39, 3),
357
createPart(40, 2),
358
]);
359
const _actual = renderViewLine(createRenderLineInput({
360
lineContent,
361
isBasicASCII: false,
362
containsRTL: true,
363
lineTokens
364
}));
365
366
const inflated = inflateRenderLineOutput(_actual);
367
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
368
await assertSnapshot(inflated.mapping);
369
});
370
371
// issue #99589: Rendering whitespace influences bidi layout
372
test('issue-99589', async () => {
373
const lineContent = ' [\"🖨️ چاپ فاکتور\",\"🎨 تنظیمات\"]';
374
const lineTokens = createViewLineTokens([
375
createPart(5, 2),
376
createPart(21, 3),
377
createPart(22, 2),
378
createPart(34, 3),
379
createPart(35, 2),
380
]);
381
const _actual = renderViewLine(createRenderLineInput({
382
useMonospaceOptimizations: true,
383
lineContent,
384
isBasicASCII: false,
385
containsRTL: true,
386
lineTokens,
387
renderWhitespace: 'all'
388
}));
389
390
const inflated = inflateRenderLineOutput(_actual);
391
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
392
await assertSnapshot(inflated.mapping);
393
});
394
395
// issue #260239: HTML containing bidirectional text is rendered incorrectly
396
test('issue-260239', async () => {
397
// Simulating HTML like: <p class="myclass" title="العربي">نشاط التدويل!</p>
398
// The line contains both LTR (class="myclass") and RTL (title="العربي") attribute values
399
const lineContent = '<p class="myclass" title="العربي">نشاط التدويل!</p>';
400
const lineTokens = createViewLineTokens([
401
createPart(1, 1), // <
402
createPart(2, 2), // p
403
createPart(3, 3), // (space)
404
createPart(8, 4), // class
405
createPart(9, 5), // =
406
createPart(10, 6), // "
407
createPart(17, 7), // myclass
408
createPart(18, 6), // "
409
createPart(19, 3), // (space)
410
createPart(24, 4), // title
411
createPart(25, 5), // =
412
createPart(26, 6), // "
413
createPart(32, 8), // العربي (RTL text) - 6 Arabic characters from position 26-31
414
createPart(33, 6), // " - closing quote at position 32
415
createPart(34, 1), // >
416
createPart(47, 9), // نشاط التدويل! (RTL text) - 13 characters from position 34-46
417
createPart(48, 1), // <
418
createPart(49, 2), // /
419
createPart(50, 2), // p
420
createPart(51, 1), // >
421
]);
422
const _actual = renderViewLine(new RenderLineInput(
423
false,
424
true,
425
lineContent,
426
false,
427
false,
428
true,
429
0,
430
lineTokens,
431
[],
432
4,
433
0,
434
10,
435
10,
436
10,
437
-1,
438
'none',
439
false,
440
false,
441
null,
442
null,
443
14
444
));
445
446
const inflated = inflateRenderLineOutput(_actual);
447
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
448
await assertSnapshot(inflated.mapping);
449
});
450
451
// issue #274604: Mixed LTR and RTL in a single token
452
test('issue-274604', async () => {
453
const lineContent = 'test.com##a:-abp-contains(إ)';
454
const lineTokens = createViewLineTokens([
455
createPart(lineContent.length, 1)
456
]);
457
const actual = renderViewLine(createRenderLineInput({
458
lineContent,
459
isBasicASCII: false,
460
containsRTL: true,
461
lineTokens
462
}));
463
464
const inflated = inflateRenderLineOutput(actual);
465
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
466
await assertSnapshot(inflated.mapping);
467
});
468
469
// issue #277693: Mixed LTR and RTL in a single token with template literal
470
test('issue-277693', async () => {
471
const lineContent = 'نام کاربر: ${user.firstName}';
472
const lineTokens = createViewLineTokens([
473
createPart(9, 1), // نام کاربر (RTL string content)
474
createPart(11, 1), // : (space)
475
createPart(13, 2), // ${ (template expression punctuation)
476
createPart(17, 3), // user (variable)
477
createPart(18, 4), // . (punctuation)
478
createPart(27, 3), // firstName (property)
479
createPart(28, 2), // } (template expression punctuation)
480
]);
481
const actual = renderViewLine(createRenderLineInput({
482
lineContent,
483
isBasicASCII: false,
484
containsRTL: true,
485
lineTokens
486
}));
487
488
const inflated = inflateRenderLineOutput(actual);
489
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
490
await assertSnapshot(inflated.mapping);
491
});
492
493
// issue #6885: Splits large tokens
494
test('issue-6885', async () => {
495
// 1 1 1
496
// 1 2 3 4 5 6 7 8 9 0 1 2
497
// 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234
498
const _lineText = 'This is just a long line that contains very interesting text. This is just a long line that contains very interesting text.';
499
500
function assertSplitsTokens(message: string, lineContent: string, expectedOutput: string[]): void {
501
const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]);
502
const actual = renderViewLine(createRenderLineInput({
503
lineContent,
504
lineTokens
505
}));
506
assert.strictEqual(actual.html, '<span>' + expectedOutput.join('') + '</span>', message);
507
}
508
509
// A token with 49 chars
510
{
511
assertSplitsTokens(
512
'49 chars',
513
_lineText.substr(0, 49),
514
[
515
'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0inter</span>',
516
]
517
);
518
}
519
520
// A token with 50 chars
521
{
522
assertSplitsTokens(
523
'50 chars',
524
_lineText.substr(0, 50),
525
[
526
'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0intere</span>',
527
]
528
);
529
}
530
531
// A token with 51 chars
532
{
533
assertSplitsTokens(
534
'51 chars',
535
_lineText.substr(0, 51),
536
[
537
'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0intere</span>',
538
'<span class="mtk1">s</span>',
539
]
540
);
541
}
542
543
// A token with 99 chars
544
{
545
assertSplitsTokens(
546
'99 chars',
547
_lineText.substr(0, 99),
548
[
549
'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0intere</span>',
550
'<span class="mtk1">sting\u00a0text.\u00a0This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contain</span>',
551
]
552
);
553
}
554
555
// A token with 100 chars
556
{
557
assertSplitsTokens(
558
'100 chars',
559
_lineText.substr(0, 100),
560
[
561
'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0intere</span>',
562
'<span class="mtk1">sting\u00a0text.\u00a0This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains</span>',
563
]
564
);
565
}
566
567
// A token with 101 chars
568
{
569
assertSplitsTokens(
570
'101 chars',
571
_lineText.substr(0, 101),
572
[
573
'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0intere</span>',
574
'<span class="mtk1">sting\u00a0text.\u00a0This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains</span>',
575
'<span class="mtk1">\u00a0</span>',
576
]
577
);
578
}
579
});
580
581
// issue #21476: Does not split large tokens when ligatures are on
582
test('issue-21476', async () => {
583
// 1 1 1
584
// 1 2 3 4 5 6 7 8 9 0 1 2
585
// 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234
586
const _lineText = 'This is just a long line that contains very interesting text. This is just a long line that contains very interesting text.';
587
588
function assertSplitsTokens(message: string, lineContent: string, expectedOutput: string[]): void {
589
const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]);
590
const actual = renderViewLine(createRenderLineInput({
591
lineContent,
592
lineTokens,
593
fontLigatures: true
594
}));
595
assert.strictEqual(actual.html, '<span>' + expectedOutput.join('') + '</span>', message);
596
}
597
598
// A token with 101 chars
599
{
600
assertSplitsTokens(
601
'101 chars',
602
_lineText.substr(0, 101),
603
[
604
'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0</span>',
605
'<span class="mtk1">interesting\u00a0text.\u00a0This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0</span>',
606
'<span class="mtk1">contains\u00a0</span>',
607
]
608
);
609
}
610
});
611
612
// issue #20624: Unaligned surrogate pairs are corrupted at multiples of 50 columns
613
test('issue-20624', async () => {
614
const lineContent = 'a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷';
615
const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]);
616
const actual = renderViewLine(createRenderLineInput({
617
lineContent,
618
isBasicASCII: false,
619
lineTokens
620
}));
621
622
await assertSnapshot(inflateRenderLineOutput(actual).html.join(''), HTML_EXTENSION);
623
});
624
625
// issue #6885: Does not split large tokens in RTL text
626
test('issue-6885-rtl', async () => {
627
const lineContent = 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.';
628
const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]);
629
const actual = renderViewLine(createRenderLineInput({
630
lineContent,
631
isBasicASCII: false,
632
containsRTL: true,
633
lineTokens
634
}));
635
636
await assertSnapshot(actual.html, HTML_EXTENSION);
637
});
638
639
// issue #95685: Uses unicode replacement character for Paragraph Separator
640
test('issue-95685', async () => {
641
const lineContent = 'var ftext = [\u2029"Und", "dann", "eines"];';
642
const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]);
643
const actual = renderViewLine(createRenderLineInput({
644
lineContent,
645
isBasicASCII: false,
646
lineTokens
647
}));
648
const inflated = inflateRenderLineOutput(actual);
649
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
650
await assertSnapshot(inflated.mapping);
651
});
652
653
// issue #19673: Monokai Theme bad-highlighting in line wrap
654
test('issue-19673', async () => {
655
const lineContent = ' MongoCallback<string>): void {';
656
const lineTokens = createViewLineTokens([
657
createPart(17, 1),
658
createPart(18, 2),
659
createPart(24, 3),
660
createPart(26, 4),
661
createPart(27, 5),
662
createPart(28, 6),
663
createPart(32, 7),
664
createPart(34, 8),
665
]);
666
const _actual = renderViewLine(createRenderLineInput({
667
useMonospaceOptimizations: true,
668
lineContent,
669
fauxIndentLength: 4,
670
lineTokens
671
}));
672
673
const inflated = inflateRenderLineOutput(_actual);
674
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
675
await assertSnapshot(inflated.mapping);
676
});
677
});
678
679
type CharacterMappingInfo = [number, [number, number]];
680
681
function assertCharacterMapping3(actual: CharacterMapping, expectedInfo: CharacterMappingInfo[]): void {
682
for (let i = 0; i < expectedInfo.length; i++) {
683
const [horizontalOffset, [partIndex, charIndex]] = expectedInfo[i];
684
685
const actualDomPosition = actual.getDomPosition(i + 1);
686
assert.deepStrictEqual(actualDomPosition, new DomPosition(partIndex, charIndex), `getDomPosition(${i + 1})`);
687
688
let partLength = charIndex + 1;
689
for (let j = i + 1; j < expectedInfo.length; j++) {
690
const [, [nextPartIndex, nextCharIndex]] = expectedInfo[j];
691
if (nextPartIndex === partIndex) {
692
partLength = nextCharIndex + 1;
693
} else {
694
break;
695
}
696
}
697
698
const actualColumn = actual.getColumn(new DomPosition(partIndex, charIndex), partLength);
699
assert.strictEqual(actualColumn, i + 1, `actual.getColumn(${partIndex}, ${charIndex})`);
700
701
const actualHorizontalOffset = actual.getHorizontalOffset(i + 1);
702
assert.strictEqual(actualHorizontalOffset, horizontalOffset, `actual.getHorizontalOffset(${i + 1})`);
703
}
704
705
assert.strictEqual(actual.length, expectedInfo.length, `length mismatch`);
706
}
707
708
suite('renderViewLine2', () => {
709
710
ensureNoDisposablesAreLeakedInTestSuite();
711
712
function testCreateLineParts(fontIsMonospace: boolean, lineContent: string, tokens: TestLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all', selections: OffsetRange[] | null) {
713
const actual = renderViewLine(createRenderLineInput({
714
useMonospaceOptimizations: fontIsMonospace,
715
lineContent,
716
fauxIndentLength,
717
lineTokens: createViewLineTokens(tokens),
718
renderWhitespace,
719
selectionsOnLine: selections
720
}));
721
return inflateRenderLineOutput(actual);
722
}
723
724
// issue #18616: Inline decorations ending at the text length are no longer rendered
725
test('issue-18616', async () => {
726
const lineContent = 'https://microsoft.com';
727
const actual = renderViewLine(createRenderLineInput({
728
lineContent,
729
lineTokens: createViewLineTokens([createPart(21, 3)]),
730
lineDecorations: [new LineDecoration(1, 22, 'link', InlineDecorationType.Regular)]
731
}));
732
733
const inflated = inflateRenderLineOutput(actual);
734
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
735
await assertSnapshot(inflated.mapping);
736
});
737
738
// issue #19207: Link in Monokai is not rendered correctly
739
test('issue-19207', async () => {
740
const lineContent = '\'let url = `http://***/_api/web/lists/GetByTitle(\\\'Teambuildingaanvragen\\\')/items`;\'';
741
const actual = renderViewLine(createRenderLineInput({
742
useMonospaceOptimizations: true,
743
lineContent,
744
lineTokens: createViewLineTokens([
745
createPart(49, 6),
746
createPart(51, 4),
747
createPart(72, 6),
748
createPart(74, 4),
749
createPart(84, 6),
750
]),
751
lineDecorations: [
752
new LineDecoration(13, 51, 'detected-link', InlineDecorationType.Regular)
753
]
754
}));
755
756
const inflated = inflateRenderLineOutput(actual);
757
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
758
await assertSnapshot(inflated.mapping);
759
});
760
761
// createLineParts simple
762
test('simple', async () => {
763
const actual = testCreateLineParts(
764
false,
765
'Hello world!',
766
[
767
createPart(12, 1)
768
],
769
0,
770
'none',
771
null
772
);
773
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
774
await assertSnapshot(actual.mapping);
775
});
776
777
// createLineParts simple two tokens
778
test('two-tokens', async () => {
779
const actual = testCreateLineParts(
780
false,
781
'Hello world!',
782
[
783
createPart(6, 1),
784
createPart(12, 2)
785
],
786
0,
787
'none',
788
null
789
);
790
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
791
await assertSnapshot(actual.mapping);
792
});
793
794
// createLineParts render whitespace - 4 leading spaces
795
test('ws-4-leading', async () => {
796
const actual = testCreateLineParts(
797
false,
798
' Hello world! ',
799
[
800
createPart(4, 1),
801
createPart(6, 2),
802
createPart(20, 3)
803
],
804
0,
805
'boundary',
806
null
807
);
808
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
809
await assertSnapshot(actual.mapping);
810
});
811
812
// createLineParts render whitespace - 8 leading spaces
813
test('ws-8-leading', async () => {
814
const actual = testCreateLineParts(
815
false,
816
' Hello world! ',
817
[
818
createPart(8, 1),
819
createPart(10, 2),
820
createPart(28, 3)
821
],
822
0,
823
'boundary',
824
null
825
);
826
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
827
await assertSnapshot(actual.mapping);
828
});
829
830
// createLineParts render whitespace - 2 leading tabs
831
test('ws-2-tabs', async () => {
832
const actual = testCreateLineParts(
833
false,
834
'\t\tHello world!\t',
835
[
836
createPart(2, 1),
837
createPart(4, 2),
838
createPart(15, 3)
839
],
840
0,
841
'boundary',
842
null
843
);
844
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
845
await assertSnapshot(actual.mapping);
846
});
847
848
// createLineParts render whitespace - mixed leading spaces and tabs
849
test('ws-mixed', async () => {
850
const actual = testCreateLineParts(
851
false,
852
' \t\t Hello world! \t \t \t ',
853
[
854
createPart(6, 1),
855
createPart(8, 2),
856
createPart(31, 3)
857
],
858
0,
859
'boundary',
860
null
861
);
862
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
863
await assertSnapshot(actual.mapping);
864
});
865
866
// createLineParts render whitespace skips faux indent
867
test('ws-faux-indent', async () => {
868
const actual = testCreateLineParts(
869
false,
870
'\t\t Hello world! \t \t \t ',
871
[
872
createPart(4, 1),
873
createPart(6, 2),
874
createPart(29, 3)
875
],
876
2,
877
'boundary',
878
null
879
);
880
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
881
await assertSnapshot(actual.mapping);
882
});
883
884
// createLineParts does not emit width for monospace fonts
885
test('ws-monospace', async () => {
886
const actual = testCreateLineParts(
887
true,
888
'\t\t Hello world! \t \t \t ',
889
[
890
createPart(4, 1),
891
createPart(6, 2),
892
createPart(29, 3)
893
],
894
2,
895
'boundary',
896
null
897
);
898
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
899
await assertSnapshot(actual.mapping);
900
});
901
902
// createLineParts render whitespace in middle but not for one space
903
test('ws-middle', async () => {
904
const actual = testCreateLineParts(
905
false,
906
'it it it it',
907
[
908
createPart(6, 1),
909
createPart(7, 2),
910
createPart(13, 3)
911
],
912
0,
913
'boundary',
914
null
915
);
916
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
917
await assertSnapshot(actual.mapping);
918
});
919
920
// createLineParts render whitespace for all in middle
921
test('ws-all-middle', async () => {
922
const actual = testCreateLineParts(
923
false,
924
' Hello world!\t',
925
[
926
createPart(4, 0),
927
createPart(6, 1),
928
createPart(14, 2)
929
],
930
0,
931
'all',
932
null
933
);
934
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
935
await assertSnapshot(actual.mapping);
936
});
937
938
// createLineParts render whitespace for selection with no selections
939
test('ws-sel-none', async () => {
940
const actual = testCreateLineParts(
941
false,
942
' Hello world!\t',
943
[
944
createPart(4, 0),
945
createPart(6, 1),
946
createPart(14, 2)
947
],
948
0,
949
'selection',
950
null
951
);
952
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
953
await assertSnapshot(actual.mapping);
954
});
955
956
// createLineParts render whitespace for selection with whole line selection
957
test('ws-sel-whole', async () => {
958
const actual = testCreateLineParts(
959
false,
960
' Hello world!\t',
961
[
962
createPart(4, 0),
963
createPart(6, 1),
964
createPart(14, 2)
965
],
966
0,
967
'selection',
968
[new OffsetRange(0, 14)]
969
);
970
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
971
await assertSnapshot(actual.mapping);
972
});
973
974
// createLineParts render whitespace for selection with selection spanning part of whitespace
975
test('ws-sel-partial', async () => {
976
const actual = testCreateLineParts(
977
false,
978
' Hello world!\t',
979
[
980
createPart(4, 0),
981
createPart(6, 1),
982
createPart(14, 2)
983
],
984
0,
985
'selection',
986
[new OffsetRange(0, 5)]
987
);
988
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
989
await assertSnapshot(actual.mapping);
990
});
991
992
// createLineParts render whitespace for selection with multiple selections
993
test('ws-sel-multiple', async () => {
994
const actual = testCreateLineParts(
995
false,
996
' Hello world!\t',
997
[
998
createPart(4, 0),
999
createPart(6, 1),
1000
createPart(14, 2)
1001
],
1002
0,
1003
'selection',
1004
[new OffsetRange(0, 5), new OffsetRange(9, 14)]
1005
);
1006
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
1007
await assertSnapshot(actual.mapping);
1008
});
1009
1010
// createLineParts render whitespace for selection with multiple, initially unsorted selections
1011
test('ws-sel-unsorted', async () => {
1012
const actual = testCreateLineParts(
1013
false,
1014
' Hello world!\t',
1015
[
1016
createPart(4, 0),
1017
createPart(6, 1),
1018
createPart(14, 2)
1019
],
1020
0,
1021
'selection',
1022
[new OffsetRange(9, 14), new OffsetRange(0, 5)]
1023
);
1024
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
1025
await assertSnapshot(actual.mapping);
1026
});
1027
1028
// createLineParts render whitespace for selection with selections next to each other
1029
test('ws-sel-adjacent', async () => {
1030
const actual = testCreateLineParts(
1031
false,
1032
' * S',
1033
[
1034
createPart(4, 0)
1035
],
1036
0,
1037
'selection',
1038
[new OffsetRange(0, 1), new OffsetRange(1, 2), new OffsetRange(2, 3)]
1039
);
1040
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
1041
await assertSnapshot(actual.mapping);
1042
});
1043
1044
// createLineParts render whitespace for trailing with leading, inner, and without trailing whitespace
1045
test('ws-trail-no-trail', async () => {
1046
const actual = testCreateLineParts(
1047
false,
1048
' Hello world!',
1049
[
1050
createPart(4, 0),
1051
createPart(6, 1),
1052
createPart(14, 2)
1053
],
1054
0,
1055
'trailing',
1056
null
1057
);
1058
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
1059
await assertSnapshot(actual.mapping);
1060
});
1061
1062
// createLineParts render whitespace for trailing with leading, inner, and trailing whitespace
1063
test('ws-trail-with-trail', async () => {
1064
const actual = testCreateLineParts(
1065
false,
1066
' Hello world! \t',
1067
[
1068
createPart(4, 0),
1069
createPart(6, 1),
1070
createPart(15, 2)
1071
],
1072
0,
1073
'trailing',
1074
null
1075
);
1076
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
1077
await assertSnapshot(actual.mapping);
1078
});
1079
1080
// createLineParts render whitespace for trailing with 8 leading and 8 trailing whitespaces
1081
test('ws-trail-8-8', async () => {
1082
const actual = testCreateLineParts(
1083
false,
1084
' Hello world! ',
1085
[
1086
createPart(8, 1),
1087
createPart(10, 2),
1088
createPart(28, 3)
1089
],
1090
0,
1091
'trailing',
1092
null
1093
);
1094
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
1095
await assertSnapshot(actual.mapping);
1096
});
1097
1098
// createLineParts render whitespace for trailing with line containing only whitespaces
1099
test('ws-trail-only', async () => {
1100
const actual = testCreateLineParts(
1101
false,
1102
' \t ',
1103
[
1104
createPart(2, 0),
1105
createPart(3, 1),
1106
],
1107
0,
1108
'trailing',
1109
null
1110
);
1111
await assertSnapshot(actual.html.join(''), HTML_EXTENSION);
1112
await assertSnapshot(actual.mapping);
1113
});
1114
1115
// createLineParts can handle unsorted inline decorations
1116
test('unsorted-deco', async () => {
1117
const actual = renderViewLine(createRenderLineInput({
1118
lineContent: 'Hello world',
1119
lineTokens: createViewLineTokens([createPart(11, 0)]),
1120
lineDecorations: [
1121
new LineDecoration(5, 7, 'a', InlineDecorationType.Regular),
1122
new LineDecoration(1, 3, 'b', InlineDecorationType.Regular),
1123
new LineDecoration(2, 8, 'c', InlineDecorationType.Regular),
1124
]
1125
}));
1126
1127
// 01234567890
1128
// Hello world
1129
// ----aa-----
1130
// bb---------
1131
// -cccccc----
1132
1133
const inflated = inflateRenderLineOutput(actual);
1134
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1135
await assertSnapshot(inflated.mapping);
1136
});
1137
1138
// issue #11485: Visible whitespace conflicts with before decorator attachment
1139
test('issue-11485', async () => {
1140
1141
const lineContent = '\tbla';
1142
1143
const actual = renderViewLine(createRenderLineInput({
1144
lineContent,
1145
lineTokens: createViewLineTokens([createPart(4, 3)]),
1146
lineDecorations: [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)],
1147
renderWhitespace: 'all',
1148
fontLigatures: true
1149
}));
1150
1151
const inflated = inflateRenderLineOutput(actual);
1152
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1153
await assertSnapshot(inflated.mapping);
1154
});
1155
1156
// issue #32436: Non-monospace font + visible whitespace + After decorator causes line to "jump"
1157
test('issue-32436', async () => {
1158
1159
const lineContent = '\tbla';
1160
1161
const actual = renderViewLine(createRenderLineInput({
1162
lineContent,
1163
lineTokens: createViewLineTokens([createPart(4, 3)]),
1164
lineDecorations: [new LineDecoration(2, 3, 'before', InlineDecorationType.Before)],
1165
renderWhitespace: 'all',
1166
fontLigatures: true
1167
}));
1168
1169
const inflated = inflateRenderLineOutput(actual);
1170
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1171
await assertSnapshot(inflated.mapping);
1172
});
1173
1174
// issue #30133: Empty lines don't render inline decorations
1175
test('issue-30133', async () => {
1176
1177
const lineContent = '';
1178
1179
const actual = renderViewLine(createRenderLineInput({
1180
lineContent,
1181
lineTokens: createViewLineTokens([createPart(0, 3)]),
1182
lineDecorations: [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)],
1183
renderWhitespace: 'all',
1184
fontLigatures: true
1185
}));
1186
1187
const inflated = inflateRenderLineOutput(actual);
1188
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1189
await assertSnapshot(inflated.mapping);
1190
});
1191
1192
// issue #37208: Collapsing bullet point containing emoji in Markdown document results in [??] character
1193
test('issue-37208', async () => {
1194
1195
const actual = renderViewLine(createRenderLineInput({
1196
useMonospaceOptimizations: true,
1197
lineContent: ' 1. 🙏',
1198
isBasicASCII: false,
1199
lineTokens: createViewLineTokens([createPart(7, 3)]),
1200
lineDecorations: [new LineDecoration(7, 8, 'inline-folded', InlineDecorationType.After)],
1201
tabSize: 2,
1202
stopRenderingLineAfter: 10000
1203
}));
1204
1205
const inflated = inflateRenderLineOutput(actual);
1206
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1207
await assertSnapshot(inflated.mapping);
1208
});
1209
1210
// issue #37401 #40127: Allow both before and after decorations on empty line
1211
test('issue-37401', async () => {
1212
1213
const actual = renderViewLine(createRenderLineInput({
1214
useMonospaceOptimizations: true,
1215
lineContent: '',
1216
lineTokens: createViewLineTokens([createPart(0, 3)]),
1217
lineDecorations: [
1218
new LineDecoration(1, 1, 'before', InlineDecorationType.Before),
1219
new LineDecoration(1, 1, 'after', InlineDecorationType.After),
1220
],
1221
tabSize: 2,
1222
stopRenderingLineAfter: 10000
1223
}));
1224
1225
const inflated = inflateRenderLineOutput(actual);
1226
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1227
await assertSnapshot(inflated.mapping);
1228
});
1229
1230
// issue #118759: enable multiple text editor decorations in empty lines
1231
test('issue-118759', async () => {
1232
1233
const actual = renderViewLine(createRenderLineInput({
1234
useMonospaceOptimizations: true,
1235
lineContent: '',
1236
lineTokens: createViewLineTokens([createPart(0, 3)]),
1237
lineDecorations: [
1238
new LineDecoration(1, 1, 'after1', InlineDecorationType.After),
1239
new LineDecoration(1, 1, 'after2', InlineDecorationType.After),
1240
new LineDecoration(1, 1, 'before1', InlineDecorationType.Before),
1241
new LineDecoration(1, 1, 'before2', InlineDecorationType.Before),
1242
],
1243
tabSize: 2,
1244
stopRenderingLineAfter: 10000
1245
}));
1246
1247
const inflated = inflateRenderLineOutput(actual);
1248
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1249
await assertSnapshot(inflated.mapping);
1250
});
1251
1252
// issue #38935: GitLens end-of-line blame no longer rendering
1253
test('issue-38935', async () => {
1254
1255
const actual = renderViewLine(createRenderLineInput({
1256
useMonospaceOptimizations: true,
1257
lineContent: '\t}',
1258
lineTokens: createViewLineTokens([createPart(2, 3)]),
1259
lineDecorations: [
1260
new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-3 ced-TextEditorDecorationType2-3', InlineDecorationType.Before),
1261
new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-4 ced-TextEditorDecorationType2-4', InlineDecorationType.After),
1262
],
1263
stopRenderingLineAfter: 10000
1264
}));
1265
1266
const inflated = inflateRenderLineOutput(actual);
1267
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1268
await assertSnapshot(inflated.mapping);
1269
});
1270
1271
// issue #136622: Inline decorations are not rendering on non-ASCII lines when renderControlCharacters is on
1272
test('issue-136622', async () => {
1273
1274
const actual = renderViewLine(createRenderLineInput({
1275
useMonospaceOptimizations: true,
1276
lineContent: 'some text £',
1277
isBasicASCII: false,
1278
lineTokens: createViewLineTokens([createPart(11, 3)]),
1279
lineDecorations: [
1280
new LineDecoration(5, 5, 'inlineDec1', InlineDecorationType.After),
1281
new LineDecoration(6, 6, 'inlineDec2', InlineDecorationType.Before),
1282
],
1283
stopRenderingLineAfter: 10000,
1284
renderControlCharacters: true
1285
}));
1286
1287
const inflated = inflateRenderLineOutput(actual);
1288
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1289
await assertSnapshot(inflated.mapping);
1290
});
1291
1292
// issue #22832: Consider fullwidth characters when rendering tabs
1293
test('issue-22832-1', async () => {
1294
1295
const actual = renderViewLine(createRenderLineInput({
1296
useMonospaceOptimizations: true,
1297
lineContent: 'asd = "擦"\t\t#asd',
1298
isBasicASCII: false,
1299
lineTokens: createViewLineTokens([createPart(15, 3)]),
1300
stopRenderingLineAfter: 10000
1301
}));
1302
1303
const inflated = inflateRenderLineOutput(actual);
1304
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1305
await assertSnapshot(inflated.mapping);
1306
});
1307
1308
// issue #22832: Consider fullwidth characters when rendering tabs (render whitespace)
1309
test('issue-22832-2', async () => {
1310
1311
const actual = renderViewLine(createRenderLineInput({
1312
useMonospaceOptimizations: true,
1313
lineContent: 'asd = "擦"\t\t#asd',
1314
isBasicASCII: false,
1315
lineTokens: createViewLineTokens([createPart(15, 3)]),
1316
stopRenderingLineAfter: 10000,
1317
renderWhitespace: 'all'
1318
}));
1319
1320
const inflated = inflateRenderLineOutput(actual);
1321
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1322
await assertSnapshot(inflated.mapping);
1323
});
1324
1325
// issue #22352: COMBINING ACUTE ACCENT (U+0301)
1326
test('issue-22352-1', async () => {
1327
1328
const actual = renderViewLine(createRenderLineInput({
1329
useMonospaceOptimizations: true,
1330
lineContent: '12345689012345678901234568901234567890123456890abába',
1331
isBasicASCII: false,
1332
lineTokens: createViewLineTokens([createPart(53, 3)]),
1333
stopRenderingLineAfter: 10000
1334
}));
1335
1336
const inflated = inflateRenderLineOutput(actual);
1337
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1338
await assertSnapshot(inflated.mapping);
1339
});
1340
1341
// issue #22352: Partially Broken Complex Script Rendering of Tamil
1342
test('issue-22352-2', async () => {
1343
1344
const actual = renderViewLine(createRenderLineInput({
1345
useMonospaceOptimizations: true,
1346
lineContent: ' JoyShareல் பின்தொடர்ந்து, விடீயோ, ஜோக்குகள், அனிமேசன், நகைச்சுவை படங்கள் மற்றும் செய்திகளை பெறுவீர்',
1347
isBasicASCII: false,
1348
lineTokens: createViewLineTokens([createPart(100, 3)]),
1349
stopRenderingLineAfter: 10000
1350
}));
1351
1352
const inflated = inflateRenderLineOutput(actual);
1353
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1354
await assertSnapshot(inflated.mapping);
1355
});
1356
1357
// issue #42700: Hindi characters are not being rendered properly
1358
test('issue-42700', async () => {
1359
1360
const actual = renderViewLine(createRenderLineInput({
1361
useMonospaceOptimizations: true,
1362
lineContent: ' वो ऐसा क्या है जो हमारे अंदर भी है और बाहर भी है। जिसकी वजह से हम सब हैं। जिसने इस सृष्टि की रचना की है।',
1363
isBasicASCII: false,
1364
lineTokens: createViewLineTokens([createPart(105, 3)]),
1365
stopRenderingLineAfter: 10000
1366
}));
1367
1368
const inflated = inflateRenderLineOutput(actual);
1369
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1370
await assertSnapshot(inflated.mapping);
1371
});
1372
1373
// issue #38123: editor.renderWhitespace: "boundary" renders whitespace at line wrap point when line is wrapped
1374
test('issue-38123', async () => {
1375
const actual = renderViewLine(createRenderLineInput({
1376
useMonospaceOptimizations: true,
1377
lineContent: 'This is a long line which never uses more than two spaces. ',
1378
continuesWithWrappedLine: true,
1379
lineTokens: createViewLineTokens([createPart(59, 3)]),
1380
stopRenderingLineAfter: 10000,
1381
renderWhitespace: 'boundary'
1382
}));
1383
1384
const inflated = inflateRenderLineOutput(actual);
1385
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1386
await assertSnapshot(inflated.mapping);
1387
});
1388
1389
// issue #33525: Long line with ligatures takes a long time to paint decorations
1390
test('issue-33525-1', async () => {
1391
const actual = renderViewLine(createRenderLineInput({
1392
canUseHalfwidthRightwardsArrow: false,
1393
lineContent: 'append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to',
1394
lineTokens: createViewLineTokens([createPart(194, 3)]),
1395
stopRenderingLineAfter: 10000,
1396
fontLigatures: true
1397
}));
1398
1399
const inflated = inflateRenderLineOutput(actual);
1400
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1401
await assertSnapshot(inflated.mapping);
1402
});
1403
1404
// issue #33525: Long line with ligatures takes a long time to paint decorations - not possible
1405
test('issue-33525-2', async () => {
1406
const actual = renderViewLine(createRenderLineInput({
1407
canUseHalfwidthRightwardsArrow: false,
1408
lineContent: 'appenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatato',
1409
lineTokens: createViewLineTokens([createPart(194, 3)]),
1410
stopRenderingLineAfter: 10000,
1411
fontLigatures: true
1412
}));
1413
1414
const inflated = inflateRenderLineOutput(actual);
1415
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1416
await assertSnapshot(inflated.mapping);
1417
});
1418
1419
// issue #91936: Semantic token color highlighting fails on line with selected text
1420
test('issue-91936', async () => {
1421
const actual = renderViewLine(createRenderLineInput({
1422
lineContent: ' else if ($s = 08) then \'\\b\'',
1423
lineTokens: createViewLineTokens([
1424
createPart(20, 1),
1425
createPart(24, 15),
1426
createPart(25, 1),
1427
createPart(27, 15),
1428
createPart(28, 1),
1429
createPart(29, 1),
1430
createPart(29, 1),
1431
createPart(31, 16),
1432
createPart(32, 1),
1433
createPart(33, 1),
1434
createPart(34, 1),
1435
createPart(36, 6),
1436
createPart(36, 1),
1437
createPart(37, 1),
1438
createPart(38, 1),
1439
createPart(42, 15),
1440
createPart(43, 1),
1441
createPart(47, 11)
1442
]),
1443
stopRenderingLineAfter: 10000,
1444
renderWhitespace: 'selection',
1445
selectionsOnLine: [new OffsetRange(0, 47)],
1446
middotWidth: 11,
1447
wsmiddotWidth: 11
1448
}));
1449
1450
const inflated = inflateRenderLineOutput(actual);
1451
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1452
await assertSnapshot(inflated.mapping);
1453
});
1454
1455
// issue #119416: Delete Control Character (U+007F / &#127;) displayed as space
1456
test('issue-119416', async () => {
1457
const actual = renderViewLine(createRenderLineInput({
1458
canUseHalfwidthRightwardsArrow: false,
1459
lineContent: '[' + String.fromCharCode(127) + '] [' + String.fromCharCode(0) + ']',
1460
lineTokens: createViewLineTokens([createPart(7, 3)]),
1461
stopRenderingLineAfter: 10000,
1462
renderControlCharacters: true,
1463
fontLigatures: true
1464
}));
1465
1466
const inflated = inflateRenderLineOutput(actual);
1467
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1468
await assertSnapshot(inflated.mapping);
1469
});
1470
1471
// issue #116939: Important control characters aren't rendered
1472
test('issue-116939', async () => {
1473
const actual = renderViewLine(createRenderLineInput({
1474
canUseHalfwidthRightwardsArrow: false,
1475
lineContent: `transferBalance(5678,${String.fromCharCode(0x202E)}6776,4321${String.fromCharCode(0x202C)},"USD");`,
1476
isBasicASCII: false,
1477
lineTokens: createViewLineTokens([createPart(42, 3)]),
1478
stopRenderingLineAfter: 10000,
1479
renderControlCharacters: true
1480
}));
1481
1482
const inflated = inflateRenderLineOutput(actual);
1483
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1484
await assertSnapshot(inflated.mapping);
1485
});
1486
1487
// issue #124038: Multiple end-of-line text decorations get merged
1488
test('issue-124038', async () => {
1489
const actual = renderViewLine(createRenderLineInput({
1490
useMonospaceOptimizations: true,
1491
canUseHalfwidthRightwardsArrow: false,
1492
lineContent: ' if',
1493
lineTokens: createViewLineTokens([createPart(4, 1), createPart(6, 2)]),
1494
lineDecorations: [
1495
new LineDecoration(7, 7, 'ced-1-TextEditorDecorationType2-17c14d98-3 ced-1-TextEditorDecorationType2-3', InlineDecorationType.Before),
1496
new LineDecoration(7, 7, 'ced-1-TextEditorDecorationType2-17c14d98-4 ced-1-TextEditorDecorationType2-4', InlineDecorationType.After),
1497
new LineDecoration(7, 7, 'ced-ghost-text-1-4', InlineDecorationType.After),
1498
],
1499
stopRenderingLineAfter: 10000,
1500
renderWhitespace: 'all'
1501
}));
1502
1503
const inflated = inflateRenderLineOutput(actual);
1504
await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);
1505
await assertSnapshot(inflated.mapping);
1506
});
1507
1508
function createTestGetColumnOfLinePartOffset(lineContent: string, tabSize: number, parts: TestLineToken[], expectedPartLengths: number[]): (partIndex: number, partLength: number, offset: number, expected: number) => void {
1509
const renderLineOutput = renderViewLine(createRenderLineInput({
1510
lineContent,
1511
tabSize,
1512
lineTokens: createViewLineTokens(parts)
1513
}));
1514
1515
return (partIndex: number, partLength: number, offset: number, expected: number) => {
1516
const actualColumn = renderLineOutput.characterMapping.getColumn(new DomPosition(partIndex, offset), partLength);
1517
assert.strictEqual(actualColumn, expected, 'getColumn for ' + partIndex + ', ' + offset);
1518
};
1519
}
1520
1521
test('getColumnOfLinePartOffset 1 - simple text', () => {
1522
const testGetColumnOfLinePartOffset = createTestGetColumnOfLinePartOffset(
1523
'hello world',
1524
4,
1525
[
1526
createPart(11, 1)
1527
],
1528
[11]
1529
);
1530
testGetColumnOfLinePartOffset(0, 11, 0, 1);
1531
testGetColumnOfLinePartOffset(0, 11, 1, 2);
1532
testGetColumnOfLinePartOffset(0, 11, 2, 3);
1533
testGetColumnOfLinePartOffset(0, 11, 3, 4);
1534
testGetColumnOfLinePartOffset(0, 11, 4, 5);
1535
testGetColumnOfLinePartOffset(0, 11, 5, 6);
1536
testGetColumnOfLinePartOffset(0, 11, 6, 7);
1537
testGetColumnOfLinePartOffset(0, 11, 7, 8);
1538
testGetColumnOfLinePartOffset(0, 11, 8, 9);
1539
testGetColumnOfLinePartOffset(0, 11, 9, 10);
1540
testGetColumnOfLinePartOffset(0, 11, 10, 11);
1541
testGetColumnOfLinePartOffset(0, 11, 11, 12);
1542
});
1543
1544
test('getColumnOfLinePartOffset 2 - regular JS', () => {
1545
const testGetColumnOfLinePartOffset = createTestGetColumnOfLinePartOffset(
1546
'var x = 3;',
1547
4,
1548
[
1549
createPart(3, 1),
1550
createPart(4, 2),
1551
createPart(5, 3),
1552
createPart(8, 4),
1553
createPart(9, 5),
1554
createPart(10, 6),
1555
],
1556
[3, 1, 1, 3, 1, 1]
1557
);
1558
testGetColumnOfLinePartOffset(0, 3, 0, 1);
1559
testGetColumnOfLinePartOffset(0, 3, 1, 2);
1560
testGetColumnOfLinePartOffset(0, 3, 2, 3);
1561
testGetColumnOfLinePartOffset(0, 3, 3, 4);
1562
testGetColumnOfLinePartOffset(1, 1, 0, 4);
1563
testGetColumnOfLinePartOffset(1, 1, 1, 5);
1564
testGetColumnOfLinePartOffset(2, 1, 0, 5);
1565
testGetColumnOfLinePartOffset(2, 1, 1, 6);
1566
testGetColumnOfLinePartOffset(3, 3, 0, 6);
1567
testGetColumnOfLinePartOffset(3, 3, 1, 7);
1568
testGetColumnOfLinePartOffset(3, 3, 2, 8);
1569
testGetColumnOfLinePartOffset(3, 3, 3, 9);
1570
testGetColumnOfLinePartOffset(4, 1, 0, 9);
1571
testGetColumnOfLinePartOffset(4, 1, 1, 10);
1572
testGetColumnOfLinePartOffset(5, 1, 0, 10);
1573
testGetColumnOfLinePartOffset(5, 1, 1, 11);
1574
});
1575
1576
test('getColumnOfLinePartOffset 3 - tab with tab size 6', () => {
1577
const testGetColumnOfLinePartOffset = createTestGetColumnOfLinePartOffset(
1578
'\t',
1579
6,
1580
[
1581
createPart(1, 1)
1582
],
1583
[6]
1584
);
1585
testGetColumnOfLinePartOffset(0, 6, 0, 1);
1586
testGetColumnOfLinePartOffset(0, 6, 1, 1);
1587
testGetColumnOfLinePartOffset(0, 6, 2, 1);
1588
testGetColumnOfLinePartOffset(0, 6, 3, 1);
1589
testGetColumnOfLinePartOffset(0, 6, 4, 2);
1590
testGetColumnOfLinePartOffset(0, 6, 5, 2);
1591
testGetColumnOfLinePartOffset(0, 6, 6, 2);
1592
});
1593
1594
test('getColumnOfLinePartOffset 4 - once indented line, tab size 4', () => {
1595
const testGetColumnOfLinePartOffset = createTestGetColumnOfLinePartOffset(
1596
'\tfunction',
1597
4,
1598
[
1599
createPart(1, 1),
1600
createPart(9, 2),
1601
],
1602
[4, 8]
1603
);
1604
testGetColumnOfLinePartOffset(0, 4, 0, 1);
1605
testGetColumnOfLinePartOffset(0, 4, 1, 1);
1606
testGetColumnOfLinePartOffset(0, 4, 2, 1);
1607
testGetColumnOfLinePartOffset(0, 4, 3, 2);
1608
testGetColumnOfLinePartOffset(0, 4, 4, 2);
1609
testGetColumnOfLinePartOffset(1, 8, 0, 2);
1610
testGetColumnOfLinePartOffset(1, 8, 1, 3);
1611
testGetColumnOfLinePartOffset(1, 8, 2, 4);
1612
testGetColumnOfLinePartOffset(1, 8, 3, 5);
1613
testGetColumnOfLinePartOffset(1, 8, 4, 6);
1614
testGetColumnOfLinePartOffset(1, 8, 5, 7);
1615
testGetColumnOfLinePartOffset(1, 8, 6, 8);
1616
testGetColumnOfLinePartOffset(1, 8, 7, 9);
1617
testGetColumnOfLinePartOffset(1, 8, 8, 10);
1618
});
1619
1620
test('getColumnOfLinePartOffset 5 - twice indented line, tab size 4', () => {
1621
const testGetColumnOfLinePartOffset = createTestGetColumnOfLinePartOffset(
1622
'\t\tfunction',
1623
4,
1624
[
1625
createPart(2, 1),
1626
createPart(10, 2),
1627
],
1628
[8, 8]
1629
);
1630
testGetColumnOfLinePartOffset(0, 8, 0, 1);
1631
testGetColumnOfLinePartOffset(0, 8, 1, 1);
1632
testGetColumnOfLinePartOffset(0, 8, 2, 1);
1633
testGetColumnOfLinePartOffset(0, 8, 3, 2);
1634
testGetColumnOfLinePartOffset(0, 8, 4, 2);
1635
testGetColumnOfLinePartOffset(0, 8, 5, 2);
1636
testGetColumnOfLinePartOffset(0, 8, 6, 2);
1637
testGetColumnOfLinePartOffset(0, 8, 7, 3);
1638
testGetColumnOfLinePartOffset(0, 8, 8, 3);
1639
testGetColumnOfLinePartOffset(1, 8, 0, 3);
1640
testGetColumnOfLinePartOffset(1, 8, 1, 4);
1641
testGetColumnOfLinePartOffset(1, 8, 2, 5);
1642
testGetColumnOfLinePartOffset(1, 8, 3, 6);
1643
testGetColumnOfLinePartOffset(1, 8, 4, 7);
1644
testGetColumnOfLinePartOffset(1, 8, 5, 8);
1645
testGetColumnOfLinePartOffset(1, 8, 6, 9);
1646
testGetColumnOfLinePartOffset(1, 8, 7, 10);
1647
testGetColumnOfLinePartOffset(1, 8, 8, 11);
1648
});
1649
});
1650
1651